/* * jvdg_usbgadget driver as kernel module - sysfs interface * * Copyright (C) 2011 Julien Viard de Galbert * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, version 2. * * written using those tutorials as support: * - http://linuxdevcenter.com/pub/a/linux/2007/07/05/devhelloworld-a-simple-introduction-to-device-drivers-under-linux.html by Valerie Henson * - http://www.linuxjournal.com/article/7353 by Greg Kroah-Hartman */ #define DEBUG #include #include #include #include /* Ids form our USBTiny config */ #define VENDOR_ID 0x6666 #define PRODUCT_ID 0x0001 /* Commands from our USB device AVR code */ enum { // Generic requests USBTINY_ECHO, // echo test USBTINY_READ, // read byte USBTINY_WRITE, // write byte USBTINY_CLR, // clear bit USBTINY_SET, // set bit // USBTINY_KEY_READ // read key status }; /* table of devices that work with this driver */ static struct usb_device_id id_table [] = { { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, { }, }; MODULE_DEVICE_TABLE (usb, id_table); struct jvdg_usbg { struct usb_device * udev; unsigned char leds; unsigned char keys; }; static void change_leds(struct jvdg_usbg *dev) { int retval; unsigned char *buffer; buffer = kmalloc(8, GFP_KERNEL); if (!buffer) { dev_err(&dev->udev->dev, "out of memory\n"); return; } dev_dbg(&dev->udev->dev, "leds = %.2x\n", dev->leds); retval = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), USBTINY_WRITE, 0xc0, dev->leds, 1, buffer, 0, 2000); if (retval) dev_dbg(&dev->udev->dev, "retval = %d\n", retval); kfree(buffer); } static void read_leds(struct jvdg_usbg *dev) { int retval; unsigned char *buffer; buffer = kmalloc(8, GFP_KERNEL); if (!buffer) { dev_err(&dev->udev->dev, "out of memory\n"); return; } retval = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), USBTINY_READ, 0xc0, 0, 0, buffer, 1, 2000); if (retval) dev_dbg(&dev->udev->dev, "retval = %d\n", retval); dev->leds = buffer[0]; dev_dbg(&dev->udev->dev, "leds = %.2x\n", dev->leds); kfree(buffer); } static void read_keys(struct jvdg_usbg *dev) { int retval; unsigned char *buffer; buffer = kmalloc(8, GFP_KERNEL); if (!buffer) { dev_err(&dev->udev->dev, "out of memory\n"); return; } retval = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), USBTINY_KEY_READ, 0xc0, 0, 0, buffer, 1, 2000); if (retval) dev_dbg(&dev->udev->dev, "retval = %d\n", retval); dev->keys = buffer[0]; dev_dbg(&dev->udev->dev, "keys = %.2x\n", dev->keys); kfree(buffer); } static ssize_t show_leds(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct jvdg_usbg *led = usb_get_intfdata(intf); read_leds(led); return sprintf(buf, "%d\n", led->leds); } static ssize_t set_leds(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct usb_interface *intf = to_usb_interface(dev); struct jvdg_usbg *led = usb_get_intfdata(intf); int temp = simple_strtoul(buf, NULL, 0); led->leds = temp & 0xf; change_leds(led); return count; } static DEVICE_ATTR(leds, S_IRUGO | S_IWUSR, show_leds, set_leds); static ssize_t show_keys(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); struct jvdg_usbg *keys = usb_get_intfdata(intf); read_keys(keys); return sprintf(buf, "%d\n", keys->keys); } static DEVICE_ATTR(keys, S_IRUGO, show_keys, NULL); static int jvdg_usbg_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_device *udev = interface_to_usbdev(interface); struct jvdg_usbg *dev = NULL; int retval = -ENOMEM; dev = kzalloc(sizeof(struct jvdg_usbg), GFP_KERNEL); if (dev == NULL) { dev_err(&interface->dev, "Out of memory\n"); goto error_mem; } dev->udev = usb_get_dev(udev); usb_set_intfdata (interface, dev); retval = device_create_file(&interface->dev, &dev_attr_leds); if (retval) goto error; retval = device_create_file(&interface->dev, &dev_attr_keys); if (retval) goto error; dev_info(&interface->dev, "jvdg USBGadget device now attached\n"); return 0; error: device_remove_file(&interface->dev, &dev_attr_leds); device_remove_file(&interface->dev, &dev_attr_keys); usb_set_intfdata (interface, NULL); usb_put_dev(dev->udev); kfree(dev); error_mem: return retval; } static void jvdg_usbg_disconnect(struct usb_interface *interface) { struct jvdg_usbg *dev; dev = usb_get_intfdata (interface); device_remove_file(&interface->dev, &dev_attr_leds); device_remove_file(&interface->dev, &dev_attr_keys); /* first remove the files, then set the pointer to NULL */ usb_set_intfdata (interface, NULL); usb_put_dev(dev->udev); kfree(dev); dev_info(&interface->dev, "jvdg USBGadget now disconnected\n"); } static struct usb_driver jvdg_usbg_driver = { .name = "jvdg_usbgadget", .probe = jvdg_usbg_probe, .disconnect = jvdg_usbg_disconnect, .id_table = id_table, }; static int __init jvdg_usbg_init(void) { int retval = 0; retval = usb_register(&jvdg_usbg_driver); if (retval) err("usb_register failed. Error number %d", retval); return retval; } static void __exit jvdg_usbg_exit(void) { usb_deregister(&jvdg_usbg_driver); } module_init (jvdg_usbg_init); module_exit (jvdg_usbg_exit); MODULE_AUTHOR("Julien Viard de Galbert "); MODULE_DESCRIPTION("JVdG USB Gadget Driver"); MODULE_LICENSE("GPL");