I Dream of a Cheeky Missile Launcher

I read a blog post by Matthias Vallentin a while back about getting a USB missile launcher working and thought that a similar gadget would be a nice candidate for the 2012 refresh of our successful EL503 Developing for Embedded Linux course so ordered a nice looking piece of hardware from Amazon – the Thunder Missile Launcher from Dream Cheeky.

Sadly these guys don’t provide Linux drivers and the hardware wasn’t an exact match for the launcher used in Matthias’ article so I had to do a bit of research.

Normally it would be a case of firing up my Windows partition and using a USB Sniffer but I didn’t want to shutdown my system as I was in the middle of quite a few projects so I hit the net and stumbled across the Retaliation project which used the Thunder Launcher to punish people who broke a CI build and a blog piece by Nathan Milford which had the requisite USB command codes but had programs that leveraged libUSB from userspace.

So now I knew what I had to feed my launcher and had some good resources on Linux USB[1] [2] [3] now I could start!

Echoing Matthias’ steps – I created a simple core driver that would print info out in the probe

/* Rocket launcher specifics */
#define LAUNCHER_VENDOR_ID 0x2123
#define LAUNCHER_PRODUCT_ID 0x1010
#define LAUNCHER_NODE "launcher"

static struct usb_class_driver class;

/* Table of devices that work with this driver */
static struct usb_device_id launcher_table[] =
{
{ USB_DEVICE(LAUNCHER_VENDOR_ID, LAUNCHER_PRODUCT_ID) },
{} /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, launcher_table);

static struct usb_driver launcher_driver =
{
.name = "launcher_driver",
.id_table = launcher_table,
};

static int launcher_open(struct inode *inodep, struct file *filp)
{
int retval = 0
pr_debug("launcher_open\n");
/* Save our object in the file's private structure. */
/* filp->private_data = dev; */
return retval;
}

static int launcher_close(struct inode *inodep, struct file *filp)
{
int retval = 0;
pr_debug("launcher_close\n");
/* We can get private data from filp->private_data; */
return retval;
}

static ssize_t launcher_read(struct file *f, char __user *buf, size_t cnt,
loff_t *off)
{
int retval = -EFAULT;
pr_debug("launcher_read\n");
return retval;
}

static ssize_t launcher_write(struct file *filp, const char __user *user_buf,
size_t count, loff_t *off)
{
int retval = -EFAULT;
pr_debug("launcher_write\n");
/* We can get private data from filp->private_data; */
return retval;
}

static struct file_operations fops =
{
.open = launcher_open,
.release = launcher_close,
.read = launcher_read,
.write = launcher_write,
};

static int launcher_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(interface);
struct usb_host_interface *iface_desc;
int retval = -ENODEV;

pr_debug("launcher_probe\n");

/* Set up our class */
class.name = LAUNCHER_NODE"%d";
class.fops = &fops;

if ((retval = usb_register_dev(interface, &class)) minor);
}
/* This will be our private interface device data instead of NULL eventually */
/* usb_set_intfdata(interface, NULL); */
return retval;
}

static void launcher_disconnect(struct usb_interface *interface)
{
pr_debug("launcher_disconnect\n");
/* Give back our minor. */
usb_deregister_dev(interface, &class);
}

static int __init launcher_init(void)
{
int result;
pr_debug("launcher_disconnect\n");

/* Wire up our probe/disconnect */
launcher_driver.probe = launcher_probe;
launcher_driver.disconnect = launcher_disconnect;

/* Register this driver with the USB subsystem */
if ((result = usb_register(&launcher_driver))) {
pr_err("usb_register() failed. Error number %d", result);
}
return result;
}

static void __exit launcher_exit(void)
{
/* Deregister this driver with the USB subsystem */
usb_deregister(&launcher_driver);
}

module_init(launcher_init);
module_exit(launcher_exit);

I compiled this with the makefile, inserted the module and saw that my init call had successfully been called so I popped the launchers USB into my laptop and…. nothing.

I checked dmesg and saw it had been claimed by hid-generic, part of the usbhid module on my laptop!


[205211.636201] usb 2-1.2: >USB disconnect, device number 23
[205214.371289] usb 2-1.2: >new low-speed USB device number 24 using ehci_hcd
[205214.462969] usb 2-1.2: >New USB device found, idVendor=2123, idProduct=1010
[205214.462979] usb 2-1.2: >New USB device strings: Mfr=1, Product=2, SerialNumber=0
[205214.462985] usb 2-1.2: >Product: USB Missile Launcher
[205214.462990] usb 2-1.2: >Manufacturer: Syntek
[205214.468668] hid-generic 0003:2123:1010.000B: >hiddev0,hidraw0: USB HID v1.10 Device [Syntek USB Missile Launcher] on usb-0000:00:1d.0-1.2/input0

To get hidusb to release it, you can tell it to unbind using sysfs.


[nick@slimtop ~]$ ls /sys/bus/usb/drivers/usbhid/
2-1.2:1.0 bind module new_id remove_id uevent unbind
^^^ Our address
[nick@slimtop ~] sudo sh -c 'echo 2-1.2:1.0 > /sys/bus/usb/drivers/usbhid/unbind'
[nick@slimtop ~]$ ls
bind module new_id remove_id uevent unbind

To get it to bind to our driver we would have to poke it manually

[nick@slimtop ~]$ sudo sh -c 'echo "2-1.2:1.0" > /sys/bus/usb/drivers/launcher_driver/bind'

Checking dmesg again showed that the probe function had been called and we had our /dev/launcher0!

I now needed to update the information expected by the driver. The original launcher used by Matthias’ had different codes for most of the commmands so using the userspace information we could update the command codes and tweak the control codes.

#define LAUNCHER_NODE "launcher"
#define LAUNCHER_CTRL_BUFFER_SIZE 8
#define LAUNCHER_CTRL_REQUEST_TYPE 0x21
#define LAUNCHER_CTRL_REQUEST 0x09
#define LAUNCHER_CTRL_VALUE 0x0
#define LAUNCHER_CTRL_INDEX 0x0
#define LAUNCHER_CTRL_COMMAND_PREFIX 0x02

#define LAUNCHER_STOP 0x20
#define LAUNCHER_UP 0x02
#define LAUNCHER_DOWN 0x01
#define LAUNCHER_LEFT 0x04
#define LAUNCHER_RIGHT 0x08
#define LAUNCHER_UP_LEFT (LAUNCHER_UP | LAUNCHER_LEFT)
#define LAUNCHER_DOWN_LEFT (LAUNCHER_DOWN | LAUNCHER_LEFT)
#define LAUNCHER_UP_RIGHT (LAUNCHER_UP | LAUNCHER_RIGHT)
#define LAUNCHER_DOWN_RIGHT (LAUNCHER_DOWN | LAUNCHER_RIGHT)
#define LAUNCHER_FIRE 0x10

Our USB command code takes the form of:
REQUEST_TYPE | REQUEST | VALUE | INDEX | DATA
where DATA is an 8 byte buffer with the first set to 0x8, our COMMAND_PREFIX.

For example, to send a fire command (0x10) we would use a usb_control_msg like this

int usb_control_msg ( struct usb_device *dev,
unsigned int pipe,
0x09, // Request
0x21, // Request Type
0x0, // Value
0x0, // Index
&buffer, // Buffer
sizeof(buffer), // Length of buffer
int timeout);

where buffer would be a blank char buffer of 8 bytes with the first and second bytes set to the COMMAND_PREFIX and command code respectively which you can see in this snipped from our launcher_write() function

...
if (copy_from_user(&cmd, user_buf, count)) {
retval = -EFAULT;
goto unlock_exit;
}

/* Prepare the buffer, noting the prefix */
memset(&buf, 0, sizeof(buf));
buf[0] = LAUNCHER_CTRL_COMMAND_PREFIX;
buf[1] = cmd;

/* The interrupt-in-endpoint handler also modifies dev->command. */
spin_lock(&dev->cmd_spinlock);
dev->command = cmd;
spin_unlock(&dev->cmd_spinlock);

pr_debug("Sending usb_control_message()\n");
retval = usb_control_msg(dev->udev,
usb_sndctrlpipe(dev->udev, 0),
LAUNCHER_CTRL_REQUEST,
LAUNCHER_CTRL_REQUEST_TYPE,
LAUNCHER_CTRL_VALUE,
LAUNCHER_CTRL_INDEX,
&buf,
sizeof(buf),
HZ*5);
...

I won’t cover too much on the USB code as it is covered in far more depth in Matthias’ article and Greg KHs usb-skeleton.c in the kernel source tree.

To control our driver, we can send bytes from userspace to the /dev/launcher0 entry and I was able to repurpose Matthias’ launcher program to send the correct command codes to the device which I have added to the Git repository. When you create the executable and module using make, and assuming you have the right libraries, you will have a launcher_driver.ko and a launcher_control executable.

Insert the kernel module using insmod ./launcher_driver.ko and you can then start issuing commands to the launcher.

Unfortunately by default, the device node /dev/launcher0 is created as root:root with permission 0600 which means that you can either chmod/chgrp it or run the launcher as root(!) which is the simplest test:

sudo ./launcher_control -f

usbhid and the Handsy Driver

As we’ve looked at, usbhid tries to claim our device by default due to it looking like a Human Interface Device and I had to do a fair amount of digging to find out how to stop usbhid from claiming our device which I’ll recap for those who seek enlightenment.

If you look in the source code for /drivers/usb/usbhid/hid-quirks.c we can see a structure that defines a blacklist of HID devices.

static const struct hid_blacklist {
__u16 idVendor;
__u16 idProduct;
__u32 quirks;
} hid_blacklist[] = {
{ USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD, HID_QUIRK_BADPAD },
{ USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD },
{ USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD },
...
{ USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
...

This shows a format of VENDOR_ID, DEVICE_ID and the HID_QUIRK mode which can be multiple values OR’d together and if we look further down the source, we can see the following function:

int usbhid_quirks_init(char **quirks_param)
{
u16 idVendor, idProduct;
u32 quirks;
int n = 0, m;
for (; n < MAX_USBHID_BOOT_QUIRKS && quirks_param[n]; n++) {
m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
&idVendor, &idProduct, &quirks);
if (m != 3 ||
usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {
printk(KERN_WARNING
"Could not parse HID quirk module param %s\n",
quirks_param[n]);
}
}
return 0;
}

Which is an array passed in from hid-core.c in the following snippet:

/* Quirks specified at module load time */
static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying "
" quirks=vendorID:productID:quirks"
" where vendorID, productID, and quirks are all in"
" 0x-prefixed hex");

This shows us that the quirk param values are read in the form of 0xVENDORID:0xPRODUCTID:0xQUIRKS so now we know how to get usbhid to use a quirk for our device, we need to know which one.

Defined in include/linux/hid.h are our quirks, we want to tell usbhid to just leave it alone so we’re going to use HID_QUIRK_IGNORE which has the value of 0x04.

Once we have this information we should be able to modprobe -r usbhid && modprobe usbhid quirks=0x2123:0x1010:0x04 … except that we can’t remove the module on our Fedora system…

[nick@slimtop ~]$ modprobe -r usbhid
FATAL: Module usbhid is builtin.

Oh dear. This means that the usbhid module isn’t loaded at runtime, instead it has been built into the kernel so we can’t change it from userspace as the kernel, with usbhid, is already running before the filesystem has been mounted so we will need to pass the parameter to the kernel in the form:
module.parameter=OPTIONS.
To do this, I edited my kernel command in /boot/grub2/grub.cfg to tell the builtin module about our device and add it to the quirks list.


...
echo 'Loading Fedora (3.6.2-4.fc17.i686)'
linux /vmlinuz-3.6.2-4.fc17.i686 root=/dev/mapper/vg_slimtop-lv_root ro rd.md=0 rd.dm=0 rd.lvm.lv=vg_slimtop/lv_root SYSFONT=True usbhid.quirks=0x2123:0x1010:0x04 KEYTABLE=uk rd.luks=0 rd.lvm.lv=vg_slimtop/lv_swap LANG=en_US.UTF-8 rhgb quiet
...

Finally, we can get our kernel module to load automatically by copying our launcher_driver.ko into the /lib/modules/`uname -r`/ directory and running depmod -a.

On rebooting, you should be able to insert your launcher and up will pop a /dev/launcher0 without usbhid getting handsy with it.

udev

udev is the device manager for the Linux kernel. Primarily, it manages device nodes in /dev so we’re going to use that to manipulate the creation of our /dev/launcher0 entry.

There is a lot of information spread over the net on udev but it’s sometimes out of date and no longer relevant. It’s best to ask questions in IRC, copy existing rules (see /usr/lib/udev/rules.d/*) or just read the source to get the most up-to-date information.

To avoid having to run our launcher as root we can create a special rule for udev[1] to manipulate how the node is created. To do this you will need to create a new file in /etc/udev/rules.d/10-dreamcheeky.rules containing the following:


KERNEL=="launcher?*",MODE="0666",GROUP="wheel"

This tells udev that we want any events from the KERNEL that match /dev/”launcher?*” which is what our driver requests (the regular expression matches any number after our launcher string, such as 1,2,3 etc).
When that event KEY has been matched, we apply the two ASSIGN values which specify the permissions mode, 0666 or rw-rw-rw-, and that it should belong to the wheel group.

To test our rule, we need to know how udev sees our USB device so we do the following:

[nick@slimtop ~]$ find /sys/devices/ -name launcher0
/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0

To test our udev rule we run udevadm test as follows against our device path

[nick@slimtop rules.d]$ sudo udevadm test /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0
run_command: calling: test
adm_test: version 182
This program is for debugging only, it does not run any program,
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.

builtin_kmod_init: load module index
...
parse_file: reading '/etc/udev/rules.d/10-dreamcheeky.rules' as rules file <--- OUR RULES FILE
...
parse_file: reading '/usr/lib/udev/rules.d/99-systemd.rules' as rules file
udev_rules_new: rules use 249228 bytes tokens (20769 * 12 bytes), 42425 bytes buffer
udev_rules_new: temporary index used 76720 bytes (3836 * 20 bytes)
udev_device_new_from_syspath: device 0xb9009810 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0'
udev_device_new_from_syspath: device 0xb9009b00 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0'
udev_device_read_db: device 0xb9009b00 filled with db file data
udev_rules_apply_to_event: GROUP 10 /etc/udev/rules.d/10-dreamcheeky.rules:1
udev_rules_apply_to_event: MODE 0666 /etc/udev/rules.d/10-dreamcheeky.rules:1
udev_device_new_from_syspath: device 0xb901a540 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0'
udev_device_new_from_syspath: device 0xb901a088 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2'
udev_device_new_from_syspath: device 0xb90239b0 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2/2-1'
udev_device_new_from_syspath: device 0xb9023cb0 has devpath '/devices/pci0000:00/0000:00:1d.0/usb2'
udev_device_new_from_syspath: device 0xb9024000 has devpath '/devices/pci0000:00/0000:00:1d.0'
udev_device_new_from_syspath: device 0xb901f220 has devpath '/devices/pci0000:00'
udev_device_read_db: no db file to read /run/udev/data/+usb:2-1.2:1.0: No such file or directory
udev_node_add: handling device node '/dev/launcher0', devnum=c180:0, mode=0666, uid=0, gid=10
node_fixup: preserve permissions /dev/launcher0, 020666, uid=0, gid=10
node_symlink: preserve already existing symlink '/dev/char/180:0' to '../launcher0'
udev_device_update_db: created empty file '/run/udev/data/c180:0' for '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0'
ACTION=add
DEVNAME=/dev/launcher0
DEVPATH=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/usbmisc/launcher0
MAJOR=180
MINOR=0
SUBSYSTEM=usbmisc
UDEV_LOG=6
USEC_INITIALIZED=684838211
builtin_kmod_exit: unload module index

That’s it – you should be able to remove/insert your USB device and have /dev/launcher0 show up with the permissions and group you chose.

Conclusion

This has been a very long blog article but I hope this is helpful and serves to tie together a lot of good resources that seem spread thinly across the net so have fun and experiment!

The code for the userspace and driver is available on our Feabhas Github at https://github.com/feabhas/dreamlauncher with a single Makefile to build both projects.

I built this on Fedora 17 so your mileage may vary depending on your distribution/environment.

[1] Special thanks go to the #udev IRC channel on freenode (in particular the user falconindy) and these two resource pages:

Dislike (0)
This entry was posted in C/C++ Programming, General, Testing, training and tagged , , , . Bookmark the permalink.

2 Responses to I Dream of a Cheeky Missile Launcher

  1. Anonymous says:

    Hi there, just wanted to tell you, I enjoyed this blog post. It was helpful. Keep on posting!

    Like (0)
    Dislike (0)
  2. Pingback: Linux Dream Cheeky USB Thunder Missile Launcher » More Zonkyness | More Zonkyness

Leave a Reply