usbドライバ
Rev.1を表示中。最新版はこちら。
カーネルソースにusb-skeleton.cと言うのがあります。これはUSBドライバの雛形で、これをコンパイルし、insmodすると、skeletonと言うなusbドライバがインストールされ、ベンダーIDが0xfff0,プロダクトIDも0xfff0の仮想と思われるUSBデバイスを挿入すると、/sys/bus/usb/devices/と、マイナー番号が192の/dev/skel1のデバイスファイルが作成され、/dev/skel1をread/writeする事で、usbデバイスとバルク読み書きする事ができます。単にバルク読み書きするUSBデバイスなら、usb-skeleton.cのベンダーIDとプロダクトIDを書き換えることだけで、usbドライバを作ることができます。
USBドライバは、ドライバから見ると2つのフェーズで構成されています。最初はUSBコア(下位の層で、ややっこしいUSBハードに掛かる処理を行ってくれます。)に、ベンダーIDとプロダクトIDそして掛かるコールバックを有するUSBドライバを登録することです。USBデバイスが挿入されると、USBコアはそのベンダーIDとプロダクトIDを一致するusbドライバのコールバック関数probeを呼び出します。
usbインターフェースを引数にprove()がコールされます。ここでこのusbインターフェースから、バッファーとかリスト、エンドポイントを取得し、デバイスファイルを作成します。ことデバイスファイルの file_operationsには、デバイスのread/writeのコールバックが設定されています。
usbデバイスとのやり取りは、エンドポイント間でやり取りします。ソケットで言うなら、デバイスがIPアドレスで、エンドポイントがポートと例えれば理解しやすいかと思います。なお、エンドポイントはin/outおよびコマンド用とか機能によって有しています。
全ソースはカーネル下のそれを見てもらえばと思います。ここではエッセンスだけと言う事で。
insmodすると、skel_driverを引数にしてusb_register()がコールされ、usbコアーにusbドライバとして登録されます。usbコアのこのid_tableを参照することで、挿入されたusbデバイスにマッチするドライバを検索でき、マッチしたドライバのprobeをコールします。従ってオリジナルなドライバを作成する場合、USB_SKEL_VENDOR_IDとUSB_SKEL_PRODUCT_IDを、必要ならnameをデバイスの適切なIDに書き直します。
#define USB_SKEL_VENDOR_ID 0xfff0 #define USB_SKEL_PRODUCT_ID 0xfff0
static const struct usb_device_id skel_table[] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* Terminating entry */ }; static struct usb_driver skel_driver = { .name = "skeleton", .probe = skel_probe, .disconnect = skel_disconnect, .suspend = skel_suspend, .resume = skel_resume, .pre_reset = skel_pre_reset, .post_reset = skel_post_reset, .id_table = skel_table, .supports_autosuspend = 1, }; module_usb_driver(skel_driver);module_usb_driverマクロは以下の様に定義されていて、module_initに__usb_driverを引数にしてusb_register()をコールし、module_exitには__usb_driverを引数にしてusb_deregister()をコールするように展開されます。
#define module_usb_driver(__usb_driver) \ module_driver(__usb_driver, usb_register, \ usb_deregister)skel_probe()の主たる処理は、ドライバがやり取りする構造体(usb_skelで、ドライバ単位で独自のものとなります。)に、usb_interfaceから必要な情報をセットし、それとinterface->usb_devにセットし、USB_SKEL_MINOR_BASEをマイナー番号のデバイスファイル/sysファイルを作成します。この情報はデバイスファイルのオープン時、inodeからのマイナー番号で、usbデバイスのusb_interfaceを取得することができます。
struct usb_skel { struct usb_device *udev; /* the usb device for this device */ struct usb_interface *interface; /* the interface for this device */ struct semaphore limit_sem; /* limiting the number of writes in progress */ struct usb_anchor submitted; /* in case we need to retract our submissions */ struct urb *bulk_in_urb; /* the urb to read data with */ unsigned char *bulk_in_buffer; /* the buffer to receive data */ size_t bulk_in_size; /* the size of the receive buffer */ size_t bulk_in_filled; /* number of bytes in the buffer */ size_t bulk_in_copied; /* already copied to user space */ __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ __u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */ int errors; /* the last request tanked */ bool ongoing_read; /* a read is going on */ bool processed_urb; /* indicates we haven't processed the urb */ spinlock_t err_lock; /* lock for errors */ struct kref kref; struct mutex io_mutex; /* synchronize I/O with disconnect */ struct completion bulk_in_completion; /* to wait for an ongoing read */ }; #define USB_SKEL_MINOR_BASE 192 static const struct file_operations skel_fops = { .owner = THIS_MODULE, .read = skel_read, .write = skel_write, .open = skel_open, .release = skel_release, .flush = skel_flush, .llseek = noop_llseek, }; static struct usb_class_driver skel_class = { .name = "skel%d", .fops = &skel_fops, .minor_base = USB_SKEL_MINOR_BASE, }; static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_skel *dev; struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; size_t buffer_size; int i; int retval = -ENOMEM; /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { err("Out of memory"); goto error; } デバイスの参照カウンタとかリストヘッド初期化します。 kref_init(&dev->kref); sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); mutex_init(&dev->io_mutex); spin_lock_init(&dev->err_lock); init_usb_anchor(&dev->submitted); init_completion(&dev->bulk_in_completion); devにinterface->usb_devとinterfaceを設定します。 dev->udev = usb_get_dev(interface_to_usbdev(interface)); dev->interface = interface; ここまでは、各ドライバに依存する内容で、ドライバ本質的な物でありません。 そして以降がエンドポイントに関する本質的な項目となります。 デバイスインターフェースのエンドポイント数で、ドライバのエンドポイントにバンドルします。 この時バッファーサイズ必要ならバッファを確保したりしています。 iface_desc = interface->cur_altsetting; for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { endpoint = &iface_desc->endpoint[i].desc; if (!dev->bulk_in_endpointAddr && usb_endpoint_is_bulk_in(endpoint)) { 読み込み処理でやり取りするチャンネル /* we found a bulk in endpoint */ buffer_size = usb_endpoint_maxp(endpoint); dev->bulk_in_size = buffer_size; dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); if (!dev->bulk_in_buffer) { err("Could not allocate bulk_in_buffer"); goto error; } dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); if (!dev->bulk_in_urb) { err("Could not allocate bulk_in_urb"); goto error; } } if (!dev->bulk_out_endpointAddr && usb_endpoint_is_bulk_out(endpoint)) { /* we found a bulk out endpoint */ 書き込み処理でやり取りするチャンネル dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; } } if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { err("Could not find both bulk-in and bulk-out endpoints"); goto error; } /* save our data pointer in this interface device */ interface->devにdevをセットします。 usb_set_intfdata(interface, dev); /* we can register the device now, as it is ready */ デバイスファイル等を作成します。ファイルはskel_class.nameで%dとすると、1から順次インクリメントされます。 マイナー番号はminor_baseから空きを検索し、それを割り当てます。 retval = usb_register_dev(interface, &skel_class); if (retval) { /* something prevented us from registering this driver */ err("Not able to get a minor for this device."); usb_set_intfdata(interface, NULL); goto error; } /* let the user know what node this device is now attached to */ dev_info(&interface->dev, "USB Skeleton device now attached to USBSkel-%d", interface->minor); return 0; error: if (dev) /* this frees allocated memory */ kref_put(&dev->kref, skel_delete); return retval; }skel_disconnect()はデバイスがデタッチされた時にコールされ、ドライバの削除および掛かるバッファー等の解放し、デバイスファイルの削除を行っているだけです。usb_kill_anchored_urbs()は書き込みで待っているとanchorリストを削除する処理です。
static void skel_disconnect(struct usb_interface *interface) { struct usb_skel *dev; int minor = interface->minor; dev = usb_get_intfdata(interface); usb_set_intfdata(interface, NULL); /* give back our minor */ usb_deregister_dev(interface, &skel_class); /* prevent more I/O from starting */ mutex_lock(&dev->io_mutex); dev->interface = NULL; mutex_unlock(&dev->io_mutex); usb_kill_anchored_urbs(&dev->submitted); /* decrement our usage count */ kref_put(&dev->kref, skel_delete); dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor); }なお、/skel_suspend/skel_resume/skel_pre_reset/skel_post_resetは、ドライバデバイスの参照カウンタの処理とか、ロックを掛けたりするだけの処理です。
デバイスファイルのファイルオペレーションについては次回と言う事で。