As my Chumby 8 kernel upgrade project neared the finish line (read parts 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 first if you want), I noticed something subtly annoying. The built-in SD/CF card reader was allocating its own dummy block device (/dev/sda) even if no cards were inserted.

scsi 0:0:0:0: Direct-Access Multi Flash Reader 1.00 PQ: 0 ANSI: 0
sd 0:0:0:0: [sda] Media removed, stopped polling
sd 0:0:0:0: [sda] Attached SCSI removable disk

# ls /sys/class/block/
mmcblk0 mmcblk0p1 mmcblk0p2 mmcblk0p3 sda

This may seem nitpicky, but I really didn’t want that empty sda device hanging around. Apparently neither did the original Chumby team, because the old 2.6.28 kernel had a workaround to avoid it. Let’s look at the relevant portion of the schematics. There are two sockets, and the SD/MMC/MS/xD socket has a bunch of pins for different card types, so the card reader takes up two whole pages. Let’s focus on just the SD/MMC pins. You can imagine that the CF/MS/xD sections look very similar:

Each socket has a detection pin that tells you if a card is inserted. In the socket above, it’s the “SDCDN” pin. These card detect pins, along with data and control pins, go back to the main card reader IC, which is an Alcor Micro AU6350. It’s a 3-port USB hub and SD/CF/xD/MS card reader all in one. This was pretty nice for use in the Chumby because it was a single-chip solution for a lot of the external expansion: both USB ports and all of the card readers.

The AU6350 has inputs for all of the different card detect pins, but it doesn’t seem to actually use them to decide whether or not to advertise itself as a USB block device. I think they are more for telling it which type of media is currently inserted. The CONTROLOUT and CARDDATA pins are shared between the different memory card sockets, so only one type of card can be used at a time. It needs to know which one to talk to.

Luckily, Chumby also brought these same detection pins into the PXA166, so we can see in software which type of card, if any, is plugged in. They go to GPIO pins 100 through 103.

The old Chumby kernel used a fairly hacky mechanism to fix the “dummy /dev/sda” problem. It modified the USB storage driver’s probe function to fail when detecting the AU6350 if none of the card reader ports had a card inserted:

#ifdef CONFIG_CHUMBY_SILVERMOON_MEDIA
    /*
     * If this is the special media card that's attached to a Silvermoon
     * board, and if no media is actually inserted, fail.
     * There's got to be a better palce to put this.
     */
    if(0x058f==le16_to_cpu(interface_to_usbdev(intf)->descriptor.idVendor)
    && 0x6366==le16_to_cpu(interface_to_usbdev(intf)->descriptor.idProduct)
    ) {
        if(!silvermoon_media_inserted()) {
                        release_everything(host_to_us(host));
                        return -ENXIO;
        }
    }
#endif

Hey, it did its job! In addition, the PXA EHCI driver was modified to add GPIO interrupts on all of the card detect pins. When the pins changed, it alerted a program running in userspace so it could reset the port to cause the kernel to redetect, and possibly ignore, the card reader device.

I wanted to do something similar to the original kernel modification, but just like last time with the RTC emulation, I wanted to keep as much of the logic in userspace as I could. Ideally there would be a way to hook up the card detection pins through device tree and let the kernel do it, but since USB is a self-describing bus, I’m not sure exactly how that would work. It doesn’t seem like it’s currently possible to make connections between internal GPIO pins and USB devices in the device tree. This is another situation where userspace seemed like the most appropriate place for a solution.

The problem was, how could I prevent the kernel from creating an empty /dev/sda device without adding an icky hack to a driver? It turns out that the usb-storage driver has a useful “quirks” kernel command line parameter that can be used to, among many other things, tell it to ignore devices by their USB vendor and product ID. With that, a strategy popped into my mind:

  1. In U-Boot, add a quirk for the Alcor/AU6350 USB VID/PID combination to tell the usb-storage driver to ignore it.
  2. After the kernel is booted, run a userspace daemon that removes the quirk and manually connects and disconnects the card reader device based on the detection pins.

This sounded pretty easy and clean enough. I added the quirk to my U-Boot fork’s default Linux command line:

usb-storage.quirks=058f:6366:i

This tells the usb-storage driver to ignore (“i”) a storage device if it detects USB vendor ID 0x058f and product ID 0x6366. Next, I wrote a simple daemon in C that deals with the card detect GPIO pins. As a quick summary of what it does:

  1. Sets up GPIO interrupts for all of the card detect pins.
  2. Removes the quirk that U-Boot added. This is done by modifying the value of /sys/module/usb_storage/parameters/quirks. The parameter is a comma-separated list of quirks, so I just have to remove the “058f:6366:i” one from the list. Realistically it’s always going to be the only quirk in the list unless someone has added another one by hand.
  3. Reads (and debounces) the card detect pins.
  4. Binds the card reader USB interface to the usb_storage driver if there is a card inserted. This is done by writing the interface to /sys/bus/usb/drivers/usb-storage/bind. The reason I have to do this is because the quirk was previously preventing the interface from binding to the driver.
  5. Connects or disconnects the USB card reader device using the /sys/bus/usb/devices/1-1.4/authorized file.
  6. In an infinite loop, waits for the next change to the card detect pins, debounces them, and connects or disconnects the USB device as needed.

I ended up being correct about this being easy. My daemon just consists of some basic calls to libgpiod and a few reads and writes to sysfs. sysfs is extremely flexible and provides all kinds of useful functionality accessible through userspace, such as modifying kernel module parameters at runtime and authorizing/deauthorizing USB devices. Out of pure laziness, I did use the system() function in a few places for writes to sysfs to make things easy for me. I know this isn’t necessarily the greatest practice in the world, but I’m not doing anything performance-critical and all the times I call it, the command being executed is entirely a compile-time constant.

This is one of the few tasks during this project that pretty much went without a hitch. This kernel upgrade wasn’t entirely a crazy struggle! But speaking of struggles, I’d like to talk about the UART driver in my next post in the series. I originally ran into a UART issue in the kernel when I was first getting started with this project, but I kind of brushed it aside and didn’t even mention it in these posts. I was recently forced to revisit the UART when Linux 6.9 came out.

Trackback

1 comment

  1. Thanks for sharing the next post in this series!

Add your comment now