 Now the first thing you'll notice is the horrible muddy mess over here. That seems to be the best video I can come up with. There's still something very wrong with the contrast code that I haven't figured out yet. The second thing is that I took the lid off and did some circuit tracing and I've managed to identify a number of interesting chips. One of which is the touchscreen controller and all this portf, portk stuff that is happening when the contrast is being set does seem to be talking to the touchscreen controller which doesn't really make a whole lot of sense to be honest. I've got the datasheet somewhere, not that one, this and it has a bit of information about the control mode. And I don't really see anything particularly relevant to contrast generation so unless it's happening as a by-product of something else I'm not sure what's going on there. But let's ignore that for now. Instead let's work on the keyboard. The keyboard is the other interesting thing. Do you remember ages ago when I first opened this thing up? There was a mysterious chip that I thought was a 8k flash chip. Well, I was right. It was an 8k flash chip wrapped in a can I get this full screen? There we go, wrapped in a ST 8-bit microcontroller. This is the thing that is connected to the keyboard and this is in turn connected to the second SPI interface on the 68000. So in order to work the keyboard we're going to have to talk to this chip and we've no idea what it's programmed with. Luckily I think we can avoid a lot of that. If we go back to trusty-old-gidra and follow some of the relevant looking functions we can see here disable key interrupts is setting a bit in PD cell. So this is obviously the keyboard interrupt line. And this scan basic palm keys, well the first thing it does is call this function which I am sure, well this is bit banging various things and then setting up the second SPI interface. So I reckon that this is resetting the ST microcontroller with some kind of parameter zero in this case. So probably we can just copy all this code. So I think what this does is reset the microcontroller and sends a command in then it sets up the SPI interface SPI data to it is that is reading a word from the SPI interface. Ah here's the one I was looking for. So this sends a zero to the SPI interface which will of course it clock in another byte of data which is coming out here. My suspicion and this is based on a suspicion and nothing else is that this is an incredibly simple device and what you do is you wait for a interrupt to arrive and then you clock out a random byte and you clock in a scan code from the keyboard. It does seem to be doing several things and there's in turn these subroutines. This just maps what I believe to be scan codes to modify keys. This does basically the same. So this would seem to be basically everything we need. Trap A249 here I have the list of traps that is a hardware delay specified in this number here specified in some kind of device agnostic intervals because I also found the hardware delay function. In fact there seem to be two of them and what this is doing is it's counting well it does computation based on the parameter there and then it counts PLL ticks based on the computed value. So this is just calculating the number of ticks I can do that and yeah you see we read the PLL value and then we keep reading it until this bit changes and then we go around again. So we can probably also replicate the delays as well. But first let's go back to the keyboard init code. There may also be a enable key interrupt. So let's go and look for that. Search for key interrupts in memory. Got this string here. Yep there it is. So again all that is doing is clearing the PD bit. This subroutine which is quite big is called private get new key states. So I'm going to bet that this is the thing that gets executed when an interrupt happens that reads the list of changed keys from the microcontroller. So if we disassemble here stick that in as a label and turn it into a function. So configure SPI 2. This could be chip select to the microcontroller. Clock out a zero byte. Start the exchange. Read the value. Keep looping until something happens. Undo chip select. Then why would be be reading another bit. I suspect that this could be oh wait no hang on this hasn't read data. This is checking the bit in the control register to see whether the transaction has completed. Then we read the byte and this controller also has a FIFO on it. So this will read. I was going to say this will read multiple bytes because that's not true because we have to clock out one byte for every byte we want to read. So this has read one byte. Has anything happened then this code is handling changed key presses and this is a 249 not that's a delay not a very big delay. It's probably getting more data. So what are these functions it's calling. This one do we have a name. Oh it's our old friend again and this one's going to be yep. As I said there seem to be multiple copies of all of these things in the ROM. Very odd. So check the basic keys. Check the extra keys. Do this key manager assemble assembly. Put the parameter in D0 move D0 to SR. This is probably changing the interrupt mask bit. This one is hub state changed and I have no idea what parmos means by hub. That's not a USB hub. This one think right. I believe that this is I believe the SPI controller is responsible for a whole bunch of hardware and this is all coming in at once. Hopefully we could ignore a lot of it and is this a function. Looks roughly like code. Now this is more of our code. A249 that's a not. See there's quite a lot of this happening. Here's another thing this one's poking lots of IO ports and SPI stuff. Lots of delays. Oh it's this again. I think we're looking at a different copy of all these routines. So why would this be resetting the microcontroller? Well who knows to be honest. If this value uvr8 is less than 8 and I bet uvr8 is what we got from the control register, not the data register. This is the date. This variable's got the date data in it. So probably okay well let's go and look up some of the some of these registers. SPI cont2. Here we are. So this one this controller is rather different than the other one. The other one has got separate transmit and receive registers. This just has a general data register which if you write to it it pushes stuff on top of the transmit FIFO and if you read from it you get it from the read FIFO. And also the enable-disable module bit has to be set before you set anything else. It says somewhere in the documentation. Here we go. You must enable the enable bit before you can change any other bits. So we'll probably find code for that. So if the value we read back is less than 8 that's going to values from 0 to 7 adjust this spit count register. That makes very little sense. It's the okay I believe it's actually reused uv7 for something else here. Yes it has. I don't know what wv1 is but okay well I think the first thing to do is to well the first thing to do is you notice that this thing is displaying the gem desktop just. And we're not going to bother with that so let's just rebuild that without the AES reset and write. Meanwhile over here we want to find the reset code. Let's find the other one. This one because this one we've filled out the the trap so it decompiles moderately correctly. And let's start mapping some of these bits. Actually let's do this one first. So here's our documentation for what the various bits do. And what this is doing is it's setting this pin to be a GPIO which means it won't trigger any interrupts. So that is bit five. No it's not it's bit four. And this is set to a default. Yeah okay oh it's one oh one oh oh oh oh. So to enable interrupts we turn this into a GPIO bit and then the interrupt fires. Okay well that just finished writing so let's run it the screen comes on and you should be able to just make out the the prompt. So yeah I was mapping this stuff. Okay so it's using mostly port k and b. k is here e is b cell six zero oh one one oh. So it is particularly interested in fb is one oh one one. So yes this is port four a bit yeah port k that's this bit here port b this is touching this bit but who knows what four. Then we set up the SPI controller to a nice hard coded value which means it's easy to look up two two zero zero uh that's oh oh one oh. So that is running at sysclock by eight uh oh oh one oh. So we enable it with no exchange and the other bits are zero. Right that as the documentation said it's setting the enable bit first two three oh seven two oh oh one one that's starting a transfer uh zero zero zero zero zero one one one one one one for the bit count is eight bits. So that's telling it to transfer a byte which in this case is the zero that it's just pulled here. Okay so this is going to be just thinking about where to put it in the code. So I would put it here but as the vectors are initialized after then probably shouldn't uh SPI here has got to do with the SD card. I suppose if it's going mostly going to be the keyboard we're going to need some keyboard routines. So is there any init code? This module also seems to work with mouse and of course I don't know where that's coming from yet called by the interrupt routine for key events. Do I actually want any of this code? What do the other platforms do? Let's go for the Amiga. Amiga keyboard init. Okay that seems plausible. Okay machine Dana. Dana keyboard init. Where else is Amiga stuff happening? Capslot management. I keyboard right B. Okay just those two places. Let's avoid Dana I keyboard right B. Hang on we've just done that. We want a keyboard init. Okay that is does not have an I on the keyboard. However we do need to put this in. Okay and in this code we want Dana keyboard init. Now what are we going to do? Well we need to we need to go to the datasheet and look up port K and figure out what these pins are because it could be that these are the SPI two pins and it's using GPIO bit banging to reset the controller before handing them off to the GPIO module. No apparently they're not they're just GPIO bits. So reset line maybe I would expect there to be a reset line and a chip select line. So the other one is in port B. This one T in T out timer input or output so I don't think that's anything we care about. So that's another GPIO. There's a delay there. The other one we wanted was port D for the interrupt. I have too many windows open. So this is put D is the one with all the interrupts on it and this is on the wrong workspace that's why I get confused. Bit four is IRQ one. So go to the interrupt controller it's external interrupt level one. So I believe that what we need to do here is set this to a interrupt handler and we are going to have to clear the interrupt because this does not get cleared automatically and oh yeah also figure out the polarity control stuff. Okay Dana Dana in it here this is IBR is not set in my init script. So we do want to set it ourselves I think. IVR is the interrupt vector. We don't want to care about that. It's this one we care about. We need to set the polarity control to the right thing. Unmask it here. Okay during interrupt service the handler determines the source of interrupts by examining ISR. When the bits in this register set they indicate that the corresponding interrupt is posted to the core. That's when reading and program does edge triggered interrupts. These can be cleared by writing a one. So we in fact need to find out whether this is an edge triggered interrupt this bit here or if it's a level triggered. I'm hoping it's level triggered because they are much more sensible and less problematic in every possible way. And we're looking for not that window that window. Okay interrupt control register is here. Where is this being set from? Hardware RAM init. This is setting these bits. All right that's just enabling the timer I think. What's this function? Hardware sleep. So I'm probably going to guess the hardware wake enables these things. Again that is only setting this bit which is pole 6. IRQ 6. So maybe zero is already the right value. Oh this is doing something. Ah EFF yes that is clearing the same bit. BOE is a register that I never got around to actually looking up. BOE is ITC ISR and it's a word. So that's not actually being set in very many places. We've got hardware wake which is not touching that particular register. We've got hardware init which is not touching that register. Yeah whatever is on I think that whatever is on IRQ 6 we need to care about. Oh I know what's on IRQ 6 because we've done that one. It's this one. It's the timer. So should we have set this to be edge level? I think we should have. So where are we setting up the timer? Here. No we're not. No we're not. Because the timer arrives on that vector but it is not IRQ 6. This is an external interrupt coming from something else on the system and we don't know yet what that is. Interrupt polarity for the IRQ 6 signal. It's all mixed up with the PLLs I have to say. The system seems to work without it so let's ignore that for the time being. Okay so let's assume that this is all we need. So we go over here to our interrupt code. Push critical registers onto the stack. Call. Dana. IRQ interrupt. Pop our registers back and return. So I think that is our interrupt handler. Also need to enable the interrupt. This was IRQ 1 so this isn't touching IMR anywhere. Well we're going to want this eventually so that will cause the interrupt to trigger immediately. Let me just check what this is wired to. Bit for IRQ 1. I'm not sure why we have ints and IRQs. Interrupt bit. Interrupt request bits. Inter signals are all level 4. IRQ has its own levels so this is going to be IRQ 1. This is a very weirdly complicated interrupt controller. Okay so IRQ 1 is bit 16. Is there anything else we need? I don't think there is. Okay now this isn't going to do anything because we have yet to reset our microcontroller. So that's this code. So what is it doing? It's easier to follow in the assembly actually. It is a big-ish loop. So this is the command. This is... That's a loop counter. Right. We keep pinging the microcontroller until it's ready. And then if it's this command then we do some stuff otherwise we don't do anything. Now the only time I've ever seen this thing being called is where the zero is the parameter. So this is more of these weird timing things. As I said, A249 is hardware delay. So what it seems to be doing is this chunk of code is trying to reset the microcontroller. Then we read a particular bit to see whether it's ready or not. If it's not ready we do some more resetting around again. If it is ready we do this setup. And I think there's some more bits we care about here. So pk4 is... Okay we do have that one. pb40. This code is somewhat mystifying. So might that adjust in the stack pointer? Hmm. So this is setting the bit, then resetting it, then delaying. I am going to guess that that is the reset bit. Then there's a delay. Then we jump to the end of our loop and go up here and do it again. Right. So this is 101.1. So this is we clear the bit, set it to a GPIO, set it back to a dedicated function. But pk4 doesn't do anything. So that's bit three of pk4, bit two of pk4, which is LDS, which is a RAM thing. What's LDS? This looks like a RAM thing. Here we are. pk2, value four, LDS. Data strobe signals. These pins default to GPIO into input pooled high. Yeah, this is all to do with the system bus. So why would they be un-setting it? So here we read the bit. We check to see whether it is set or not. Okay. That looks weird, but straightforward. So let's hash out a bit of code. What we're going to do is reset the controller, see if it's ready in a loop. So to reset the controller, we are going to, I'm getting pkdir and pkcell mixed up. pkcell is the one that sets, whether it's a GPIO pin or dedicated function pin. pkdir is the one that configures whether it's input or output. That makes much more sense. So what this is doing is clearing the value, setting it to be a whatever it is. If we're going to read it, I'm going to say that I'm going to guess that that is setting it to an output. pkdir one output. Now, this isn't reset. This is ready. So this is all actually happening here. We're doing our code in a slightly different order. So this will cause the pin to be driven down for a short period. I should probably figure out how long these intervals are. We then set it to be an input several times. Then we read it. If it's zero, then it's not worked. If it's non-zero, then we have successfully reset the microcontroller. Okay. So now the reset logic is this bit here. We're always going to go through the reset logic at least once unlike this code. That's on PB, which is bit six. So this code is set it, reset it, wait. So then we also need another delay here. So I think that is the bulk of our code. But we need to add some pkdir is 440, pdcell is 4.1b. We have a warning here. The IMR is IMR is a 32-bit value. Okay. So that builds. So let's figure out some of these delays so that the reset delay is a 3e8. It's this one. This is short. Ready test. This is 32. That's why the code works like this. If the value we get back from the controller after initialization isn't right, then we reset it and start again. I did wonder. Oh, look, there's another bit. Port e, bit three of port e. And port e is set to 200. That's not a correct value. In it, we're pre-running it. This is where all these things are set. So, oh, right. It's just I cut and pasted from this. So that should be c8. C is 1100. So the ST microcontroller bit is a GPIO from the start, which is there, which is nice. So let's leave that off. So we'll leave the interrupts disabled. This will just run through the reset procedure and see if it actually responds at all. Okay. Hardware delay. Right. Now, this is the delay in some units or other. This is converting, this is a division. So what that's doing is dividing by 256, adding on divided by 16, adding on divided by 16. Is that actually, do I have my precedence correct? So delay is indeed one. You want to see zero, shift right, add D zero, take the original value, shift by eight to D zero, shift the result by four. D, D six is the resulting ticks value. I wish you could rename variables over here without renaming the registers over here, because it's not helping. Registers are frequently reused, even if Gidra thinks it's a register variable. Okay. So this shift is shifting the result. So therefore, what we've got is x divided by 256 plus x divided by 16 plus x divided by 16. So remember how to do my maths. Yes, of course, this is x times 256 plus 16 plus one. So this is 256 so 16 equals x plus 16 plus one, which is 496t times 273x. Therefore, ticks is 273 over 496 times x is not a terribly interesting looking number. It's 15 inverted. So I think that is just t equals x divided by 15. Right. And that makes sense, given it this, because 15, whatever these are, is the smallest amount you can delay that will turn into one tick. So anything less than that, it just falls through. Okay. So PLLF's FSR is it's looking at bit fifth. This is not the right register. Here we go. Clock 32, the bit switches with each cycle of clock 32. What is clock 32 connected to? I have no idea. It's not the system clock. I don't think it's the 32 kilohertz clock. So each tick is bit switches with each cycle. Is that a complete round trip of high to low? Ah, here we go. No, it's just a simple oscillation. It's simply wired up to the clock signal. So at 32 kilohertz, this means that going from low to high, and back to low again, that is waiting for two transitions is one 32th of a, well, it's 32.768 kilohertz. So half of that is twice the 64 kilohertz. So here you can see an example where 31 periods of the clock is one millisecond. If we're dividing by 15, that suggests half a millisecond. But why would this delay be in half milliseconds? It would make much more sense for this to be a millisecond delay. And I just realized that over here in my web browser, I actually have the Parmos SDK. So I can actually go look at the headers. Is there a hardware? Search it. Hardware delay. Microseconds. That does say one millisecond. Well, I am going to believe the documentation. We can't wait for microseconds. We can wait for milliseconds. But let's just stick in a... I wonder if I got this wrong. It's quite possible. It's been a very long time since I've done elementary algebra. So this line, we multiply by 16. Or, you know, let's just feed it into this and see what comes out. That, I believe, is about 115. Okay, it is dividing the value by 15. I was right. That's amazing. So we divide the tick value by 16. Let's make this a u-long while u-word PLL equals PLLFSR, while PLL, PLLFSR. And there's a race condition here. If the PLL flips over between this read and this read, then I think you'll lose a tick. Okay, so this is a short delay. This is a longer delay. What is 3e8 in decimal? It's a... it's a bloody millisecond. I should have done that a while ago. So this was 32. Okay, so has our... what has our delay turned into... oh, hang on. Did I actually do that right? Yes, I did. Good grief, that's actually calling a divide routine. Don't think that should be doing that. Okay, and this code is all L, so that is basically the same thing. All right, now let's try running it and see what happens. Okay, so what do we get? Nothing. Right, this happened last time. We have enable kdebug here, but I am not convinced it's actually going anywhere without more configuration. You know, Dana debug print. Is that being compiled? Yes. Is that being compiled? Yes. I think it's hitting this and failing. That is, it's bailing out here and never actually printing anything. I think I do not want console debug print set. I don't think it does what I thought it did. I think that what this does, it goes to the BIOS console. That's set to zero, so that is not actually being executed. In fact, we can simply disassemble this. EK printf is inlined. In fact, it's going straight to Outsie Dana as 232. Outsie Dana. No, no. It's gone to, what has that actually done? So it is, okay, it has called do printf. This push here should have pushed a printf Outsie Dana RS232. It should have pushed this. This goes to RS232 write b, which is here, which sends the byte. Interesting. Well, I think given that it seems to work with serial console set, let's try with this and see if we get any more tracing. Okay, execute. I am suspicious. I'm not quite sure what of. I'm just generally suspicious. Let's try turning this on as well. Okay, and what does this do? Still nothing. You see, I'm pretty sure that this is similar configuration to what I was using before. Have I enabled debug there? Yes, I have. We're not getting any of these messages either. He's calling do printf. What have I changed since last time? That looks all quite reasonable, to be honest. Okay, well, I know this was working at some points in the past. So let's take a look at previous versions. 7476, let's go for 7473, because that was a known working configuration. Okay, I was taking a bit of a shortcut there. Let's do that properly. Also, I don't remember whether I actually remember to do a make clean. That could well be the problem. And we're ending up with an inconsistent build. Okay, and execute. That's better. Okay, so we reset the microcontroller. It said it was ready. So now we want to set up the SPI stuff reset, which is this code. So the first thing we do is we wait for 1B58, 7000 microseconds, and we then do something with port E. I bet port E is chip select. I wrote that down. SPI, SPI data 2 equals 0 equals 2202307. We then want to wait until this bit is clear. Exchange, yep, wait, 80. That's not the exchange bit. 80 is the IRQ bit. This bit is set when an exchange is finished. Okay, remains a certain test cleared. Okay, so we want to wait until it's finished. We then raise CS and it'll be active low, which is why raising it disables it. And then we get a, this will probably be fine, if the microcontroller said A, A, finish. Okay, and of course we need more registers. Getting quite a collection here, to be honest. So SPI-Cont2 is a word at 802. SPI data 2 is a word at 802. PE data is a byte at, I'm going to guess it's 423. No, it's not. I should point out that while I was setting up to do this particular session, I spent quite a lot of time trying to figure out why the initial gem image wouldn't run. And then I tried, you know, firing up Parmos and that wouldn't run either. And it didn't do anything and the backlight wouldn't come on and so on. Till I eventually I realized that I had, you might work, yep, that I'd taken the thing apart in order to trace lines. And when I put it back together again, I had forgotten to plug the screen and keyboard in. So, yes, this, I think that these videos are containing more incompetence than usual. Okay, and we execute. Well, we're getting 3A's and 3B's. What happens if I type? Doesn't seem to be changing. 3A, 3B, 3B, 3B, 3B, 3B, 3B, 3A, 3B, 3B, 3B, 3B, 3B. Is it always? No, it's kind of random. Did I miss some weight? Yeah, there is one there. So this is testing the exit bit and jumping up to the top where it tries to ping the board again. So it doesn't reset it. If this condition fails, it doesn't reset the microcontroller. It just prods it and sees whether it responds. And only if it doesn't respond does it reset. Let's try changing our code to be the same model. I think I've got my select backwards again. IOPORT functions. Yeah, I've got my select backwards again. That won't help. And PE cell is 423. Okay. Okay, and now we execute it and well, it's different. So the controller is always ready. And it's always saying 39. So here if the command is EC, then we do the same thing as before. We clock out the zero. We get back. Shouldn't this be, if the command is EC, shouldn't we be sending the command at some point? But we are not. Hang on. Here's the command, which is zero. Oh, good grief. I'm an idiot. Yeah. I think this, yeah, this is not my best work. Okay, let's try reading it from the right register this time. I'm not sure if mistakes like that are something that I just generally do and don't think anything of it because I correct them. Or whether that is unless I'm actually narrating it like this, in which case it becomes more obvious that I've screwed things up. Or whether I'm making unusually more of these at the moment. Okay, and now let's run it and see what it does. Fantastic. Look, the controller is awake. We are now talking to the keyboard controller. So let's lose this tracing messages. Let's wire up the interrupt handler. And inside the interrupt handler, what we are going to do is do a transaction and see what the controller said. So I'm hoping here that if I type, I will see scan codes show up. They're probably, I don't know whether the messages are going to be multi byte, but we should be able to actually check that by doing scan basic palm keys. Okay, so we reset the microcontroller. We send a zero, but a zero is an AA. Maybe we only get the AA the first time. I mean, if you send a zero, you get back an AA. Maybe you only get the AA the first time the microcontroller wakes up just to say that it's ready. And then you get scan codes. So it's, we seem to be getting WVAR1 and it's then setting bits in the return parameter. I think to tell you what it, maybe what kind of event it is. And then we read another byte. Yeah, I think that's how it works. Okay, let's execute this and see what it does. It may just spam us with gibberish. Zeroes. Okay, let's, oh, right. I am going to guess that this is a sequence of key pressed command and then the scan code. What happens if I press more than one key at a time? That's kind of slowed things down a bit. Okay, well, just hold down, let's say h and then click control C. And we've got, well, random bytes. However, we are getting a steady stream of bytes, which means that the microcontroller is always, always has the interrupt line set. So do we have to tell it that we've finished? I've noticed here it's turning the SPI module off after it's finished. So this appears to be a single scan. This is, it's a, it's a pull system. You call this function, it then talks to the microcontroller and gets results back. What we want is interrupt based, is an interrupt based system where it tells us when it's got something to say. So prove enable int is called from, no, that's the wrong one. We want keyboard, keyboard, cable. What was that called reset? Drive open, check extra palm keys, scan basic palm keys. And one difference between, oh yes, scan is, here we go, disable keyboard interrupts and we wanted enable keyboard interrupts, enable key, enable key. All right, I forgot to name this. So there it is as a function. Unfortunately, Gidra can't find anywhere it's been called from. So memory search. Is there another one somewhere? Keep putting the mouse in the wrong window. Proof enable key, search all. One place. Okay. And we don't know where it's called from. That's not helpful. Ah, but there is this function that we found above it. It's very big function. Get new key states. Okay, let's, let's rework some of these traps. They're all delays. E3F1, that's a new one. Okay. So, and I will also take the opportunity to clear all the parameters because I think most of these are bunk. Maybe not. Yeah, we're getting some. Four, eight, scene, 10. Those are four pointers. Does it know where this is called from? No. Okay. Well, so we are, here's our usual exchange where we send a zero and get back a value. We then look at the various bits in the parameter that we got to see what it is. And depending on what they are, we do a number of different things. What's this? Unnamed helper function. Let's call that. Hang on. This is, this is disabling interrupts and returning the old interrupt state. There's a delay. This is reading bytes from the keyboard controller. This is writing something. PBVAR10 is IVAR4, which is in turn a pointer to something. Looking at what it's used for. Where is it? Where did it go? Here we go. So, PBVAR10 is figured out that it's a byte variable. So, therefore, this must also be a byte variable. Did I do the right thing there? Byte star. No, I think IVAR4. This is a byte star. Why is that not changing? Interesting. Well, so, this is doing a reset with a zero. This is the other implementation of reset ST with command. I was hoping to see something in here about clearing the interrupt line, but I don't. You know, like writing out a particular card coded value to the to SPI data too. Unless, no, here we have, this is reading keys. Okay. So, keyboard in it. PD cell is 0x10. Have I got my select backwards again? IOPORT function pins are connected. Wait. Wait. We want that to be a dedicated function pin. Yeah. Let's see what this does. Okay. And run zeros. So, that hasn't helped. So, this interrupt keeps firing. Why does it keep firing? It's possible that the 10 is cell 4, which is, should be this one. Yeah, data bit 4, IRQ1. Do I need to tell it it's a level rather than an edge? If it's an edge, then it will keep asserting until we mask it. So, zero is a interrupt request edge register. PDIRQEG. Let's take a quick look here. PDIRQEG is used, is written to, we're writing a zero. So, that's setting it to be level. Are we updating it anywhere else? This bit, this here is, where are we anyway? SPI open. Oh, right. This was setting up the, the SDIO guards. This is clearing the top 4 bits, including one of the ones we're interested in. So, they are level sensitive. Okay. So, IRQEN, port interrupt request enable. Yeah. The same thing is happening here. So, that's setting it to a zero. Oh, no. These are for the low bits. Yeah, because the top 4 are for IRQ1 to 4. The bottom 4 are for INT1 to 4. Bizarre. And that is clearing the bottom 4 bits. Okay. PDPolarity is clearing the bottom 4 bits. Are they being set anywhere? Yes, they are. To a zero. Oh, these only work for the INT ones anyway. Pull-ups? Do we need pull-ups? It seems to think we need pull-ups for the INT IRQs, but not the other ones. Ah, we do need pull-ups. But that should be set. PDPU and 1F. Yeah. That's done in our script. I'm just going to have another quick look at the documentation for the interrupt controller, whether they do actually need to be cleared. Interrupt status. Software handler, okay, may need to prioritize them. External interrupts int. IRQ1236 can be cleared when programmed at level-triggered interrupts. These interrupts are cleared at the cresting sources. So the microcontroller should be clearing them. Well, let's just try this anyway. So that would be clear that. Bit16 is IRQ1. Did you know that this system on a chip actually has built-in keyboard controller support? Which they're not using, I think, because there just aren't enough lines for it. I think you have to use all the D registers. I suspect that when they say keyboards, they mean very small keypad. So when we run it, still nothing. Okay, that didn't help. Interesting. Do I need to see maybe the interrupt, maybe the microcontroller uses the interrupts for something else? I know what we can do. So that will set the interrupt line to a GPIO. So that will just spin dumping the contents of PD data. So we should be able to see the, if it is an interrupt line, we should be able to see it go up and down PD data. Actually, I don't think that will, that's unlikely to work now I think of it. The reason is that the microcontroller will assert the interrupt line and will keep it asserted until we read all the data. Is our interrupt handler wrong? That doesn't look complicated. I don't think there's any way we can get into an infinite loop there. Trying various keys. They all do the obvious things. See, we could, one thing we could do is in the timer handler, we ping the keyboard controller to see if it's got anything to say. So that we poll it rather than relying on the interrupt. That should work. It's not brilliant. Do we actually have the right interrupt line? EF. Yeah, that is the right thing. Interesting. The keyboard controller is going to be quite complicated inside and we can tell by, you know, the size of some of this piece of, some of this code and all the stuff it's doing that it does quite a lot. So here we go. Here's the new key state thing. So it's possible that we're, there's just something I don't know that I need to do to clear the interrupt line. So I think the best thing to do is to poll it instead. That's a shame. Never mind. So what we're going to do is, here is our five millisecond vector. We are, I wonder, is there a better way to do this? So we could just put a call in here to call our interrupt handling routine. However, there's this complicated system where it goes out to the, to another piece of code, this, and there is some cold fire code in there and some stuff to do with repeat keys. See, it's already saved the registers we want. So time of C and Dana, J. S. R. Dana poll keyboard. That's the second time I've made that mistake. Okay. So in this piece of code, and I will actually, I think factor this stuff out. Okay. So, so what we're going to do is loop as the same receive zero, if not be break, otherwise, do that. This is all happening from an interrupt handler. So we should not be calling K debug, but let's just roll with it for now. Accessing the individual pins is very irritating because they are all, they're all on different ports and all the ports are volatile. So you have to manually handle reading and writing multiple bits in multiple different ports and it's all ghastly. I did originally think of using, there we go, bit fields for them. But the problem with bit fields is you end up setting and reading them one at a time. And because the registers are volatile, that means each one turns into an individual read or write, which again is not right. Okay. So we run it. Right. We got back. Oh, no, I haven't done anything. Let's press a key. 4118, 4118, 4118, 440. That was an H. Let's try a J. 4128, 4128, 440. Let's press H and J. 4218, 28. So H is 1, 8. J is 2, 8. So 4, 1 is clearly, keys have been pressed and the bottom bits are telling us how many keys. So that's two keys. We see four, two, three keys. Four, four, followed by four bytes. That could be rollover issues due to the keyboard matrix. Okay, let's try shift A and control. So shift is 09. A is 2B. Shift and A, 4209, 2B. Shift and A and control for 3092B7B. Right. We can work the keyboard. So the, where are we? Here we go. So the first byte tells us whether it's a keyboard command or not. And we can see that happening in the Parmos code. Here we are reading and writing, okay, if it's not zero, just give up. As this is happening 200 times a second, we want it to be as fast as possible. Yeah, I am doing the send and receive is, we are waiting for the microcontroller. I much rather use interrupts. But you know, okay, we are writing to our, to a structure now of our eight, what the thing was, if the top bit is set, then the remaining seven bits are UVAR 7, which becomes BVAR 12. This is hub management. What does this do? This turns interrupts off. No, it doesn't. This sets SR and UVAR 4, where was that set? Here. Disable interrupts. Okay. So this is putting interrupts back on again, calling prove hub state change, whatever that is. And then we exit. If it's not a hundred, we do much the same thing. Yeah, I don't know what the, what a hub is in this context. And this is checking various other states of things with the top bit set, none of which I know what they are. And here we are reading bytes and doing transfers and stuff. But we want to get to here. If this bit is set, right, if this bit is set, if this bit is also set, give up now, if more than eight bits are set, something's gone wrong. So reset the microcontroller. Otherwise, we fall out the bottom and read keys. Okay, so let's refactor this as well. We can't do that from inside a interrupt handler. Well, let's just pretend it doesn't happen. So if opcode and 0x40 keyboard command, if opcode and 0x10, then give up. Otherwise, actually, I just 040, ah, right, it's reporting the number of keys pressed with zero meaning no keys pressed. So it's not giving us key up key down events. This means we're going to have to keep track of what keys are pressed. I don't know yet what the Atari wants. So let's take a look at how the Amiga does it. Okay, so it converts Amiga scan codes into Atari scan codes, which apparently happens from machine code here. So it gets the scan code, it looks it up in the table and d0 and it calls keyboard vex, which is this structure. Do I need to handle key ups, key downs myself? Because what we're doing is we're emulating the Atari keyboard microcontroller. All right, what I was expecting was a, oh, keyboard int. I think we just need to call this. And yes, we do need to handle key ups, key downs. So where is keyboard int being called from? Oh, okay, right. This is to allow, yeah, the Amiga stuff was calling this via a vector so that things can, oh, that's useful. Here is what we actually need to send. Packet header to the packet length. Nothing there on keyboard. Here are the keyboard vectors. Okay, let's take a look at that Amiga stuff again. Keyboard vex. Right, that is calling the, it's calling this keyboard vex. So that's not part of the keyboard vex structure. Keyboard vex. So I don't think we can get at it from C, which is interesting. The reference to underscore keyboard, oh, that's what it's initialized to. So this is the vector structure. Right, here are the things that go further, that go for and after. Keyboard input, toss, greater, equal. This is the one we want to call. Undocumented feature is called with the received data byte in D0 and appointed to the I keyboard IO wreck in A0. It's tricky because we can't call that from C. Let's take another look at that Amiga stuff. Well, A0 is not the IO wreck. Yeah, here it's initializing it. Oh dear, we should actually call that properly. Trying to remember how to do function pointers. I think that's right. So we want to move, we want to load the value of keyboard vex into register equals general register keyboard vex. Is that right? Does that compile? It needs to go there. Okay, I've got my operands the wrong way round. That's specifying output one. As in volatile is GCC is typically overdesigned and incredibly complicated inline assembly thing. Right, keyboard vex is of course not exposed. So I think we do that. Okay, what does that actually produce code wise? Yes, that looks fine. Right, so we are going to be receiving a set of scan codes from the microcontroller. And we need to map these into what the Atari ST uses. But we need to send key up and key downs to the rest of Atari TOS. And the keyboard controller on the data doesn't do that. It only tells us what keys we actually have. So we're going to have to keep track of what keys were pressed so that we can send key ups. So we are going to we're going to have up to eight keys always get the order of parameters mixed up. So I always look it up to be sure. All right. So we're going to be reading the keys into a new buffer that you think we only know about the receive about the releases after we have received all keys. So we're going to end up with a new buffer and an old buffer. So so this will read so new keys is going to be a new keys is going to be the buffer containing well the new keys. And we read all the keys pressed from the microcontroller and stashed them in there. Now we want to compare this against the currently pressed keys and decide whether they are set or not. I think there's a better way to do this. So this will receive a key code. We then if the key being pressed is already in the buffer then we know it has remained pressed and we do nothing. By default we assume that they're going to be pressed. So if we get here Delta is now going to be zero if it was pressed and we do nothing with it other than keep the buffer updated or one if it was just pressed. So this will track new keys. Now I'm going to have to put a put that buffer in. Okay. So for for every key in the old buffer. So memory search. I think there is mem true. I don't know if the Atari TOS kernel has it. So if if the key is not in the new pressed buffer then this key has been released released. Then we go through the new keys. If there are any new keys aren't in the pressed key buffer that key has just been pressed. And of course the last thing we need to do is to copy the key the state over. So this is going to be Ubyte scan code pool pressed pressed keys undeclared. Let's do it the other way around. Okay. We do not have a mem true. So that's all there is to that. Can I do these both at the same time. Do not see any reason why not. Okay. Well let us try it and see what happens. Okay. So let's try this and okay press a key. Well that doesn't help. Is the system running. Yes it is. So we're never getting to press release key. Let's just put some tracing in and try this. Let's not do that because I think I figured out something that's wrong when I went from mem true to find key the sense of the return value changed. Okay. Let's try this. Okay. And execute press key. Oh. Okay. Doesn't look like it's doing anything useful. So our opcode was four of four zero. Then we are falling through this piece of code. So this should be receiving bytes from the micro controller. This is very much the same code as we would have doing before. Do I need to delay to let the micro controller get stuff done. Let's just put lots of undoes in. You see we've got this in place which is a delay in our get new key states. So somewhere down here we are reading the keys. We are reading one byte. We're checking it for the stuff and then we are delaying for 32 microseconds. Okay. All right. Execute. Press a key. And that's better. So oh yes. We want to scan code zero means nothing. So let's just put this in here. Scan code zero can never be found. So six, eight, seven, eight, seven, eight. However, holding them down causes them to be continually pressed and released. So I think my logic here isn't right. So I just pressed one key and then a space. Interesting. Let's try this again. Space. So the key went down. Why is it showing up as pressed when keys pressed should be empty? It should be new but not pressed. Let's try m. This logic is bogus. Okay. So if we ignore new keys that are zero, ignore zeros. If the key is currently pressed and it is not present in the new set, release it. New keys. If k and if the key, yeah. Okay. That might work better. And let's also lose this tracing because it's slowing everything down. And I'm seriously running out of time. So I'm hoping this will work because then I can finish. Okay. So execute. Press the key. Brilliant. Okay. That's the keyboard working. It still needs to be wired up to the system. This is wrote work. I need to map the keyboard and figure out what all the scan codes do and do the put together a conversion table. But that is the keyboard working. We should be able to have a fully functional, mouseless or command line based emu toss environment on this system. So let's just try shift a control. Seven B shift is nine. A is two B. Let them all go at once. Three releases. Fabulous. So I'm going to go to bed now. I hope you've enjoyed this video. Please let me know what you think in the comments.