 The story so far, I am attempting to port physics to the ESP8266. So far I've got the kernel up and booting, I've got a file system working, I've got binaries that can be loaded and run code in. And I have been trying to make the system call handler work, so you can actually use system calls. So last time I spent a lot of time on a custom system call handler and exception handler, which is this code. And it nearly works. It will sometimes run system calls, sometimes it will just hang mysteriously. It doesn't seem to like it when I run kprintf, but it seems to hang before the kprintf actually executes, which is extremely odd. Since then I've done a bit of reading and I've discovered a few other things. One is that the user exception handler is actually also used for interrupts. So we are going to have to pay attention to the state of the interrupt flag. And also there is one additional register we need to save, which is the SAR special register, which is used to control the shifty unit. Now I've also found in the Arduino source code some prototypes for the exception handling framework that's already in the ROM. So given that this is more complicated than I thought, and the ROM already contains code for doing everything I want, and it's documented reasonably well in this header file, let's just abandon nearly all the stuff we did last time and start again using the ROM code, which is annoying, but these things happen. And if it works, then we will have smaller code. So the first thing to do is to simply dub out our old exception handler. And we configured it in boot.c, so we're not using any of this anymore. And so this is now running without the system call handler. What I expect is a hang as it hits the ROM's user exception vector and system call handler. All right, so now let us copy the relevant bits of these prototypes. So the way it works is, where is it? There is a function which sets an exception handler in C. There is a type definition, and that's not it. This is actually a reference to the system call handler table itself. The ROM actually has two, one for machine code handlers and one for C handlers, and we're using the C one. So this type represents an exception handler. What you do is you call set exception handler with the vector number, which in this case is going to be one for a system call, and a handler function pointer, and then it will call us back with the cause, again one, and exception stack frame, which is actually the same as our own exception handler was doing. And this is the machine code handler. I don't think we need any of the other things, but we will need this structure which defines stack frame, and I'm actually going to tweak it slightly, because this is using anonymous unions, indentation, like so. Now note that this does not save A1, which is the stack pointer. These exception handlers don't switch stacks, they run everything on the same stack where the exception or interrupt happened from. So for us that will either be the kernel stack or the user stack, depending where the interrupt happens. This means that for a user system call, we are going to have to have our own code to switch to the kernel stack. The other aspect is, which is actually a good thing, is that the large exception frame is being allocated on the user stack, unless, of course, an interrupt happens in the kernel stack. So we now want to register our handler, so that's xdos setExceptionHandler, one for a system call. Let me just find... Here is the actual implementation. So that's the vector number, 0 to 63. Yes, and here it is shifting it. And you see this doesn't actually call anything. All it does is write it to the cExceptionTable. Where's that list of addresses? This did describe the... So this is the machine code vector table, and this is the cVectorTable. So yes, this is a reference to the machine code table. This is the cTable, so we should see it writing. So this is multiplying the vector by 4. This is computing the address in the cTable. This is loading the old one. Why is it doing that? This is storing the new one. Okay, what this is doing is saying if the past in function pointer is 0, then set it to a10, which is this routine, which will be the default exception handler. So this will allow you to unset a vector by passing in null. Do you want to know what it's doing with a2? Yes, what this is about is returning a value a9, which is 0, otherwise it returns a2. I believe what this is doing is it's checking to see whether it actually sets something or not. Right, so if a vector has not been set, then the value read here from the table will be the default handler. So submovex is comparing it against a10, which is dbf8, which is the stub routine. What this will also do is write the cRapper to the machine code vector table. So that's an a8. We should see aS32 versus a8, which is here. This is computing the address in the machine code vector table, and then here we are writing the cRapper to that. The cRapper is because the machine code vector table is doing... Sorry, let me start that again. In order to hook a cRoutine to a vector, you need to do additional work if you're wrapping a machine code routine. So this is the wrapper, and what it's doing is it's reading lots of stuff and storing it on the stack frame. So this is all that code that I wrote yesterday, except hopefully correct, including the SAR register. So we want to set our syscallHandlerCb, and our syscallHandlerCb is one of these. So syscallHandlerCb, and all we're going to do is ETS puts the z and hang. So that's not working. UndefinedRef... Right, I didn't set the addresses of the ROM routines. So setExceptionHandler here. Okay, so with luck we should see a z. Yep, we've got a z. Now, can we do... Okay, printf without weird things happening. %lx cause... Actually, I took out the array, so let's just do PC equals 0x%lx that's the program counter called EPC. So that's defEPC, wrong button. We do need to include ROM.h. So let's see what this does. Okay, that worked. Exception1, program counter is a... Is that the right place? Kernel.ld... Yeah, user code. Yep, that's fine. That's all worked. However, well, we could just hook it up to a system call now, but we do want to be on the right stack. So in order to switch stacks, we do need a machine code routine. I am just wondering... We can have the cHandler point at our machine code routine that changes stacks and calls the system call handler. Oh yeah, that's the null thing. Corresponding excHandlerTable will be set to blah. So I don't know whether it's turned interrupts off. Not shall have to handler. cRapperHandler, which is right here. Here it's doing... Okay, it's setting the interrupt state to 1. So that's 32 plus 1. 32, as we found out last time, is user mode. Yes, yes. Yeah, there's actually a lot of these registers, some of which are owned by user code. So here is sa shift amount. I don't think we've got the loop stuff. I don't think we've got the boolean stuff. The point of the extensor architecture is a chip manufacturer can opt to have any of these additional extensions added on. Presumably depending how big their chip is going to be and how much they want to spend. PS, processor state. 1632 user mode. Why is it setting user mode? Presumably because it's C? It's decided that if it's going through a C vector handler then it must be user mode? It doesn't really... Well, all the ROM stuff is not going to be used by real operating systems. And in fact, this entire unit is not designed to run real operating systems. It's intended to run machine code, intended to run single embedded applications. We're doing something decidedly odd with it here. Okay, well, let's take a look at the machine code handler, which is called... This is the unhandled exception. I haven't spotted that. That might actually be original source code for the ROM, exeC wrapper handler. Nice open license. This looks familiar-ish. Well, so set handler is the routine that actually sets a exception handler, yeah. That'd be so much nicer if I'd seen this earlier. Anyway, the... So what's this then? Is that the default handler? User mode exception handler for the syscall course. Not used by default. So this is actually... This is exactly the code we want. Okay. Right, this is a default handler. This is... So this is some stuff for the loop add-on, which we don't have. This is the register window stuff that we don't have, and it doesn't implement anything else. So this is just a demonstration stub. So we do need... Well, we do need that line, but we are still going to need to do our stack switching stuff. What I was looking for is the default handler which is extosUnhandledException. This one. I'm looking for information about what the calling convention is, because it might be more useful to switch stacks here rather than in the cHandler. So this should be the top level handler. Yeah. Is it? This is another syscall handler. And there are a whole bunch of other routines dealing with interrupts. But I thought that the interrupts... Well, the reading I found said that the interrupts went through the exception handler. Okay, you know what? I'm going to do this... I don't want that one. I want this one. I think I'm just going to... This is returning from an exception. Where is it restoring the registers? So we're going to want to return the system call... Well, return values in registers. So if it's not going to update the registers from the stack frame, we can't use this. Okay, there's too many configuration options here. Let's take a look at what's actually in the cRapperHandler is here. So here we store the registers. Here we are calling the underlying exception handler. This is doing the return from the exception via this routine, which returned from the exception and restores all the registers. Good. Let's just trust that this is doing the same thing. And write a system call handler that switches stacks. And I... This should actually be easier to do. Now, we do know that we're only going to get system calls from user code. Therefore, we don't need to worry about the case when the interrupt or exception is being called from kernel code, which is nice because we can just simplify. We need somewhere to put the old stack pointer. Let's put this here, which is unused in the original stack frame. So that zero is the program counter. No, it's not. Yes, it is. A2 is loaded from offset zero is the program counter. So zero for eight, where is SAR loaded from? Don't see it. Right. It happens here. Yeah, at eight. And I do not see any 12. So we're safe to put the stack pointer there. Right. So A2 is already set up. So all we need to do is save stack pointer to A2 comma 12. Done. And on exit, we need to put it back the way it was. So load stack frame from A2 comma 12. Right. So that's set the stack. We now want to switch stacks. So that's a simple MOV I2 Udata plus U block size. So. And then call call zero Unix syscall. I think that's all. If this works, which it looks like it worked. Well, I would rather like the three hours of my life back that I wasted yesterday writing all this stuff. Oh, well, these things happen. So it's not actually wasted work. It has taught me how all this stuff actually works. Anyway, we've switched. We've switched stacks to the right place. So we go over to main. See, let's just copy our system call code. It's actually a little bit different than it is here. So the call number is in EFR2. EFR3. EFR4. EFR5. EFR6. In the system call. And on exit. EFR2 equals that. EFR3 equals that. Now, I don't know whether the interrupts are on or off. They should be on. But let's see what this does. Does not like it. A3. A. Computer says panic killed in it. We've got one system call. But it panicked there. Well, now we did find last time that there is actually some built-in debugging. So we can do, it's in process.c. Let's turn this on and see if that illuminates anything. And whoa. Pid one. Cisco 35 is doing signal. And it returns. Cisco, this one does exit. Right. So panic killed in it is a perfectly normal error telling us that init exited, which is usually not allowed. All right. Well, that seems to be working. I think there should be more twos being printed. But anyway, let's get rid of this. So I think what's happening is these values are not being restored correctly or else are not being processed correctly by the system call handler, which is in here, which is this routine. Yes, I got the wrong registers. That should be A2, A3. So A2 is the... No, it's not. A3 is the error. If it is zero, return A2. If it's not zero, store it in... Hang on, I didn't put store instruction in. Store it in erno. S32, I... Wait. That was actually what I wrote originally. I just forgot to put store in. Is that all that's needed? S32, I... A3. The storing A3, which is the return error value, into that address. Okay. So we now need to build the libraries and applications again. Got a make file in the history somewhere, which is here. I need to be in the library directory to make it work. Extra, comma, I've got to put the zero in. Okay. So we now want to rewrite the flash, so we use that burn command. And what happens? Write the flash. And it still doesn't work. So are these return values getting out of the system called properly? Is one question. Another one is something else could be going wrong. So let's actually take a look at... Let's take a look at this to see what it does. Where is it calling signal? Right. Main. It calls signal once. C int is... Okay. So this... Is it calling this? It returns zero, zero. It doesn't try to call that. So what does the library version of signal do? One of these routines will actually implement... No, signal goes straight to a system call. This is called signal.s. Okay. So it... Right. So it returns from the system call. It does this thing, but then for some reason, it's returning to the wrong place. Well, there is no code between here and here. We should be seeing two calls to the signal exception handler. The... Unless... This zero is spurious and not really an exit. Because we do know that from the documentation, syscall zero is used in certain circumstances by the extensor for windowing stuff. So... Because you see these parameters here do not look like parameters for exit. Okay. So let's deal with this by changing our system call ABI. Now we could change the numbers so that we don't use zero anymore. We could pass this in another parameter like A7. And then you always use system call one, meaning physics, kernel, system calls. Let's just change that. In our main, we want to do in here. My... Did not update the program counter. So let's try that. So rebuild all that stuff. Flash it. Interesting. Okay. See if I can... So what's it done? It's always... Oh dear. Dear, dear, dear. Let's try this again, shall we? It could have just been this, but I was kind of surprised... Oh, it's doing something. Right, it's trying to open a file and it's failing. Good, we've got somewhere. Okay, so... Actually, I will try this again. I just want to look for syscall zeroes. So I hit the reset button and try to catch the... No, too quick. Okay. Let's try not to drop the board on the floor. So here are the two calls. Signal, we delete a file, we close, stood in, stood there, stood out. We try to open stuff and we sit and spin because init is trying to open presumably dev files which don't exist. Okay, so... Right, the reason why... Yeah, the problem was this line. It was missing. The reason for it was... It was indeed not hitting the syscall instruction again. However, on exit, it had set A2, which is where the system call number is passed in, to zero, which meant that it instantly hit the syscall instruction again and tried to do system call zero, which is exit. So we don't need that. So we can change this. Okay, now... We now have nearly all the system call stuff working. We should be able to actually like run programs. We can't fork yet. We can't switch in and switch out. We can't swap stuff out. But init should run. We do need to put some more things in the file system, which we're going to do here. Now, I'm just wondering if there is a script that does it for us. I'd expect to see a call to mcnod somewhere. Here's some scripts. I think that might be obsolete. Here's some stuff. Kernel, dev... Let's look at this one. Standalone, file system source, file system node, engine... Where were those files? UCP script. Okay. And we're going to use a here document to inline our script. So hopefully this should work. That looks like that's worked. Okay, so we can now copy all this script stuff into it. So this is going to create the root file system with its directory structure. We don't actually need all the directories, but we do. Let's have the root file system directories, and we also need our device nodes. We don't need any of the HDs except A. We don't need any FDs. We do need these. Here are the binaries we can actually run from the util block. I'm not going to copy them in because they won't be run. So let's just go with that for the time being. I mean, it's not like we can run them or anything. So does it work? Macnod error 28. It's probably run out of space. Okay, we have a file system image. How big is it? 64K. All right. Our directory block should be somewhere. I think this is in it. That's a binary, so it's going to be in it. Here's that errors file that we copied. No, we didn't copy an errors file. No, these are strings belonging to init. Are they? Yeah, I'm not sure where they come from, but here are the directory nodes for dev. Okay, that's all there. Let's run it again and see what happens. So it's now writing twice as much flash data to the file system. Hmm. Not right. Very not right. Okay, I need to try and capture some of this. So let's get it in time. I think I did. Okay, so here we can see system calls as it's doing stuff. It's opening very... It's doing the stuff we were expecting before. It's trying to write something. Oh, it's written to stood out. It's written right. This is what it's doing wrong. It's writing to stood out. It's writing from here this many bytes, which is quite a lot of bytes. So here it has written the banner, which is, you know, we're expecting it to do that. But then it just goes and starts spewing garbage to stood out. So there's something wrong with our write system call. Let's take a look at it. Call write. Our write takes three parameters. We should arrive in A2, A3 and A4. So we put A4 into A5. A3 into A4. A2 into A3. And the system call number is 8, which goes into A2. The third parameter is ending up in the wrong place. Okay. Let's give this one a try. Again, I don't need to... I don't need to write the file system, so let's save a bit of time. Let's see what this does. Fantastic. So here is our banner, and it just failed to find the init tab, which tells it what files to... what processes to start. Awesome. We are making real progress. So we need an init tab. Let's put it in our script. Let's not delete that. Let's also... We know the system calls now seem to be working, so let's just turn that off. Where is our... We did have an update file system script in here, didn't we? So this is going to be in... Applications... Library... Standard... Where is that? EtcFiles... Standalone... FileSystemSource... EtcFiles... It works. Let's see what this does. At some point we're expecting it to try to fork, which point we then need to start working on the next bit. Interesting. That's because it's put it in... Dev. Let's put init in dev where the kernel can't find it. Right. Excellent. It has tried to fork in order to run the first binary, which is probably going to be a TTY, and there aren't any in here anyway. So... This is all... Excellent progress is hit here. Right, now what fork does, rather what do fork does, is it sets up to run a new process. In a single tasking environment like we have here, what it actually does is it swaps the current process out. It writes it to a new swap page. This then becomes the parent process. The new... The currently running process becomes the child. This means that the first... Normally the first thing that happens in the child is it will call exec and replace the current process. So we then end up with the child process running and the parent process descheduled, which is all exactly what we want. Now, I can find the MSP430 version of do fork, which is here. This actually does a certain amount of annoying stuff. The suspended parent is suspended with the kernel stack. So we have the process stack. Then in swap, we have the process data, including the stack, and the uData block, including the kernel stack. When we swap the process back in again, we'll start execution from that point in the kernel stack, so do fork will return. And we do this without needing machine code. That would be kind of nice. I think we... Yes, we do actually need to save our current registers. There is... So I could start work on this. However, there is an additional problem, which is currently we have nowhere to swap to. Our file system has a single partition on it. Rather, it doesn't have any partitions. We've got the file system occupying the entire disk. So we kind of want to create a disk image to flash to the thing, which has got a swap partition and the file system. At this point, let's actually check stuff in as we've reached a good point to do so. Do we have all our files? It looks like it. We do not have this. Do we want it? No, we're using the capital S version. Okay, that looks like all our files. So we actually probably want to overhaul the entire way we're doing stuff on Flash. I have thought that one thing I can do is import SPIFFS and put a big file on that. That will sort out all our FDL problems. SPIFFS is kind of big and a rather not. Our kernel, by the way, is 40k of code. The other thing we could do is to try and create a multi-partition file system, a multi-partition disk image. The tricky bit there is while creating a multi-partition file system image is easy. We can use the FDisk command on a disk. That works fine. So you see we have here a disk in file system, let me edge containing 64k and 128 sectors. Also, I now realize that I changed the file system size without also changing the configuration here. So try to access anything in the top half of the disk won't work. However, the UCP command, as far as I know, does not understand partitions. So we would need a way to do that. I'm pretty sure that Linux does have a way to allow you to use partitions in a file system image, but I don't know what it is. So I may actually take a break and go and look this up. Okay, I figured out how to make this work, which is pretty straightforward, although it has made the fast system creation script rather more complex. We call LO setup to bind a loop device to our file. This then causes the kernel to parse it for partitions. I set up one megabyte disk image with two partitions. So here we create the file system on loop 0p2, meaning partition two of the file system, etc. Our file is now a megabyte, so I will start that flashing. That's actually found the wrong serial port. Let me read the GTI USB 0. Okay, that will take a while. I've changed the configuration to boot from HDA2 and the swap isn't HDA1. I think we may need to. Now I think about it. There's a flash. I think we... Okay, we're already calling scan, so it knows about partitions on the device. I think it knows about partitions. So we should be good to go. I also changed the number of processors down from 16 to 4, because each one uses 96k, so that was actually beginning to use quite a lot of space. Even four processors uses 384k of swap space. So 16 is using about a megabyte and a half, and I'm not going to wait that long for a flash. So we should be able to finish this and then run it again, and then we can take a look at our fork, which is actually pretty simple. I hope. Let's actually just look at the MSP430. Okay, fork. What this does is swap stuff out. We need to access. We need to save stuff in the stack. We need to save the childpid. Oh yeah, because of course fork returns the childpid, so we need that. What is this doing? I've also forgotten how MSP430 machine code works. Well, that's done, so we don't want to rewrite our flash, because it takes forever. So let's try this and see whether it actually does what we want. We don't want to call update flash anymore. Okay, let's see what happens. Right, so scanning flash. It's found 108k of flash. That doesn't sound like the right number, honestly. It parsed HDA and has found two partitions. It has managed to mount the root file system at partition two. That's actually the wrong number is printed there. But it has successfully mounted the file system and has failed to fork, so we know that that's all working. So what this is doing is it's saving the registers that C expects to have saved onto the stack. This I believe in the MSP430 is the return parameter. So it's saving this here so that when we restore ourselves after things get switched in, we're actually returning the right value. We then swap out the current process. That has set up the parent. Okay, we call new proc which creating a new process using the current code. We don't care about the state that we saved. And then we just return, essentially. All right, well, I am on entry R12 as the process pointer. So that's actually going to be in A2. We will need this value. So we're going to have to put it into kernel def file, which we cleaned out yesterday because it was all wrong. So let's look at the source of truth, ppid offset. Offset ptab. Okay, so this is going to be ptab zero status flags, tty, pid, uid, ppointer, alarm, exitval, wait page, page two. We're not using udata, priority, pc, wait node, timeout, name. Yikes, there's lots of this. You know what? I'm not going to set them all. I'm just going to set the one we want. Pid offset is zero, one, two, three. That can't be three. Three's not lined. Zero, one, two, three. Okay, it will have inserted padding. So it's actually going to be at four. This could be reordered. What else do we need? We need the stack pointer in the udata block. Define udata, usp is, so, zero, four, six, eight. How big is a bool? I think a bool is an int on this. Twelve, oh dear, it's more padding. So 13, a uadra is an int, so that's going to be 16. 20, 24, 28. We need to check these things. This one is six. So if udata, usp is not equal to offset of, now we can't do this here because we don't have the kernel configured. We're going to have to do it in here. Checking this stuff at runtime is bad, but not as bad as it could be as the compiler will optimize all these conditions away if they're correct. C++ has a static assert statement which is really useful for doing that sort of thing because that's guaranteed to not produce any code, and it'll produce compile time errors. So cpu, nx106, cpu.a, no, we don't, we want rules. So I want to make it find this header file, so that needs to be root dear. Okay, now let's just put dofoc back again and see if that produces anything useful. Hopefully nothing. Okay, that's wrong. This will tell us what the right value should be. 24. Yeah, I think that bool wasn't as big as I thought it was. That will be a dependency issue. We want all our objects also depend on... Okay, so that rebuilt everything. Good. So we now know that that constant is set to the right value. So let me just check that it's generating no code by looking at its calling... I'll be looking for main, so here. And our entire conditional has vanished. That's good. Okay, let's do the same thing for the other one. p, tab, ppid, offset. That's not equal to the offset of p, tab, ppid. In extra, this is there. And we should actually now be able to write some code. Okay, that's worked. So let's get rid of our string again. And what we need to do is push the registers onto the stack in the order in which switch in is going to save them. Switch in and switch out are the routines that switch from one process to another. Do fork is subtly different, because it switches from the current process to the current process while saving a state that looks like switch out onto the stack. The state, we can tell, is going to be these values. So this is going to be the non... the caller saved... the callee saved registers. So this is the windowed API. Right, here we go. So this is A12 to A15. We're going to save A12 onto something. A13, A14, A15. We also need to save SAR. So read special register SAR A2. That's going to be onto something. We need to save the current stack pointer. Why do we need to save the current stack? We save the stack pointer into the UData block. So this is going to be UData plus UData USP. S32 by SP goes into there. Actually, we're referring to UData again. So let's put UData in A2. And we can do this. So not times four. One times four. Two times four. Three times four. Four times four. Therefore, we need that many bytes of stack frame. Do we need to save the status register? We're always going from system call to system call because we're not doing any preemption, so no. Okay. This does not mention SAR as part of the ABI. Hmm. Save. Save the same order. That switch in will do a switch out. We'll do it. So switch in will restore it correctly. A2 is the tab of the current process. So we can't put this in A2. Let's just use A3 for all this. Okay. Save the current process to disk. So this is pushing the current process onto the stack. Why? What does switch in do? Odd. So somewhere here there should be code that restores the... Here we go. This is the code that actually restores the process. So we can see it loads stack pointer. Reset the run count. This is for preemption. We don't think we care about. Status register and restore all the registers. So it's doing this in reverse. So... Ah. I haven't scrolled down far enough. This is saving the... This is saving the p-tab pointer because we're about to call swap out which can corrupt it. Yeah. Because the MSP430R12 is a call saved register. So we actually want to save A2 into a slot here. We want to pass in... Right. This is actually the... This is actually a global variable. It's not an offset. Yeah. So that actually wants to be offset. Which means we need this to be offset. And we want this to be offset. So what we need to do here is we want to load the process... The p-tab pointer from user data. Now user data is an A3. So we can just do... LZTUI... Udata... U... p-tab offset. Okay. So call swap out. And then load A2 back again off the stack frame. So A2 is now pointing at the current processor's p-tab. Right. This we haven't done yet. Hang on. Hang on. I'm doing this wrong. Right. This is saving the caller save registers. Because we know that swap out is going to save registers A12 up. This is needing to save the registers that aren't normally saved. Oh dear. Okay. So this is going to want to save A0. A1 is the stack pointer. So we're not saving that. A2... A3... A4... A5... A6... A7... A8... A9... A10... A11... A5... A6... A7... A8... A9... A10... Save... SA. Do we need to save SA? I'm not sure we do. I am slightly surprised not to see much in the way of references here to whether SA needs saving. I think that you generally assume that SA is not preserved across a call. So it doesn't really matter what it's going to be on exit. Do I actually have this right? On return from our system call, all the user registers will be returned to what they were before except for our two return registers. So we only care about calling stuff from the kernel. And this is a normal function. So it corresponds to the normal ABI. Let's take a look at another one. Let's take a look at the 68... Tirey... ST... Trix.S. That's a generic one. So platforms which I'll call the current process... Save A0... A2... A4... A6... A7... I don't think we need to save any registers. So let's say I am a kernel function. I have some state in registers. I've got some state in the low registers which are call... Er saved. And some state in the high registers which are call e saved. I call a function. I expect the call e saved registers to have their values preserved. So... I call do fork. Do fork does its stuff. Suspends the process because I'm the parent. The parent gets rescheduled and this returns via the switch in process. I expect to see the high registers with the values as they were. So I was right the first time I need to save the call e registers. And... Okay. So the old PID needs to go into the... The child PID needs to be returned from the parent as the return value. So do fork needs to... Let me just see how do fork is actually... So... Yeah. So the parent has to return the child process. But when we switch back in then... Where does the return value come from? Well on the MSP430 it's in R12. Let's look at another platform. What have we got? Let's try... Let's try a Z80 platform. Okay so this is simpler. So switch in... Here we go. Switch in is doing the work. So here is do fork. Prepare the return value in the parent process. So HL is actually the PID of the child. That's going into HL. When we get switched back in these parameters get restored. So switch in... What's the... Switch in never returns. The process gets replaced with the one that gets loaded from swap. That includes all of its stack frames. So switch in only ever happens from switch out. Which will preserve the call you saved registers. But won't preserve the caller saved registers. Because it's just going to return from this void function. So platform switch out gets called. That switches out the process. And the process will later get switched back in again. This will return. So the only case when a return value is coming out of switch out is when we've set up a new parent process. So we need to load that PID which is this. And put it onto the stack frame so that switch in can place it in R2. So switch out will store a zero. Well it doesn't matter what switch out stores here. Because it'll be garbage on the way back in. I hope that is right. So A2 is the P tab of the current process. So we want to load this value. And we're going to store it at slot 5 times 4. So this is going to be 6 and 6. So we wish to call new proc to find a new child to create a new child process. I will admit this does all seem kind of backwards. The child PID does new proc assign the PID or has it been figured out ahead of time? This looks like it's preserving R12 which is the first parameter. So that must be in order to put it into new proc. So let's assume that was the reason. So we were going to want to return zero. So let's put i into zero. We need to save the value. We need to save i into, we need to save zero into run ticks. We need to restore our stack frame and return. Okay let's find out what new proc does. Not much apparently. Underscore new proc. Is it a thing I need to define myself? Is it in a no? Okay I'm a bit confused. Does it build? No it doesn't. I want to change this to that invalid symbolic operand. We didn't add that to our kernel file. So it is at stores stack pointer when the process is switched. So that will be zero, four, six, eight, twelve, sixteen, twenty, twenty four, twenty eight. Also I'm looking for the p-tab field, not the sp field, which is there. Oh it's at the top. Good that's going to be a zero. Udata UpTab set nine forty seven. That's in fact just a typo. It's embarrassing. All right. Statement with no effect. We've got a... Undefined reference to new proc. I reckon that new proc has gone. And the reason why I can't find it is that it's no longer in the source code. So something else will be doing it instead. It will be bit rot. Let's take a look at this make proc. And it appears to be pushing two values, which is the proc fixes up the table for a child of a fork, but also for init. Sets it to for runnings. It must be called immediately. We need to pass in. I think this is a process pointer. Yes it is. And also the Udata. So this is... So A2 is already the process pointer, the ptab pointer from here. So we need A3 to be the Udata. Well that's easily done. This is pushing stuff for later. I don't think we care about any of that. Let's see what happens. Hopefully a failure due to swap out. Yay! We have created a child process. Okay, well that is a ton of progress. The next bit is the swapper, which is pretty simple. I mean it's just this. We're going to copy some of this code. This is the original swap code, which is very straightforward. It writes the Udata block, and then it writes the data. But we're going to have to change it to write both data and code. But I'm going to call it now because I'm running out of time. So that's really good. I'm pleased to see that. Notice that we don't have any interrupt handlers yet. We don't have a timer or anything. The system will actually run reasonably well without one. Yeah. Okay. I hope you enjoyed this video. Please let me know what you think in the comments.