usb 分成四種裝置
- Control
- Interrupt
- Bulk
- Isochronous
在Reference 1有介紹一個工具...可以把host 跟device 的protocol印出來
snoopypro
以Ref 1的例子....他實作了一個飛彈的發射器... missile launcher
先以主體來看....下面列出大概的source code.... 整個code在Reference 2 的連結
這裡建立了一個 usb_driver struct ...
初始化了四個欄位.....
name -> "missile_launcher"
id_table -> ml_table fuction
probe -> ml_probe function
disconnect -> ml_disconnect
同時也建立了一個 usb_device_id 的struct
他用 USB_DEVICE的函式來初始化.....
其中 ML_VENDOR_ID 不能夠自己亂定義....每個USB裝置的製造商...都必須向USB forum申請一個代表自己
的識別碼...
ML_PRODUCT_ID 就是獲的VENDOR_ID的製造商..都可以管理自己產品的識別碼
然後呼叫MODULE_DEVICE_TABLE (usb, ml_table);
把參數連結到usb 變數...
首先來看看 初始化的時候做了甚麼事
static int __init usb_ml_init(void)
{
| int result; | |
| DBG_INFO("Register driver"); | |
| result = usb_register(&ml_driver); | |
| if (result) { | |
| DBG_ERR("registering driver failed"); | |
| } else { | |
| DBG_INFO("driver registered successfully"); | |
| } | |
| return result; | |
| } |
他呼叫了 usb_register function
把 ml_driver的記憶體位置傳進去.... 其用意是向核心註冊 usb_driver
在卸載usb 的驅動程式時...也必須使用 usb_deregister() 來向核心註銷struct usb_driver..
source code 如下:
| static void __exit usb_ml_exit(void) | |
| { | |
| usb_deregister(&ml_driver); | |
| DBG_INFO("module deregistered"); | |
| } |
接下來就是細說 probe 和 disconnect
在linux usb 的設計裡面....usb driver 必須提供 probe和 disconnect 讓usb core可以在適當的時
機觸發他們...這兩個function 都是 callback function.....所以你要盡可能的將主要的工作放在裝置被開啟的期間. 將usb core的探詢時間縮到最短...
| static int ml_probe(struct usb_interface *interface, | |
| const struct usb_device_id *id) | |
| { | |
| struct usb_device *udev = interface_to_usbdev(interface); | |
| struct usb_ml *dev = NULL; | |
| struct usb_host_interface *iface_desc; | |
| struct usb_endpoint_descriptor *endpoint; | |
| int i, int_end_size; | |
| int retval = -ENODEV; | |
| DBG_INFO("Probe missile launcher"); | |
| if (! udev) { | |
| DBG_ERR("udev is NULL"); | |
| goto exit; | |
| } | |
| dev = kzalloc(sizeof(struct usb_ml), GFP_KERNEL); | |
| if (! dev) { | |
| DBG_ERR("cannot allocate memory for struct usb_ml"); | |
| retval = -ENOMEM; | |
| goto exit; | |
| } | |
| dev->command = ML_STOP; | |
| sema_init(&dev->sem, 1); | |
| spin_lock_init(&dev->cmd_spinlock); | |
| dev->udev = udev; | |
| dev->interface = interface; | |
| iface_desc = interface->cur_altsetting; | |
| /* Set up interrupt endpoint information. */ | |
| for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { | |
| endpoint = &iface_desc->endpoint[i].desc; | |
| if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) | |
| == USB_DIR_IN) | |
| && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) | |
| == USB_ENDPOINT_XFER_INT)) | |
| dev->int_in_endpoint = endpoint; | |
| } | |
| if (! dev->int_in_endpoint) { | |
| DBG_ERR("could not find interrupt in endpoint"); | |
| goto error; | |
| } | |
| int_end_size = le16_to_cpu(dev->int_in_endpoint->wMaxPacketSize); | |
| dev->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL); | |
| if (! dev->int_in_buffer) { | |
| DBG_ERR("could not allocate int_in_buffer"); | |
| retval = -ENOMEM; | |
| goto error; | |
| } | |
| dev->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); | |
| if (! dev->int_in_urb) { | |
| DBG_ERR("could not allocate int_in_urb"); | |
| retval = -ENOMEM; | |
| goto error; | |
| } | |
| /* Set up the control URB. */ | |
| dev->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL); | |
| if (! dev->ctrl_urb) { | |
| DBG_ERR("could not allocate ctrl_urb"); | |
| retval = -ENOMEM; | |
| goto error; | |
| } | |
| dev->ctrl_buffer = kzalloc(ML_CTRL_BUFFER_SIZE, GFP_KERNEL); | |
| if (! dev->ctrl_buffer) { | |
| DBG_ERR("could not allocate ctrl_buffer"); | |
| retval = -ENOMEM; | |
| goto error; | |
| } | |
| dev->ctrl_dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); | |
| if (! dev->ctrl_dr) { | |
| DBG_ERR("could not allocate usb_ctrlrequest"); | |
| retval = -ENOMEM; | |
| goto error; | |
| } | |
| dev->ctrl_dr->bRequestType = ML_CTRL_REQUEST_TYPE; | |
| dev->ctrl_dr->bRequest = ML_CTRL_REQUEST; | |
| dev->ctrl_dr->wValue = cpu_to_le16(ML_CTRL_VALUE); | |
| dev->ctrl_dr->wIndex = cpu_to_le16(ML_CTRL_INDEX); | |
| dev->ctrl_dr->wLength = cpu_to_le16(ML_CTRL_BUFFER_SIZE); | |
| usb_fill_control_urb(dev->ctrl_urb, dev->udev, | |
| usb_sndctrlpipe(dev->udev, 0), | |
| (unsigned char *)dev->ctrl_dr, | |
| dev->ctrl_buffer, | |
| ML_CTRL_BUFFER_SIZE, | |
| ml_ctrl_callback, | |
| dev); | |
| /* Retrieve a serial. */ | |
| if (! usb_string(udev, udev->descriptor.iSerialNumber, | |
| dev->serial_number, sizeof(dev->serial_number))) { | |
| DBG_ERR("could not retrieve serial number"); | |
| goto error; | |
| } | |
| /* Save our data pointer in this interface device. */ | |
| usb_set_intfdata(interface, dev); | |
| /* We can register the device now, as it is ready. */ | |
| retval = usb_register_dev(interface, &ml_class); | |
| if (retval) { | |
| DBG_ERR("not able to get a minor for this device."); | |
| usb_set_intfdata(interface, NULL); | |
| goto error; | |
| } | |
| dev->minor = interface->minor; | |
| DBG_INFO("USB missile launcher now attached to /dev/ml%d", | |
| interface->minor - ML_MINOR_BASE); | |
| exit: | |
| return retval; | |
| error: | |
| ml_delete(dev); | |
| return retval; | |
| } |
首先先呼叫 interface_to_usbdev () 來取得 usb_interface 所控制的 usb device 也就是 udev
接下來就是 dev = kzalloc(sizeof(struct usb_ml), GFP_KERNEL);
宣告一個記憶體.....
然後
sema_init(&dev->sem, 1);
spin_lock_init(&dev->cmd_spinlock);
初始化一下 dev->sem.....然後打開 spin_lock...把dev 鎖住....
接下來就是所有的端點...找到我們要的端點
| /* Set up interrupt endpoint information. */ | |
| for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { | |
| endpoint = &iface_desc->endpoint[i].desc; | |
| if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) | |
| == USB_DIR_IN) | |
| && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) | |
| == USB_ENDPOINT_XFER_INT)) | |
| dev->int_in_endpoint = endpoint; | |
| } |
測試條件為IN direction 並且為 INT type
符合條件後....先向系統配置一個usb
dev->ctrl_urb = usb_alloc_urb(0, GFP_KERNEL);
成功之後...
然後就是產生一個urb 的struct...把資料都準備好呼叫
usb_fill_control_urb()
把資料傳給device...
把 dev的指標存入 struct usb_interface
usb_set_intfdata(interface, dev);
| 到這裡一切都搞定了....就可以註冊裝置了 retval = usb_register_dev(interface, &ml_class); |
| static void ml_disconnect(struct usb_interface *interface) | |
| { | |
| struct usb_ml *dev; | |
| int minor; | |
| mutex_lock(&disconnect_mutex); /* Not interruptible */ | |
| dev = usb_get_intfdata(interface); | |
| usb_set_intfdata(interface, NULL); | |
| down(&dev->sem); /* Not interruptible */ | |
| minor = dev->minor; | |
| /* Give back our minor. */ | |
| usb_deregister_dev(interface, &ml_class); | |
| /* If the device is not opened, then we clean up right now. */ | |
| if (! dev->open_count) { | |
| up(&dev->sem); | |
| ml_delete(dev); | |
| } else { | |
| dev->udev = NULL; | |
| up(&dev->sem); | |
| } | |
| mutex_unlock(&disconnect_mutex); | |
| DBG_INFO("USB missile launcher /dev/ml%d now disconnected", | |
| minor - ML_MINOR_BASE); | |
| } |
第一步先鎖住device....不讓它發生 race condition
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
當usb 裝置被抽離系統時...原本分配給該裝置的所有資源都應該盡可能地歸還給系統...
所以我們要呼叫 usb_get_infdata() 把之前得到的interface 取回來..
然後把 struct usb_interface 結構裡的interface 設成NULL...避免以後的犯錯
接下來就是
down(&dev->sem); /* Not interruptible */
然後呼叫 usb_deregister_dev() , 將之前的得到的次編號還給USB CORE
usb_deregister_dev(interface, &ml_class);
接下就是check count ...
如果count為零...就把裝置刪除
如果不為零...代表還有人在使用裝置...就不做任何事
| if (! dev->open_count) { | |
| up(&dev->sem); | |
| ml_delete(dev); | |
| } else { | |
| dev->udev = NULL; | |
| up(&dev->sem); | |
| } |
最後就是打開中斷 mutex_unlock(&disconnect_mutex);
Reference 1: http://matthias.vallentin.net/blog/2007/04/writing-a-linux-kernel-driver-for-an-unknown-usb-device/
Reference 2 : https://github.com/mavam/ml-driver/blob/master/ml_driver.c
Reference 2 : LINUX驅動程式第三版