Your compiler may have further extensions that allow you to pass data in and out of the assembly insert. You didn't tell us which compiler you're using, so I'm going to show you how to do it with GCC, which is the one whose extensions I remember without looking it up:
uint32_t os_SVC(uint32_t n, uint32_t pid, void *p1, void (*p2)(void))
{
register uint32_t rv asm ("r0");
asm ("svc #0" : "=r" (rv))
return rv;
}
The register ... asm ("r0") annotations on the declaration of rv require the compiler to put it in r0, and the : "=r" (rv) annotation on the asm statement tells it that the assembly instruction writes to that variable. Then you can return rv as normal.
Also, notice how I removed the inline? That was intentional. If you inline this function, the arguments won't be neatly lined up on the stack where the system call expects to find them. If your operating system expects system call arguments in registers, you could annotate the assembly insert further and make an inline work, but if it expects them on the stack, leaving the function out-of-line is the best approach. (In production code I would put __attribute__((noinline)) on the function itself and __attribute__((used)) on each of its arguments, but that would be too much clutter for an example.)
The documentation for the extensions I'm using are in the "Using Assembly Language with C" section of the GCC manual. Read the entire thing very carefully.
If you are not using GCC, consult your compiler's manual to see if it has equivalent extensions. If it doesn't have equivalent extensions, your only option may be to write this function entirely in assembly language, in a separate .S file.