Linux 1.0 kernel source reading - 15: Low-level sound, high-level SCSI

After something of an extended hiatus, I've taken this post-operation recovery time opportunity to read a bit more Linux 1.0 kernel. 'cos why not? :)

I finished reading the sound driver stuff, and I must admit I mostly skimmed it as the low-level details of individual sound cards didn't seem terribly exciting to me. "gus_card.c" is the longest source file outside of "tcp.c" - 3.5k lines! There's a lot of faff in there that I didn't care about, with both low-level hardware mangling and having to care about sound stuff. In comparison, the Soundblaster midi code is a doddle. Anyway, the sound directory is done.

For a bit of a change, I thought I'd attack SCSI. SCSI had always been a bit of a mystery to me. When I built my first Linux-specific PC around 1997 or so, I got a rather fancy Adaptec 2940 PCI card and SCSI disk, since SCSI disks were deemed important for performance back in the day (along with plenty of RAM - 16MB at 30 quid a meg!). However, the details of it all were magic, and this is a chance to learn!

Indeed, Linux 1.0 here doesn't support PCI or the AHA2940, but I guess only having the simpler stuff is better for me as a reader. :) Lots of the supported cards are built around the NCR 5380, so for preparatory reading I read the Wikipedia pages about SCSI, and skimmed the NCR 5380 data sheet. Then I plunged into the source...

The source is split into layers, with the abstract drivers, the intermediate glue layers, and the low-level device-specific drivers. I decided to start at the top end and work my way down. The generic end is around 8.5kloc, the device-specific code is 14kloc.

  • Makefile, constants.[ch] Usual Makefile structure, and the constants file contains a bunch of nice constants I recognise from reading up on SCSI docs.
  • s[gtrd].[ch], s[rd]_ioctl.[ch] High-level drivers for SCSI generic devices, tapes, CDROMs and disks. They sit on top of the infrastructure provided by scsi.[ch], and convert between the world of Unix requests and the actual SCSI commands needed. It's somewhat annoying that "sg" is overloaded as "scatter-gather" as well as generic SCSI! The block devices (sr and sd) use the standard block device drivers, which somewhat unobviously hook in through the do_s[rd]_request functions.
  • hosts.[ch], scsi.[ch] and scsi_ioctl.[ch] These provide the "middle layer". "hosts" deals with the adapters, and "scsi" the devices. An awful lot of the middle layer adapting seems to be handling errors and timeouts. As SCSI is a bus that can connect all kinds of weird peripherals and fail in all kinds of exciting ways, I guess this makes sense. The SCSI code has a lot more debugging (kprintf) code than other subsystems.

Once again, the code reminds me of how basic C makes it difficult to ensure invariants are met, and keep reasonable abstractions. For example, there are several places where the runnabilty of the current process is changed in different ways, directly, rather than using standardised sleep/wait mechanisms. And error handling... ugh. So easy to leak resources in little-used error paths.

I sometimes think "How could the code be structured better?". There are some core multi-hundred-line functions that could be broken up. Given some operations span over multiple functions through completion functions etc., I think there could be explicit state machines. Mostly though... yeah, I want invariants and abstractions. I want the code to be obviously correct, not mysterious. Doesn't seem too easy to me. :/

And that brings us to the end of the device-independent part. Next up, I'll be working through that, but I imagine it'll be pretty dull, with just some tedious register mangling. We'll see, shan't we?

Posted 2018-08-12.