Linux Driver for the
RadioShack 12-258 S.A.M.E. Weather Radio

Moving to the Unix Domain

Unable to make much sense of what I saw in the captured USB communication, I found a Perl script that takes the output of usbsnoopy and turns it into C source code that essentially plays back the captured messages using libusb. libusb is a brilliant library available for Linux that allows device drivers to be written in user space instead of as kernel modules. I purchased and read 3 consecutive revisions of the O'Reilly book Linux Device Drivers 3rd edition by Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman, without ever getting very far. Writing kernel drivers from scratch requires a pretty big investment in time and has a steep learning curve. libusb lowers that bar substantially.

Installing libusb (and libusb-dev) on any modern Linux, if not already present, should be trivial but is beyond the scope of this project description. Building and running the generated C code is also beyond the scope of this writing. usbsnoop2libusb can be downloaded from http://lindi.iki.fi/lindi/usb/usbsnoop2libusb.pl

Here is what it produces from a version of the usbsnoopy captured log.

/* This file is generated with usbsnoop2libusb.pl from a usbsnoop log file. */
/* Latest version of the script should be in http://iki.fi/lindi/usbsnoop2libusb.pl */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <ctype.h>
#include <usb.h>
#if 0
 #include <linux/usbdevice_fs.h>
 #define LIBUSB_AUGMENT
 #include "libusb_augment.h"
#endif

struct usb_dev_handle *devh;

void release_usb_device(int dummy) {
    int ret;
    ret = usb_release_interface(devh, 0);
    if (!ret)
        printf("failed to release interface: %d\n", ret);
    usb_close(devh);
    if (!ret)
        printf("failed to close interface: %d\n", ret);
    exit(1);
}

void list_devices() {
    struct usb_bus *bus;
    for (bus = usb_get_busses(); bus; bus = bus->next) {
        struct usb_device *dev;

        for (dev = bus->devices; dev; dev = dev->next)
            printf("0x%04x 0x%04x\n",
                   dev->descriptor.idVendor,
                   dev->descriptor.idProduct);
    }
}

struct usb_device *find_device(int vendor, int product) {
    struct usb_bus *bus;

    for (bus = usb_get_busses(); bus; bus = bus->next) {
        struct usb_device *dev;

        for (dev = bus->devices; dev; dev = dev->next) {
            if (dev->descriptor.idVendor == vendor
                && dev->descriptor.idProduct == product)
                return dev;
        }
    }
    return NULL;
}

void print_bytes(char *bytes, int len) {
    int i;
    if (len > 0) {
        for (i=0; i<len; i++) {
            printf("%02x ", (int)((unsigned char)bytes[i]));
        }
        printf("\"");
        for (i=0; i<len; i++) {
            printf("%c", isprint(bytes[i]) ? bytes[i] : '.');
        }
        printf("\"");
    }
}


int main(int argc, char **argv) {
    int ret, vendor, product;
    struct usb_device *dev;
    char buf[65535];
#if 0
    usb_urb *isourb;
    struct timeval isotv;
    char isobuf[32768];
#endif

    usb_init();
    usb_set_debug(255);
    usb_find_busses();
    usb_find_devices();

    if (argc!=3) {
        printf("usage: %s vendorID productID\n", argv[0]);
        printf("ID numbers of currently attached devices:\n");
        list_devices();
        exit(1);
    }
    vendor = strtol(argv[1], NULL, 16);
    product = strtol(argv[2], NULL, 16);
    if (vendor <= 1 || product <= 1) {
        printf("invalid vendor or product id\n");
        exit(1);
    }
    dev = find_device(vendor, product);
    assert(dev);

    devh = usb_open(dev);
    assert(devh);

    signal(SIGTERM, release_usb_device);

    ret = usb_claim_interface(devh, 0);
    if (ret != 0) {
        printf("claim failed with error %d\n", ret);
                exit(1);
    }

    ret = usb_set_altinterface(devh, 0);
    assert(ret >= 0);

ret = usb_get_descriptor(devh, 0x0000001, 0x0000000, buf, 0x0000012);
printf("1 get descriptor returned %d, bytes: ", ret);
print_bytes(buf, ret);
printf("\n");
ret = usb_get_descriptor(devh, 0x0000002, 0x0000000, buf, 0x0000009);
printf("2 get descriptor returned %d, bytes: ", ret);
print_bytes(buf, ret);
printf("\n");
usleep(5*1000);
ret = usb_get_descriptor(devh, 0x0000002, 0x0000000, buf, 0x0000022);
printf("3 get descriptor returned %d, bytes: ", ret);
print_bytes(buf, ret);
printf("\n");
usleep(8*1000);
ret = usb_release_interface(devh, 0);
if (ret != 0) printf("failed to release interface before set_configuration: %d\n", ret);
ret = usb_set_configuration(devh, 0x0000001);
printf("4 set configuration returned %d\n", ret);
ret = usb_claim_interface(devh, 0);
if (ret != 0) printf("claim after set_configuration failed with error %d\n", ret);
ret = usb_set_altinterface(devh, 0);
printf("4 set alternate setting returned %d\n", ret);
/*
usleep(23*1000);
ret = usb_control_msg(devh, USB_TYPE_CLASS + USB_RECIP_INTERFACE, 0x000000a, 0x0000000, 0x0000000, buf, 0x0000000, 1000);
printf("5 control msg returned %d", ret);
printf("\n");
usleep(2*1000);
ret = usb_get_descriptor(devh, 0x0000022, 0x0000000, buf, 0x0000062);
printf("6 get descriptor returned %d, bytes: ", ret);
print_bytes(buf, ret);
printf("\n");
usleep(10*1000);
*/
ret = usb_interrupt_read(devh, 0x00000081, buf, 0x0000008, 1000);
printf("7 interrupt read returned %d, bytes: ", ret);
print_bytes(buf, ret);

// many lines deleted here
// this goes on for pages...

printf("\n");
        ret = usb_release_interface(devh, 0);
        assert(ret == 0);
        ret = usb_close(devh);
        assert(ret == 0);
        return 0;
}

The beauty of this approach is that it is very easy to modify the C code in Linux, run it and see what happens. With a bit of luck and perseverence the important commands can be discovered without really understanding what they mean or how they work. Of course, I'd prefer to really understand what is happening, but this is a good first step.


<<Previous Page | Index | Next Page>>