2016年1月20日 星期三

試著在raspberry 上面寫一個usb driver吧



usb 分成四種裝置


  • Control
  • Interrupt
  • Bulk
  • Isochronous


在Reference 1有介紹一個工具...可以把host 跟device  的protocol印出來

snoopypro

以Ref 1的例子....他實作了一個飛彈的發射器... missile launcher

先以主體來看....下面列出大概的source code....  整個code在Reference 2 的連結
struct usb_ml {
    /* One structure for each connected device */
};

static struct usb_device_id ml_table [] = {
    { USB_DEVICE(ML_VENDOR_ID, ML_PRODUCT_ID) },
    { }
};

static int ml_open(struct inode *inode, struct file *file)
{
    /* open syscall */
}
static int ml_release(struct inode *inode, struct file *file)
{
    /* close syscall */
}

static ssize_t ml_write(struct file *file, const char __user *user_buf, size_t
        count, loff_t *ppos);
{
    /* write syscall */
}

static struct file_operations ml_fops = {
    .owner =    THIS_MODULE,
    .write =    ml_write,
    .open =     ml_open,
    .release =  ml_release,
};

static int ml_probe(struct usb_interface *interface, const struct usb_device_id
        *id)
{
    /* called when a USB device is connected to the computer. */
}

static void ml_disconnect(struct usb_interface *interface)
{
    /* called when unplugging a USB device. */
}

static struct usb_driver ml_driver = {
    .name = "missile_launcher",
    .id_table = ml_table,
    .probe = ml_probe,
    .disconnect = ml_disconnect,
};

static int __init usb_ml_init(void)
{
    /* called on module loading */
}

static void __exit usb_ml_exit(void)
{
    /* called on module unloading */
}

module_init(usb_ml_init);
module_exit(usb_ml_exit);

   這裡建立了一個 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驅動程式第三版