by Nicholas Carlini 2023-12-03
So let's suppose you had a 1980s Apple ProFile Hard Drive, and you wanted to recover the data.
But the drive is older than you are, and it doesn't even power on. Oh, and the Lisa it was connected to is completely corroded to death. So you can't use the Lisa. (yet.)
But you do have an Arduino, which runs 3x faster than the original Lisa did (for ~5000x cheaper too!), so you have that going for you...
What do you do? Well, apparently this.
The Apple Profile Hard Drive was an external hard drive designed for use with the Apple III and Apple Lisa. It's an early 1980s drive, with a capacity of 5MB and was initially sold for a price of $3,500---just under ~1 dollar per kilobyte of storage. (For comparison: today you can easily get 50 gigabytes---50 million times more---for the same dollar.)
The ProFile is actually more than just a hard drive. In fact, it's two separate devices bundled together:
The controller connects to the Lisa through an Apple proprietary protocol over a standard DB-25 cable. The protocol is fairly simple, and is described in several manuals I managed to find online. Here's a logic timing diagram of how to read from the drive:
That is, in words the protocol works like this:
In order to actually indicate that the Lisa has read or written off of the data bus, there's a fourth pin, PSTRB, which the Lisa pulses high for a few microseconds to say either “I've read the data, you can put new data on the bus” or “I've written the data, you can take it off the bus now”. The ProFile can read or write data to the bus at a rate of 1MHz.
Now let me comment briefly on what I needed to do to get the hard drive turned on. I'm not going to linger here much because this isn't something I'm good at and so don't actually feel qualified to talk about it much. Which also means, a word of caution:
THIS IS NOT AN INSTRUCTION MANUAL. I DO NOT KNOW WHAT I AM DOING. IF YOU FOLLOW THESE INSTRUCTIONS YOU WILL PROBABLY BREAK YOUR PROFILE. AND MAYBE GET ELECTROCUTED TOO.
When applying power to the ProFile, I put a volt meter on what's supposed to be the 5 volt rail, I wasn't getting any voltage within a few seconds. Given that this drive is 40 years old, I figured that the capacitors in the power supply were probably dead. Capacitors are known in particular to fail catastrophically, and I didn't want to cause any serious damage, so I decided to just replace them all. This involved opening up the power supply which is kind of scary because it connects to the mains voltage. But I was pretty careful. Buying the replacements wasn't so expensive (~$20) and I didn't do too much damage to the board resoldering them.
After this, the power supply actually gave the correct voltage readings on the 5V and 12V rails. Which was a good sign. But when I plugged it in to the ProFile the drive didn't spin.
So here's where I did another thing that you probably shouldn't do. Either the drive motor was dead (in which case I'd have to replace it), or maybe it wasn't dead but just was a little bit stuck. In either case, I'd need to open up the drive and try to see what's going on. So I got it open, and then just manually spun the motor around a few times to hopefully losen any stuck bearings.
After reassembling the drive, I plugged it in again, and it still didn't work, but I did hear the motor trying to spin. So I opened it back up, spun it around a few more times, and this time before turning the drive on, put it under a heat lamp for a few minutes. (Not that it's cold in california, but the drive was sitting at around 50 farenheit / 10 celsius, and figured maybe this would help warm up any stuck bearings.)
And then it worked! I don't know what actually ended up fixing it, but the drive now spins. And even better: when the ProFile starts up, it runs through a self test of a bunch of sectors on the drive. And mine passed! (I think. The red light on the front comes on, which I think means it passed. Again: I don't really know what I'm doing here.)
Now that the ProFile is powered on, let's try to read some data off of it. We'll walk our way through the protocol step by step writing the code to directly follow the protocol outlined above.
Steps 1-4: as described abvve, we should raise CMD, and then the ProFile raises BUSY, we read the 0x01 off the bus (and pulse RSTB), we lower RRW, write 0x55, lower CMD, and then the ProFile lowers BUSY. Except there's a catch... the ProFile decided that, for some reason, the pins that it uses for PSTRB, CMD, and BSY are inverted. So you should flip everything that I just said upside down (except for RRW which stays the same.)
// step 1: lower cmd
digitalWrite(CMD, 0);
setup_read_bus();
delay(A_SHORT_WHILE);
// step 2: wait for BSY to go low
while (digitalRead(BSY));
delay(A_SHORT_WHILE);
// step 2.5: read the 0x01 on the bus
int is_ok = read_byte();
// assert is_ok is true
// step 3: write a 0x55 on the bus
setup_write_bus();
write_bus(0x55);
delay(A_SHORT_WHILE);
// step 3.5: tell the profile we wrote it
digitalWrite(CMD, 1);
delay(A_SHORT_WHILE);
// step 4: wait for the drive to say it's read it
while (!digitalRead(BSY));
Here I've made use of a few helper functions to read and write from the bus. I'll show just the read function here, if you want to see the write too, you can find the whole implementation on my github project.
int read_byte() {
int o = 0;
digitalWrite(PSTRB, 0);
for (int i = 0; i < 8; i++) {
o |= digitalRead(i+2)<<i;
}
int parity = digitalRead(PARITY);
delayMicroseconds(A_VERY_SHORT_WHILE);
digitalWrite(PSTRB, 1);
delayMicroseconds(A_VERY_SHORT_WHILE);
return (parity << 8) + o;
}
And if you run this, then you actually get to see data on the on the parallel port that makes it looks like it's understanding what we're sending! The first byte we read is indeed 0x01 and that's a very good sign.
Let me show you what's actually happening over the port. (I don't have a digital logic analyzer, but I did buy an oscilloscope for this project. So I can show you four channels of data. I've picked the four control channels, you'll have to trust me that the data channels are correct.)
Here you can see that even though the chip is a 5 volt logic chip, BSY actually only being reported at slightly over 3 volts. I don't know if this is something wrong with my Lisa, expected behavior because BSY is tied to the LED on the front panel, or something else. (Maybe there's an internal 3.3v logic somewhere? Who knows.) I also found it interesting to actaully see the processing delay between setting CMD low and the ProFile responding with BSY, and then high to high again.
Step 5-6: Now we need to run the next steps, and tell the ProFile which sector on the drive we want to read. We do that with this code here
// step 5: write command bytes
write_cmd(0x00); // read
write_cmd(0x00); // location 00
write_cmd(sector1); // location high
write_cmd(sector2); // location low
write_cmd(0x64); // retry 100
write_cmd(0x14); // no idea but set it to 0x14
delay(A_SHORT_WHILE);
// step 6: say that we're done
digitalWrite(PSTRB, 1);
delay(A_SHORT_WHILE);
setup_read_bus();
delay(A_SHORT_WHILE);
digitalWrite(CMD, 0);
Where the implementation of write_cmd is given here:
void write_bus(int byte) {
int xorb = 1;
for (int i = 0; i < 8; i++) {
digitalWrite(i+2, (byte>>i)&1);
xorb ^= (byte>>i)&1;
}
digitalWrite(PARITY, xorb);
}
void write_cmd(int byte) {
digitalWrite(PSTRB, 1);
delayMicroseconds(A_SHORT_WHILE);
write_bus(byte);
delayMicroseconds(A_SHORT_WHILE);
digitalWrite(PSTRB, 0);
delay(A_SHORT_WHILE);
}
Again we can see the diagram of this below. Sorry that I can't show you the data bits being written. Just be happy you get anything at all.
Step 7-8: Finally, we do a lot of cleanup to make sure everything went through correctly.
// step 7: wait for read
while (digitalRead(BSY));
// step 7.5: and read the byte on the bus
int read_ack = read_byte();
// assert that read_ack is 2
// step 8: write a 0x55 on the bus
setup_write_bus();
write_bus(0x55);
// step 8.5: tell the profile we wrote it
digitalWrite(CMD, 1);
I found it rather interesting that BSY starts to have a large amount of noise on the line right before it goes low. Maybe it's doing something internally that's drawing power or something? No idea. I'm pretty sure this isn't just noise on the oscilloscope because I can replicate the effect consistently.
Step 9: And then we wait ... for quite a long time, until the disk seeks and we reach the data we asked for.
Step 10: Finally we read the data off the bus and dump it to the serial port of the Arduino.
int log[4+256+256+20];
for (int i = 0; i < 2*(4 + 256 + 256 + 20);) {
// read each of the bytes on the bus
int n = read_byte();
log[i++] = (n & 0xF) + 0x40;
log[i++] = (n >> 4) + 0x40;
}
// and when we're done dump it to the serial port
Serial.print("<");
Serial.write(log, 2*(256+256+4+20));
Serial.println(">");
In the plot below, I've moved the oscilloscope probe to probe just PSTRB (which is the pin for the arduino to ask the ProFile for more data) and two of the data pins. The other 6 pins behave similarly but then the plot gets messy. (And also my oscilloscope only has 4 channels.)
You can see here how the response bits aren't as perfectly clean as you might think, where sometimes they overshoot by a pretty good amount. Also, I see the same slightly-over-3v response as from the BSY pin, so maybe it's actually expected that this should happen? Or maybe there's some 3.3v logic inside this thing after all? I don't know, but it works!
Could I read the data faster if I really wanted to put in the work? Yeah, probably. The ProFile is capable of feeding these 532 bytes at 1MHz and I'm only reading it out at like one twientieth of that. But this was good enough for me to read the entire contents of my ProFile off this 40 year old disk.
I've made my code available on GitHub at this repository if you want to use it for yourself. I'm pretty sure it won't destroy your hard drive---it didn't destroy mine---but I'm not making any promises.
After running the above code for every sector on the disk, I got a full 5MB disk image, and it looks like it was a success! I'll have to do more work (later...) to figure out how to actually mount this disk image on a modern computer, but if I run strings over the image I can read a few nontrivial pieces of information, e.g., ":ESCAPE cannot be used to exit this menu; use Quit instead.", "!No device drivers have been read.", "%You must use the 'Read' option first.".
Here's an image representing the entire disk, with one pixel for each byte, each page represented as one small grid. It looks like there's a bunch of code there (that's what I'm guessing the random looking data is) along with some things that have patterns (encoded images?) and then a bunch of empty blocks.
As for what any of this data actually means, that's a problem for another day. (You'll notice I never said I actually needed any of this data. That's because I don't, and I was told this drive was probably factory fresh. So this entire project really didn't have any point except for the fun of it.)