 The story so far, I am trying to port Fusix to the ESP8266. In previous videos, I have the kernel up and running. I have a user land building, mostly. It's probably got lots of bugs in it. I have the user side of the system call interface. I have a file system on the internal flash, although it's pretty noxious and needs to be replaced entirely. So today, what I'm going to do is to try actually running programs. Now, some stuff that's happened in previous videos actually needs fixing. So the first thing is to... I do need to change something in the... Where is it? I need to change one of the linker scripts flags because something came up, which is... Text start here, the beginning of the text area, does actually include the header. So I need to do it like that. And I just need to clean and rebuild. Okay, we have a new and improved libc. Go into here, clean and rebuild the applications. Okay, so that bit's done. Right, so the first thing is to actually hit the reset button. So it scans the flash. Okay, I want to dismiss it. This tells us where it is trying to load... Where it is trying to find the init binary. So we need to add that to our file system image, which is currently completely empty, and then flash that. That will let us actually get started with the kernel stuff. So I believe then there should be a UCP is the program. It allows you to interact with physics file system images. So if we do this, we can see what's inside it. And the answer is nothing. So we should be able to do... What are the commands? Right, put and get. So we should be able... Yeah, okay, I'm not quite sure which way around those are. I believe put a file into the image. Util init, our system.image, put, be put. What about just put? What has that done? Nothing. Okay, let's go take a look at the actual source code for this, shall we? Standalone UCP.C. Because I think the command line options are wrong. Put, be put. Should be one argument. Okay, that should actually be right. Unless it's the other way around. Let's try get... No. Okay, let's actually read the documentation. Be get, copy file... Okay, it is the opposite direction than the one I thought. So get copies into the file system. So this should be the command we want. Why isn't that doing what I want? Be for byte swapping, which we're not using. There's nothing actually interesting in there. I used DOS a long time ago, but not very much. And ever since then, okay, somehow my muscle memory has determined this to be copied this file into the current directory. Because that's how it worked in DOS. I have never been able to get rid of that. That does not look particularly exciting. Okay, so it's never getting as far as parsing the command line. So... Okay, that is the file name itself. Five command line parameters, which is one, two, three, four, five. Optin is the argument currently being looked at. argval is... Yeah, it is argv. But it only wants two parameters. I know what's going on. I know what's going on. Yeah, the third... The entire command line goes into a single parameter, which is a bold design choice. Anyway, we now have our file system with our init thing in it, which we need to make executable 6.6. No, it's not. I can't remember my octal. Not quite. So four does not look anything like write. Okay, this does say that mode is a octal number. 755 should be... Yeah, okay. I don't know. All right. We need to convert it to FDL, and we need to write it with that. Okay, so that will take a while. All right. The other thing we need to do is we have a number of dummied out routines which need to be sorted. So let's just do this. I think string will work. MoviR2 and jump to panic. What's that going to do? A2, A2. First in... Okay, and we're going to duplicate this for the others. The reason for this is when we hit an unimplemented routine, we do not want it to just do nothing in return because that will almost certainly be wrong. Well, it will be wrong, but worse, it will fail in a really difficult to debug way. So what we actually want to do is have it halt cleanly with an error, which is what we're doing here. Okay, the flashing has finished. We don't need to do it for platform idle and we don't need to do it for program vectors because those are both no ops for this platform. Okay, and we also need to do this for low level... The next one is six. No, we don't because there's nothing in there. User mem? No, we're using the C version of user mem. Okay, we're good. All right, so let's see what happens. Hopefully exactly the same thing as before. So we slowly scan the flash. All right, we successfully mounted the file system. We call starting init and we halt. This is because we need to implement this routine. But before we do that, there's actually something else we need to do, which is currently our kernel is configured to use the simple swap module. This file handles swapping in and out processors. So when you swap out a process, it allocates a block in swap, calculates the address to it, writes the user data, writes the Udata block, writes the actual code block and returns, and swap in does the reverse. But we can't use this because this file assumes that we have a single memory area that everything lives in, which is not the case for the ESP8266 because we have separate code and data memories. So I'm going to copy that file. It should be in here. And instead of linking swap.o... Wait a minute. Instead of linking simple.o, we link swap.c. So what is swap.c? Okay, this is the routine that handles the swap map, figuring out where things are going to go, et cetera, et cetera. And it's also got helper routines for copying data in and out of swap. So our code is actually going to be very similar to this. So I'm not going to remove any of the existing code. I'm just going to dummy out these. I don't need that one. I do need this one. So that, again, it will cleanly halt when we... If this kernel actually tries to call that. And the other thing we need to do is these definitions here are used by the swapper stuff and the loader, both of which are custom and user mem, apparently. We're using user mem. I think we are... Okay, this is assuming that uData is down the bottom of memory, I believe. Are we actually using that? I think we are. I have a feeling that we might need our own one of these as well. Annoyingly, we just need to replace valadr. What is config.mmu? Not much, but we can stop it from implementing this valadr by setting it. So let's do that. Config.mmu. Okay, so that should be the only thing that uses these. So we are actually going to create our own variables, which is database, pun not intended, code base. What did we call this? We want to avoid at all costs defining addresses in the code. I think we have in one place... Oh dear, swap.c. Oh wait, that's our swap.c. Yeah, let's not call that swap.c because there's already one in the code base. So let's call that... Well, swap.c is kind of the right file. Let's just call it swapper, just if it's different. That becomes swapper. No target to make swap.c. I think we need a clean. Okay, right, this is config.h. So this is going to be database, code base. Let's clean up these a bit. So these symbols are defined to addresses and integers. However, the way C works is a symbol that represents a variable is the address where the value is stored so it can change. So a symbol like code len set to a simple integer cannot be accessed from C. So we actually have to do this, which is kind of annoying, particularly as the compiler is not necessarily capable of optimizing this kind of expression. But we're going to go with it for now. So we don't have these anymore. Right, this is... That's correct, actually. Max swaps is actually 16. Those are correct. This is now irrelevant. Okay, so now we have a compilation failure in this code. So in fact, I will disable these and we'll come back to it when we need them. simple.c Why are we still linking against simple? Oh, we're compiling it, but we're not using it because the kernel make file, not this one, the one at the level above, compiles all the source files even if they're not being used. Are we going to have to rename this? I think we might. Yeah, actually, what's it actually complaining about? I wonder, can we... Yeah, we can't get rid of simple.c. No, we can. We can turn off config swap only. Where is this used? simple.c Cool. Right, and main.c is ours. So where is ramtop used? That doesn't ring a bell. Apparently a number of places. I think top is... What is ramtop? Okay, it's used by the system call handler. Sorry, it's used by exec and it's red. And top is ignored. I think ramtop is the top address of data memory. Must be in common for single process swapping cases. I think it's the top address of available memory. But why is it a variable? I don't see anyone actually setting it, apart from platform mains. Okay, I don't think we need that. Instead, I will do... So that's to find it as a constant, which means it doesn't use any variable storage anymore. We can't do that. Okay, I'll just stay with it. And this is going to be... So this can then just be initialized as data top. Right, what's this complaining about? Flat mem unused variables. Fixing values for types. Flat mem is not one of mine. That's odd. How did that ever work? Okay, what's... Right, I know what is happening, which is that one of the config changes I did is causing conditional compilation to pull in a version of these files with different prototypes, cursors. Okay, user mem.c And that's failed to compile. No rule to make targets. Swap, swapper. And now we're hitting compilation problems with this. Good. So page map realloc. This is trying to allocate memory for a process. Because we have one process at a time, this is essentially a no op. So data. This is patching the executable header. These are byte fields in numbers of pages, which is what, number of 256 byte pages, which is why there's that shift. These are not relevant for us. Yeah, not relevant for us. Because we're special. Page map mem used in kilobytes. Where's that used? I have a feeling this is diagnostics only. Yes. So again, because we have two memory banks. And this is a constant value. So this will be data len plus code len. Okay, and we need to define val adder, which can go here. So val adder, no, that's in user mem. If the address is below base, yeah, that's normal. If it is, what's this doing? You give it an address and a size, and it returns a flag. So if the size is negative, or if it wraps, if it is less, right? Udata lives out of the user memory area. So we don't want that condition. Utop is the top of the allocated area. Okay, that'll do. That's not progbase. That is database. And more references to page map in it. But page map in it is here. No, page map base. Page map base is... Page map base is a function you only get with 32-bit kernels. Oh, right. Now this has failed to show up, because we haven't defined progload. No, we have defined progload, so we don't get this anymore. So we need to define page map base to just return database. What this is for is that on flat memory systems where programs can be loaded anywhere in memory, this returns somewhere to load the program. All right. That builds. This is not going to do anything exciting. I hope it's just going to go straight to write. Now we can start on our system call on this thing to actually execute binaries. Now we're going to want all of this. So this is code to load a physics small binary into a 16-bit address space. We're using the same binary format. We just need to fiddle with certain things to compensate for our weird memory format. So what it does is open the file, check that it's executable, update the access time. This then reads the binary header into this header structure at which point we then look at it and set things up. Page map prepare was that function we define which patches up the header. If the binary doesn't specify how much memory it wants, then page map prepare will do this. Let's get rid of that and get rid of that. And here we are just going to... We're just going to dump the contents of the header just to make sure that it's actually like worked. And we've successfully read the header. This is the old binaries format. This is the new binary format. So a magic is header.a magic, a CPU is header.a CPU, CPU feet, address, 8-bit address of the load position. That's the thing that page map will have fixed up, which for us is unused, it hints for us is unused. We then get the size of the text segment, data segment, bss segment, bss segment, the entry point offset in bytes. So as I said yesterday, the entry point has to be within 2-6 bytes at the start of the binary. Amount of memory required in pages. Amount of stack required in pages. The amount of zero page required in pages. And stop. Right, and this is failing because firstly we need to define printf to h. And let's actually just patch out this stuff here to here. And let's go with that. Hopefully this should print the values from the header of init. Look good to me. So here you can see the file system is reading sectors from the flash. Logical block, sector within the block from 0-6, physical block where that thing is actually positioned. We won't see the physical block differ from the logical block until we actually start writing to the file system. That FDL layer is a horrible bodge and rather inefficient. I've been thinking about ways around it. And honestly, where levelling is hard and I think the simplest thing to do is to link in a where levelling file system and put our physics partition in a file on that file system. There aren't any that I can find off the shelf FDL libraries. And most people are focusing on file systems because in order to make efficient use of flash you need to know which bits of the flash have data in them. So, yeah. Anyway, that's actually worked. So what do we do here? Prog load. Let's actually get rid of some of these variables because we can do better than this. We should do it. So what this is trying to do is calculate the various addresses where things are. Let's say data loads at always. Now we do kind of want to honour the data size if there is one. So if the size parameter is actually set then let's override data top by saying data load plus a size like this. We're not setting a size so we'll always, data top will always be the top of data. I should add that this is the logic that was in page map init. This is, what is this trying to do? This is sizing the binary and trying to calculate how big it is. So, yeah, that's actually correct. Does it fit? I think I can use that logic. What is prog pointer? I don't know. It's used for, it's temporary. It's only used for that one thing. So this is actually top minus prog load. Okay. So prog pointer is being defined as the, this is the size of the text and data. This is the BSS size. Therefore, prog pointer is one kilobyte past the top address that the binary is using in memory. This is padding for stuff like a stack. What this is trying, what this is testing for is whether the whole binary is going to fit in memory. So, we've already got a data top. So, call that load top. And I am, of course, forgetting that we have a split system. Therefore, bin size is not how things work. So we're going to have to rewrite this logic from scratch. If this is testing for overflow, that is when you add a data to a text, has it wrapped around the top of the 16-bit value? Irrelevant for us because we're using 16-bit ints. Where is top defined? Just there. Yeah, we don't want this. So, if the load address is greater than the available memory, always true because these are constants. If total binary size plus BSS, okay, this is another overflow check. This entire condition can go. If the binary is too small, which means if it doesn't have the appropriate header, just give up. Although we know that it's got the appropriate header because we've just read it in and checked the magic number. Yeah, let's ignore that one as well. If data is less than prod load, what is data? All right, got halfway through, right, changing that to something else. Okay, if... Right, because the original prod load variable was taken from the base address from the header, which we are not using anymore. So these two conditions we're dispensing with, we only have these two now, which is load top, as I say, is basically the first address that's not loaded as part of the binary plus a bit. These are all based on this bin size. So okay, we need to check the data. So let's do data size is a data plus a BSS. Code size is a text. So if code size is bigger than code length, then the code won't fit. Or if data size is greater than data length, then the data won't fit. Okay. Right, we set up the process status to no sleep. I don't know what no sleep is, but I'm going to assume that's correct. We allocate a couple of buffers. Buffers are 512 byte blocks owned by the kernel, which are used for their file buffers. They're used for all forms of IO. This is using them as a cheap way to allocate some temporary memory. They're freed further down. rargs collects the argv and nvp arrays into the buffers. Now these macros are defined so that our name argv and nvp refer to the system call parameters. The system call code will marshal them into these uData variables. So that is cross-platform. PageMapRealloc will then change the size of the current processors page, which is a no-op for our platform. We're going to keep that because, of course, exec replaces the current process with a new one, top. We don't have top anymore. So this needs to be data top, I believe. Top equals run top. Yep, that needs to be data top. This we keep. This is a thing I don't know about, to be honest. SysStubs, I believe that this is... Okay, this is how these platforms call... This is how these platforms make system calls. This is owned by the kernel. This routine copies this block of memory over the top of the file header into program space so that the binary can then make a system call by jumping to the first address of its binary. Our platform has the system call instruction. Mark the inode in some way that I don't care about. And here we actually read in the program. Now this comment surprises me because I don't think we have read the first block. We have, however, read the file header and then up there when we touched the... when we copied the SysStubs, we then overwrote the file header so this is preventing it from being overwritten again. So we don't need that, actually. Okay, so the first thing we need to do is to read in the... Sorry, clarification before. The file header is never actually read into process memory. It's always read into that kernel structure on the stack. So copying the SysStubs does not overwrite the file header. What it does is overwrite the old SysStubs from the previous process. Anyway, so what we need to do now is to read in the program code into instruction memory. So, and that comment is, I believe, still wrong. So code, code base. So this is the number of bytes which is the size of the text segment, which is therefore header.aText. And we don't need that variable. This is actually... We do want data size, but just for that. Sysio equals false means it's reading into user memory rather than kernel memory for our platform. This doesn't matter. Perform the read from the file. If the read failed, give up. Okay, now I'm pretty sure this is not going to work. We'll have to deal with that later. That has read the program code. We now need to read the data. With the old code, the data was read as part of this. So this now needs to become database, data, false. So we read in the program code at code base. We read in the data at database. We need to wipe the memory for BSS. So prog pointer here is going to be database plus header.aData because the BSS is after the data. And this is therefore going to be BSS. Okay, set the initial break. This is the top of used memory for the process. You use the Brooke or Sbrook system calls to adjust this. So that is going to be database plus aData plus aBSS. Turn off court signals. Basically, arguments are the stack on top of user space memory. Okay, now this is a little bit intriguing because of the way the stack works on the ESP8266. The stack pointer always has to be 16-byte aligned. So normally you would just push all the data onto the stack, then push argv, argp, then you pop argv and argp in what annual stack pointer is now pointing at the environment pointer. Actually, I think we can't do that. But anyway, let's just put in this. Okay, that has not built. I need to get rid of that. That's a kind of a mess. That's a lot of a mess. That's our first error. And the indentation is 3. Yeah, I keep doing that. And that's why we're getting so many nasty errors. That's much better. So 118. This is supposed to be reallocating the current process to the size required. I think we just want this. I think we want this. The page map realloc is livesandswapper.c here and there's no op. I'm not convinced about that size, to be honest. You know what? Let's just lose that. 178. Interesting. So top is data top. Yeah, that's the top of used memory, which for us with our binaries, they're not setting the size hint. Therefore, we're always getting the full 64k. So this is actually placing right. What this is doing is writing stuff onto the stack. So pointer is the top address of where we want the data block to start. So this will start at this address and keep working down. I'm not sure about that minus 2, or what it does. We have previously put the argv pointer and the end pointer into aBuff and eBuff. So I think this is just that. This copies the first argument into the processor's uData's name field. Okay, so this is going to need a fair bit of modification, I believe. So let's just turn all this stuff off for now. Okay, and let's run it and see what happens. I think it's going to crash. Yep, so let's hit the reset button and see if I can catch this. This is annoying. Did I pause the serial terminal? It's two key presses, which is annoying. Okay, so starting in it, we read bits of the directory and the binary and now we want to see what's at this address. I've got a feeling I know what it is. Yep, right. What is happening here is that it's trying to memcopy data into instruction memory. So yes, here is the address that caused the exception and the problem is that the built-in memcopy for physics does 8-bit accesses. You see that 8SI there? S8I and instruction memory doesn't support 8-bit accesses. So now where would this actually be happening? Well, obviously not there. Probably here, so let's just put in some trace lines. I am a strong believer in C99. Normally you can't do this in physics because it requires a bunch of really old and creakingly terrible compilers, but we are using actual modern GCC, so we can. And let's see what it does. So we got to line 144. Right, as I thought, it's trying to read code into instruction memory. Now, I suppose there's two ways we can do this. We can either read it in one buffer at a time and then use a custom copy routine to move it into instruction memory. Or we can try and change memset so that it recognises instruction memory and copies there. Or we could change memset so it calls the kernel version. So not the kernel version, the ROM version. But actually I think that's not a good idea because the ROM version is not aware of flash. And also I'm looking for memcopy. Yeah. See what this is doing? A5 is the count. This is setting flags. This is auring A2 with itself in order to set flags and put the result in A5. Oh, this is a register move. It's copying A2 to A5. This is what MOV A5, A2 turns into. Interesting, there's no .in version. We then look to see whether either of the bottom two bits are set. If the bottom bit is set, then we jump off to an 8-bit version. If the bottom bit is clear but the next bit is set, we jump off to a DF2C 16-bit version which actually looks very much like the 8-bit version. Otherwise, we do this which is our optimised 32-bit version. So provided we're doing aligned accesses, we could use the ROM memcopy. Where is memcopy? Where's that disassembly? It doesn't give me any line numbers. But I do know it's not the ROM version. That's very interesting. 402191DE. It's... Oh, I own that. Okay. Let's get rid of that then. Yes, I actually remember writing this. Okay, let's just stub this out because we want to see if it works. I think we'll still need Stirlen. We certainly won't need Memset. We might need to... steal... We might need to provide our own Stirlcopy. Yeah, if this is used to copy strings out of Flash, it's not going to work. Oh, wait a minute, wait a minute. I'm being an idiot. Right, I can do this. Also, I think I've figured out what this OR is doing and I'm not right about... You see, if it's wanting to detect aligned accesses, then it will need to... all the input and output parameters together. 3, 4... No, let's just testing the destination. Weird, okay. Anyway... Right, if the source, the destination and the size are aligned, which we can make sure that they are, then we do the 32-bit version, which is... Also, there was a bug in this, it was returning the wrong value. Okay. The advantage of this is that it will get... We can copy stuff to Instruction Memory and we should have... We should be free to... What am I doing? We should be safe to read out of the Flash. So what's this going to do? Probably crash somewhere else. So here you can see it reading the init program into memory. And I bet that... Yeah. So I think what's happening is it's reached the last block, which does not have an aligned size, because it's allowed not to, and therefore we're going through the slow path, which with the 8-bit accesses. And I think the way we do this is we make sure that the text block always has an aligned size, which is easily done. Though I am actually a little bit surprised. See, I thought the literal blocks here would have to be... Oh no, because the literal blocks appear before the code. And I can't remember how to align things. Dot equals align. Unfortunately, this means that... This means that I can easily work around the whole problem by aligning this. We don't care if we write a few more bytes. Because there is nothing after the code. We're not using the instruction memory for anything else. So I fixed the linker map so that any future binaries will be aligned, but cool, I think. No, no I cannot do this. Because this will read the wrong number of bytes from the file, which means the data will not be lined up correctly. Right, I am going to have to fix the binaries. Alright, so that was a linker change. I don't need to rebuild the libc, and I do need to rebuild all the binaries. And I need to copy the init again, but luckily it's kept the file arguments correct. So I need to convert the ftl and write the flash. Okay, so while that's going, let's have a look at some of this. rargs and wargs copy a argument list, that is nfp or argv, from user memory to kernel memory and back again. So we copy the arguments into some temporary buffers and in rargs and then write them back in wargs. Now, this was written for 16-bit machines where pointers are 16-bit wide, so just need to check to make sure there aren't any assumptions. That looks okay, actually. Yeah, I think that'll work. I do not like assignments in expressions. Cal goal, my pet programming language, I deliberately designed not to have these assignments or statements. That looks okay, actually. Anyway, that's now flashed, so hit the reset button and let's see what happens. Okay, good. So we got to line 159 which is here. This means that we've successfully read the program code and the data, which is these three lines here. And we've given up at line 159. No, we haven't read all the data. Interesting. We never got to this line. Let me just double check that this is annoying. So it's read 3a4 bytes, but the header block says 3b4 bytes. Data n minus data start. I mean, that looks kind of right. So we could be, I know what's happened. I know what's happened and it's really stupid. We have in fact already read the file header, which is 10 hex bytes long. So the file pointer is in the wrong place. Let's do this and then we wait for the flash. It hasn't gone that far. It must have failed here. That's because this is also wrong. I mean, we could just seek back to the beginning of the file and read it again, but I don't know where the count, okay, that's much better. Panic loaded, we've got to here. Good. I don't know where the count is modified after a read. Done is incremented. Let's actually check that. I think count will have the number of bytes left. So count will end up being zero on a valid right. By the way, the reason why these fields start out with a prefix is historical reasons based from a zero. Historical reasons from a very old C dialect where structure members were in the global namespace. So you couldn't have a member in two different structures with the same name. Some of this code is quite old. All right. So we are hopefully correctly loading the binaries into memory. So next, let's try the arguments. So this will write the argument, the argv array first, that is at the top of memory. Then below it is the environment pointer. Then we copy right. So what this is doing is it's fetching the first argv array value. So firstly, this is a pointer, so it should be one of these. But that should work. That should be correct. So let's do this argc undeclared. argc is not appearing in this. Oh yeah. argc is written by w args. Okay. Stop this out. Scan flash. Okay. So the argv array appears at 7ffo. The kernel printf has... It only seems to assume that it's a 16-bit wide. I wonder if I can put an L on that. The environment pointer is below it. This does seem to align the arrays, which is a little bit awkward due to the alignment issue I mentioned. Okay, I think we're going to have to solve this in another way. Right, now we need to... Yes, we are. So we need to push two parameters onto the stack, which is the argv pointer and argc. If we do that, this will go from C to 4, and then our stack pointer will not be aligned. So what we are going to have to do is... Well, for a start, these are pointers. These are 32-bit values. So we're actually going to have to push three values, which is nvp, argv, and argc. No, hang on. We're going to have to... Right, we're going to have to allocate space on the stack for our three 32-bit words, and then align it down to 16 bits. I saw a line up. Is there an align down? Yes, there is. Good. Okay, so this is going to align down by 16. So we allocate three words. We're talking about words because this is a pointer variable, and then align it down to 16-bit values to a 16-aligned block. Then we're going to put our three parameters in this order. Now the new stack pointer then becomes... Yes, we can actually do it like this. Like this. No, need to do that. Set the status to running, and then doexec is the routine which actually causes the process to actually run, which we do not have to find anywhere. Yep, it's a low-level thing. Okay, so low-level, lx106, and doexec-move-a-do, doexec-base-jump-panic. Okay, and let's run it and see what happens. Scanning flash, right. Okay, so our new stack pointer is correctly set to a 16-bit aligned address. Now, there is a bit we're going to have to do. We're going to have to fix this. So l32iA4sp8, because now nfp is a pointer on the stack rather than a location on the stack, so this is actually now pointing at the right place. Okay, so now we go back to our library, rebuild it. Actually, we didn't need to rebuild everything. Never mind. Rebuild that. Interesting, that's something in my history from the last time I used UCP. B, get, yep. Convert to FTL, flash. Yeah, I'm going to have to do something about that. The flashing and the waiting for the flash to be loaded is really painful. Yeah, I'm just not really sure of a good way to do it. I mean, bolting on SPIFFS would work, but it's pretty nasty. I may even have to add trim support to Fusix, which is straightforward. There's a routine that is called for every block which is no longer in use by the file system. So we do actually have a way to pass on to the platform code that blocks and no longer in use and can be erased. But I believe that we have now successfully loaded our first binary. Whether it's loaded correctly, I don't know, so I will actually put in some checks. Okay, print F. So this will dump the first 32 words of code and data. That's not right. Makes pointer from integer. Oh, that is a, that's a pointer. Okay, so I, better. So let's see what this does. That's not so great. That's not a good address. That's a bad address. We didn't even get this far. So Udata UISP is a pointer, so adding a value on will actually increment it by four. 4001800 is ROM, 4001800, 4001800 is ROM. This is deep stuff. This isn't a simple routine. It's called something it shouldn't. Yeah, I know what's happened. I'm doing pointer, I'm doing arithmetic on a void pointer because UISP is a void star and not a void star star. So actually adding values on here, if it was a void star star, it'd be incrementing it by the size of a void star. But adding a value on to a void star increments it by the value of a void and which is actually illegal, or at least was illegal, but GCC has historically had an extension that does byte arithmetic on these. I don't know if that's changed in modern C compilers. So that was trying to write underlying pointers. Right, and let's see what this does. Okay, we've got stuff. So what do we got? 8800B01. Yeah, okay, the stuff I did here, this is actually not written. Oh dear. This has the file header has been read into kernel memory and not into the instruction memory. And we've then read all the instruction data at the bottom of instruction memory rather than at the right address. So let's have a look at data. 2158 and 6374652F, yeah. This is where the data starts and that's working. Okay, so I am going to have to... Well, we can either change this so that text start is here, but then that means that this file format isn't valid anymore because text start is supposed to be from the very beginning of the... It's supposed to be at the very beginning of the file. So even though it's going to waste 16 bytes, I think we need to do this and see what happens. And what does our code look like? See, I would expect this to be... Wait a minute, these are quads. So this is 16 bytes further on, which matches this. Okay, we are successfully loading our program code and data into memory. Excellent. So let's get rid of some of this tracing. Commit that. I think that will do for today. The next thing to do is to... That's interesting. I wrote the MSP430 low-level stuff in C. Gosh, that's simple. You know what? I was actually going to finish for today, but I think I am going to clone this because if I can do it in C for the MSP430, I can do it in C for the LX106, and it would be so much easier. Or maybe I can't, because the MSP430 used a different build system. Yeah, let's just stick to the machine code, not have to touch the build system. All right, anyway, I can use the... I can use this as a reference. Let's implement doexec because it looks pretty simple and then watch it crash. All right. So what this needs to do is load the stack pointer, enable interrupts, and jump to the start address. Now we are going to need to talk about stacks because we need to set up the boot stack, which... No, actually, it's fine. We do need to talk about stacks. That will probably happen next time. So start address is passed in on the... as a parameter. So I need to find a... I need to find how to get the... how to... Here we go. How to fetch the stack pointer from the uData structure. So that's going to be... 32i stack pointer A3 uData uISP. And interrupts should actually be on, really. Invalid symbolic operand. Line 19 needs to be actually defined somewhere. Oh, I never got round to making one of these. So this... This is definitions used by assembler code. So these are all the offsets into the uData structure. So let's just copy the 68000 one and hope it's right. So uISP is here. 42. The 26 showing up here are 416 bit platforms where pointers are half the size. I think uBlock size is actually 512 for us, but we might have to change that. That's the wrong file. And we want to go to our low-level routine. I said our low-level routine. And do this kernel nx106.dev. Okay, that's built. So let us see what it does. This will actually try to run the init code. We do not have a system call handler. So it's just going to hang. Okay, I was not expecting that. I was expecting some more exceptions to appear. But this gives us a place to start tomorrow. And we are going to be working with trying to debug machine code with no debugger. Yay. Well, I hope you enjoyed this video. Please let me know what you think in the comments.