top | item 30679923

Writing a device driver for Unix V6

133 points| vegesm | 4 years ago |mveg.es | reply

9 comments

order
[+] yjftsjthsd-h|4 years ago|reply
Absolutely fantastic:) I've rather fallen into the trap of thinking of kernel code, including drivers, as being some sort of deep magic beyond human comprehension, but this makes it seem utterly straightforward; map a chunk of memory in the "hardware", map the memory in the OS, wire it up, use that to communicate. Is this how it works on newer OSs (*BSD, Linux, Illumos)? Are there equivalent tutorials for any modern FOSS *nix?
[+] gmueckl|4 years ago|reply
A device driver is program code like any other, so there's no way it can be magic. Whether it's simple or complex depends on a couple of things. Some hardware maps registers into memory and you communicate it by reading from/writing to these addresses. This is very common in simpler architectures like microcontrollers. Other times, your hardware is attached to shared system busses like PCI or USB and you need to go through driver layers for them. Of course, there's the occasional bit of quirky hardware that requires that the driver does some odd things, but even that isn't magic if you understand the device hardware.

I think the biggest source of complexity for device drivers is the expectations that come with the design of the operating system the driver should run on. If there are e.g. strong expectations that device drivers don't block for certain operations or keep certain locks for extended amounts of time, then the driver may gain a lot of complexity from trying to play nice. Modern optimized desktop and server operating system are somewhat guilty of forcing that kind of complexity onto device drivers.

[+] antoinealb|4 years ago|reply
At my previous job I had to write a device driver for a (simple) I2C device. I already had experience writing drivers for microcontrollers, but to move to Linux I found it was pretty simple to take the code for an existing device that was similar to what I wanted to do and use it as a reference. If you look at the code for a simple serial port [1], with the knowledge gained in the article, you would probably figure out what are the different parts.

[1] https://github.com/torvalds/linux/blob/master/drivers/tty/se...

[+] matheusmoreira|4 years ago|reply
> Is this how it works on newer OSs

It can be. Not all devices will use memory-mapped I/O. USB devices, for example, use message passing. Messages are packets called USB Request Blocks, or URBs.

https://www.kernel.org/doc/html/latest/driver-api/usb/URB.ht...

A USB device driver is just a program that sends and receives theses messages. There are standardized device classes which enable generic drivers. Human interface devices, for example, will most likely be handled by a generic driver which can deal with any device of that class.

Hardware manufacturers can still add custom functionality on top of a standard interface, usually through control messages. A USB keyboard with LEDs, for example. All the key events are handled by generic drivers, I only had to figure out the LED control interface.

I used wireshark to reverse engineer it. Captured the USB traffic, saw a lot of control packets and correlated all of them with the proprietary manufacturer software's functions. With this data, it was simple to write a user space Linux driver for my keyboard.

https://wiki.wireshark.org/USB

The manufacturer's program would send a control message containing a payload, essentially bytes instructing the hardware what to do. For example, to set the color of a specific LED, my program sends this buffer to the hardware:

  const unsigned char report[] = {
    0xCC,     /* Some kind of prefix? Always present in all payloads. */
    0x01,     /* Set LED color command. */
    led,      /* LED index, some keys have multiple LEDs. */
    r, g, b,  /* RGB8 color of the LED. */
    0x7F      /* Some kind of suffix? Always present in all payloads. */
  };

  hid_send_feature_report(keyboard, report, sizeof(report));
To turn off all LEDs and clear all configuration:

  const unsigned char report[] = {
    0xCC,
    0x00, 0x0C,
    0x00, 0x00, 0x00,
    0x7F
  };
> I've rather fallen into the trap of thinking of kernel code, including drivers, as being some sort of deep magic beyond human comprehension

There certainly is a lot of deep magic. Often because dealing with hardware is an ugly business. I've read Linux drivers which actually work around literal defects in the hardware. For example, I remember a bit of special code for a button that inexplicably sent two press events every time it was pushed. I wouldn't even have known that was the case were it not for the extremely helpful comments left by the developer.

In my case, I discovered the proprietary driver from the manufacturer was intercepting all of my keystrokes in order to support a special mode where LEDs light up when you press a key. It boggles my mind to this day, why couldn't they have done that in hardware?

[+] johnny22|4 years ago|reply
there are entire books and tutorials about writing drivers for linux, or even how to do a mini OS from scratch. So many i can't even remember them all.