## 13.4.?編寫一個 USB 驅動
編寫一個 USB 設備驅動的方法類似于一個 pci 驅動: 驅動注冊它的驅動對象到 USB 子系統并且之后使用供應商和設備標識來告知是否它的硬件已經安裝.
### 13.4.1.?驅動支持什么設備
struct usb_device_id 結構提供了這個驅動支持的一個不同類型 USB 設備的列表. 這個列表被USB 核心用來決定給設備哪個驅動, 并且通過熱插拔腳本來決定哪個驅動自動加載, 當特定設備被插入系統時.
struct usb_device_id 結構定義有下面的成員:
__u16 match_flags
決定設備應當匹配結構中下列的哪個成員. 這是一個位成員, 由在 include/linux/mod_devicetable.h 文件中指定的不同的 USB_DEVICE_ID_MATCH_* 值所定義. 這個成員常常從不直接設置, 但是由 USB_DEVICE 類型宏來初始化.
__u16 idVendor
這個設備的 USB 供應商 ID. 這個數由 USB 論壇分配給它的成員并且不能由任何別的構成.
__u16 idProduct
這個設備的 USB 產品 ID. 所有的有分配給他們的供應商 ID 的供應商可以隨意管理它們的產品 ID.
__u16 bcdDevice_lo__u16 bcdDevice_hi
定義供應商分配的產品版本號的高低范圍. bcdDevice_hi 值包括其中; 它的值是最高編號的設備號. 這 2 個值以BCD 方式編碼. 這些變量, 連同 idVendor 和 idProduct, 用來定義一個特定的設備版本.
__u8 bDeviceClass__u8 bDeviceSubClass__u8 bDeviceProtocol
定義類, 子類, 和設備協議, 分別地. 這些值被 USB 論壇分配并且定義在 USB 規范中. 這些值指定這個設備的行為, 包括設備上所有的接口.
__u8 bInterfaceClass__u8 bInterfaceSubClass__u8 bInterfaceProtocol
非常象上面的設備特定值, 這些定義了類, 子類, 和單個接口協議, 分別地. 這些值由 USB 論壇指定并且定義在 USB 規范中.
kernel_ulong_t driver_info
這個值不用來匹配, 但是它持有信息, 驅動可用來在 USB 驅動的探測回調函數區分不同的設備.
至于 PCI 設備, 有幾個宏可用來初始化這個結構:
USB_DEVICE(vendor, product)
創建一個 struct usb_device_id, 可用來只匹配特定供應商和產品 ID 值. 這是非常普遍用的, 對于需要特定驅動的 USB 設備.
USB_DEVICE_VER(vendor, product, lo, hi)
創建一個 struct usb_device_id, 用來在一個版本范圍中只匹配特定供應商和產品 ID 值.
USB_DEVICE_INFO(class, subclass, protocol)
創建一個 struct usb_device_id, 可用來只匹配一個特定類的 USB 設備.
USB_INTERFACE_INFO(class, subclass, protocol)
創建一個 struct usb_device_id, 可用來只匹配一個特定類的 USB 接口.
對于一個簡單的 USB 設備驅動, 只控制來自一個供應商的一個單一 USB 設備, struct usb_device_id 表可定義如:
~~~
/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, skel_table);
~~~
至于 PCI 驅動, MODULE_DEVICE_TABLE 宏有必要允許用戶空間工具來發現這個驅動可控制什么設備. 但是對于 USB 驅動, 字符串 usb 必須是在這個宏中的第一個值.
### 13.4.2.?注冊一個 USB 驅動
所有 USB 驅動必須創建的主要結構是 struct usb_driver. 這個結構必須被 USB 驅動填充并且包含多個函數回調和變量, 來向 USB 核心代碼描述 USB 驅動:
struct module *owner
指向這個驅動的模塊擁有者的指針. USB 核心使用它正確地引用計數這個 USB 驅動, 以便它不被在不合適的時刻卸載. 這個變量應當設置到 THIS_MODULE 宏.
const char *name
指向驅動名子的指針. 它必須在內核 USB 驅動中是唯一的并且通常被設置為和驅動的模塊名相同. 它出現在 sysfs 中在 /sys/bus/usb/drivers/ 之下, 當驅動在內核中時.
const struct usb_device_id *id_table
指向 struct usb_device_id 表的指針, 包含這個驅動可接受的所有不同類型 USB 設備的列表. 如果這個變量沒被設置, USB 驅動中的探測回調函數不會被調用. 如果你需要你的驅動給系統中每個 USB 設備一直被調用, 創建一個只設置這個 driver_info 成員的入口項:
~~~
static struct usb_device_id usb_ids[] = {
{.driver_info = 42},
{}
};
~~~
int (*probe) (struct usb_interface *intf, const struct usb_device_id *id)
指向 USB 驅動中探測函數的指針. 這個函數(在"探測和去連接的細節"一節中描述)被 USB 核心調用當它認為它有一個這個驅動可處理的 struct usb_interface. 一個指向 USB 核心用來做決定的 struct usb_device_id 的指針也被傳遞到這個函數. 如果這個 USB 驅動主張傳遞給它的 struct usb_interface, 它應當正確地初始化設備并且返回 0. 如果驅動不想主張這個設備, 或者發生一個錯誤, 它應當返回一個負錯誤值.
void (*disconnect) (struct usb_interface *intf)
指向 USB 驅動的去連接函數的指針. 這個函數(在"探測和去連接的細節"一節中描述)被 USB 核心調用, 當 struct usb_interface 已被從系統中清除或者當驅動被從 USB 核心卸載.
為創建一個值 struct usb_driver 結構, 只有 5 個成員需要被初始化:
~~~
static struct usb_driver skel_driver = {
.owner = THIS_MODULE,
.name = "skeleton",
.id_table = skel_table,
.probe = skel_probe,
.disconnect = skel_disconnect,
};
~~~
struct usb_driver 確實包含更多幾個回調, 它們通常不經常用到, 并且不被要求使 USB 驅動正確工作:
int (*ioctl) (struct usb_interface *intf, unsigned int code, void *buf)
指向 USB 驅動的 ioctl 函數的指針. 如果它出現, 在用戶空間程序對一個關聯到 USB 設備的 usbfs 文件系統設備入口, 做一個 ioctl 調用時被調用. 實際上, 只有 USB 集線器驅動使用這個 ioctl, 因為沒有其他的真實需要對于任何其他 USB 驅動要使用.
int (*suspend) (struct usb_interface *intf, u32 state)
指向 USB 驅動中的懸掛函數的指針. 當設備要被 USB 核心懸掛時被調用.
int (*resume) (struct usb_interface *intf)
指向 USB 驅動中的恢復函數的指針. 當設備正被 USB 核心恢復時被調用.
為注冊 struct usb_driver 到 USB 核心, 一個調用 usb_register_driver 帶一個指向 struct usb_driver 的指針. 傳統上在 USB 驅動的模塊初始化代碼做這個:
~~~
static int __init usb_skel_init(void)
{
int result;
/* register this driver with the USB subsystem */
result = usb_register(&skel_driver);
if (result)
err("usb_register failed. Error number %d", result);
return result;
}
~~~
當 USB 驅動被卸載, struct usb_driver 需要從內核注銷. 使用對 usb_deregister_driver 的調用做這個. 當這個調用發生, 任何當前綁定到這個驅動的 USB 接口被去連接, 并且去連接函數為它們而被調用.
~~~
static void __exit usb_skel_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver);
}
~~~
#### 13.4.2.1.?探測和去連接的細節
在之前章節描述的 struct usb_driver 結構中, 驅動指定 2 個 USB 核心在合適的時候調用的函數. 探測函數被調用, 當設備被安裝時, USB 核心認為這個驅動應當處理; 探測函數應當進行檢查傳遞給它的關于設備的信息, 并且決定是否驅動真正合適那個設備. 去連接函數被調用當驅動應當不再控制設備, 由于某些理由, 并且可做清理.
探測和去連接函數回調都在 USB 集線器內核線程上下文中被調用, 因此它們中睡眠是合法的. 但是, 建議如果有可能大部分工作應當在設備被用戶打開時完成. 為了保持 USB 探測時間為最小. 這是因為 USB 核心處理 USB 設備的添加和去除在一個線程中, 因此任何慢設備驅動可導致 USB 設備探測時間慢下來并且用戶可注意到.
在探測函數回調中, USB 驅動應當初始化任何它可能使用來管理 USB 設備的本地結構. 它還應當保存任何它需要的關于設備的信息到本地結構, 因為在此時做這些通常更容易. 作為一個例子, USB 驅動常常想為設備探測端點地址和緩沖大小是什么, 因為和設備通訊需要它們. 這里是一些例子代碼, 它探測 BULK 類型的 IN 和 OUT 端點, 并且保存一些關于它們的信息在一個本地設備結構中:
~~~
/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
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 &&
(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK)) { /* we found a bulk in endpoint */ buffer_size = endpoint->wMaxPacketSize;
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;
}
}
if (!dev->bulk_out_endpointAddr &&
!(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK)) { /* 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;
}
~~~
這塊代碼首先循環在這個接口中出現的每個端點, 并且分配一個本地指針到端點結構來使它之后容易存取:
~~~
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
~~~
那么, 在我們有了一個端點后, 我們還沒有發現一個塊 IN 類型端點, 我們看是否這個端點的方向是 IN. 那個可被測試通過看是否位掩碼 USB_DIR_IN 被包含在 bEndpointAddress 端點變量中. 如果這是真, 我們決定是否端點類型是塊, 通過使用 USB_ENDPOINT_XFERTYPE_MASK 位掩碼首先掩去 bmAttributes 變量, 并且接著檢查是否它匹配值 USB_ENDPOINT_XFER_BULK:
~~~
if (!dev->bulk_in_endpointAddr &&
(endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_BULK))
{
~~~
如果所有的這些檢查都是真, 驅動知道它發現了正確的端點類型, 并且可保存關于端點的信息到本地結構中, 它后來將需要這些信息和它通訊.
~~~
/* we found a bulk in endpoint */
buffer_size = endpoint->wMaxPacketSize;
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;
}
~~~
因為 USB 驅動需要獲取在設備的生命周期后期和這個 struct usb_interface 關聯的本地數據結構, 函數 usb_set_intfdata 可被調用:
~~~
/* save our data pointer in this interface device */
usb_set_intfdata(interface, dev);
~~~
這個函數接受一個指向任何數據類型的指針, 并且保存它到 struct usb_interface 結構為后面的存取. 為獲取這個數據, 函數 usb_get_intfdata 應當被調用:
~~~
struct usb_skel *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;
subminor = iminor(inode);
interface = usb_find_interface(&skel_driver, subminor);
if (!interface)
{
err ("%s - error, can't find device for minor %d",
__FUNCTION__, subminor);
retval = -ENODEV;
goto exit;
}
dev = usb_get_intfdata(interface);
if (!dev)
{
retval = -ENODEV;
goto exit;
}
~~~
usb_get_intfdata 常常被調用, 在 USB 驅動的 open 函數和在去連接函數. 感謝這 2 個函數, USB 驅動不需要保持一個靜態指針數組來保存單個設備結構為系統中所有當前的設備. 對設備信息的非直接引用允許一個無限數目的設備被任何 USB 驅動支持.
如果 USB 驅動沒有和另一種處理用戶和設備交互的子系統(例如 input, tty, video, 等待)關聯, 驅動可使用 USB 主編號為了使用傳統的和用戶空間之間的字符驅動接口. 為此, USB 驅動必須在探測函數中調用 usb_register_dev 函數, 當它想注冊一個設備到 USB 核心. 確認設備和驅動處于正確的狀態, 來處理一個想在調用這個函數時盡快存取這個設備的用戶.
~~~
/* we can register the device now, as it is ready */
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;
}
~~~
usb_register_dev 函數要求一個指向 struct usb_interface 的指針和指向 struct usb_class_driver 的指針. struct -usb_class_driver 用來定義許多不同的參數, 當注冊一個次編號USB 驅動要 USB 核心知道這些參數. 這個結構包括下列變量:.
char *name
sysfs 用來描述設備的名子. 一個前導路徑名, 如果存在, 只用在 devfs 并且本書不涉及. 如果設備號需要在這個名子中, 字符 %d 應當在名子串中. 例如, 位創建 devfs 名子 usb/foo1 和 sysfs 類名 foo1, 名子串應當設置為 usb/foo%d.
struct file_operations *fops;
指向 struct file_operations 的結構的指針, 這個驅動已定義來注冊為字符設備. 這個結構的更多信息見第 3 章.
mode_t mode;
給這個驅動的要被創建的 devfs 文件的模式; 否則不使用. 這個變量的典型設置是值 S_IRUSR 和 值 S_IWUSR 的結合, 它將只提供這個設備文件的擁有者讀和寫存取.
int minor_base;
這是給這個驅動安排的次編號的開始. 所有和這個驅動相關的設備被創建為從這個值開始的唯一的, 遞增的次編號. 只有 16 個設備被允許在任何時刻和這個驅動關聯, 除非 CONFIG_USB_DYNAMIC_MINORS 配置選項被打開. 如果這樣, 忽略這個變量, 并且這個設備的所有的次編號被以先來先服務的方式分配. 建議打開了這個選項的系統使用一個程序例如 udev 來關聯系統中的設備節點, 因為一個靜態的 /dev 樹不會正確工作.
當 USB 設備斷開, 所有的關聯到這個設備的資源應當被清除, 如果可能. 在此時, 如果 usb_register_dev 已被在探測函數中調用來分配一個 USB 設備的次編號, 函數 usb_deregister_dev 必須被調用來將次編號給回 USB 核心.
在斷開函數中, 也重要的是從接口獲取之前調用 usb_set_intfdata 所設置的數據. 接著設置數據指針在 struct us_interface 結構為 NULL 來阻止在不正確存取數據中的任何進一步的錯誤.
~~~
static void skel_disconnect(struct usb_interface *interface)
{
struct usb_skel *dev;
int minor = interface->minor;
/* prevent skel_open() from racing skel_disconnect( ) */
lock_kernel();
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
/* give back our minor */
usb_deregister_dev(interface, &skel_class);
unlock_kernel(); /* decrement our usage count */
kref_put(&dev->kref, skel_delete);
info("USB Skeleton #%d now disconnected", minor);
}
~~~
注意在之前代碼片段中的調用 lock_kernel. 它獲取了 bigkernel 鎖, 以至于 disconnect 回調不會遇到一個競爭情況, 在使用 open 調用試圖獲取一個指向正確接口數據結構的指針. 因為 open 在 bigkernel 鎖獲取情況下被調用, 如果 disconnect 也獲取同一個鎖, 只有驅動的一部分可存取并且接著設置接口數據指針.
就在 disconnect 函數為一個 USB 設備被調用, 所有的當前在被傳送的 urb 可被 USB 核心取消, 因此驅動不必明確為這些 urb 調用 usb_kill_urb. 如果一個驅動試圖提交一個 urb 給 USB 設備, 在調用 usb_submit_urb 被斷開之后, 這個任務會失敗, 錯誤值為-EPIPE.
### 13.4.3.?提交和控制一個 urb
當驅動有數據發送到 USB 設備(如同在驅動的 write 函數中發生的), 一個 urb 必須被分配來傳送數據到設備.
~~~
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
{
retval = -ENOMEM;
goto error;
}
~~~
在 urb 被成功分配后, 一個 DMA 緩沖也應當被創建來發送數據到設備以最有效的方式, 并且被傳遞到驅動的數據應當被拷貝到緩沖:
~~~
buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
if (!buf)
{
retval = -ENOMEM;
goto error;
}
if (copy_from_user(buf, user_buffer, count))
{
retval = -EFAULT;
goto error;
}
~~~
應當數據被正確地從用戶空間拷貝到本地緩沖, urb 在它可被提交給 USB 核心之前必須被正確初始化:
~~~
/* initialize the urb properly */
usb_fill_bulk_urb(urb, dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
buf, count, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
~~~
現在 urb 被正確分配, 數據被正確拷貝, 并且 urb 被正確初始化, 它可被提交給 USB 核心來傳遞給設備.
~~~
/* send the data out the bulk port */
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval)
{
err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
goto error;
}
~~~
在urb被成功傳遞到 USB 設備(或者在傳輸中發生了什么), urb 回調被 USB 核心調用. 在我們的例子中, 我們初始化 urb 來指向函數 skel_write_bulk_callback, 并且那就是被調用的函數:
~~~
static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
/* sync/async unlink faults aren't errors */
if (urb->status &&
!(urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN)){
dbg("%s - nonzero write bulk status received: %d",
__FUNCTION__, urb->status);
}
/* free up our allocated buffer */
usb_buffer_free(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
}
~~~
回調函數做的第一件事是檢查 urb 的狀態來決定是否這個 urb 成功完成或沒有. 錯誤值, -ENOENT, -ECONNRESET, 和 -ESHUTDOWN 不是真正的傳送錯誤, 只是報告伴隨成功傳送的情況. (見 urb 的可能錯誤的列表, 在"結構 struct urb"一節中詳細列出). 接著這個回調釋放安排給這個 urb 傳送的已分配的緩沖.
在 urb 的回調函數在運行時另一個 urb 被提交給設備是普遍的. 當流數據到設備時是有用的. 記住 urb 回調是在中斷上下文運行, 因此它不應當做任何內存分配, 持有任何旗標, 或者任何可導致進程睡眠的事情. 當從回調中提交 urb, 使用 GFP_ATOMIC 標志來告知 USB 核心不要睡眠, 如果它需要分配新內存塊在提交過程中.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考