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驅動程式第三版