天天看點

Linux那些事兒之我是UHCI(6)來來,我是一條總線,線線線線線線

下一個函數,1577行,usb_register_bus.我們說過,一個USB主機控制器就意味着一條USB總線,因為主機控制器控制的正是一條總線.古人說,貓走不走直線,完全取決于耗子,而資料走不走總線,完全取決于主機控制器.

是以這裡作為主機控制器的驅動,我們必須從軟體的角度來說,注冊一條總線.來自drivers/usb/core/hcd.c:

    712

    720 static int usb_register_bus(struct usb_bus *bus)

    721 {

    722         int busnum;

    723

    724         mutex_lock(&usb_bus_list_lock);

    725         busnum = find_next_zero_bit (busmap.busmap, USB_MAXBUS, 1);

    726         if (busnum < USB_MAXBUS) {

    727                 set_bit (busnum, busmap.busmap);

    728                 bus->busnum = busnum;

    729         } else {

    730                 printk (KERN_ERR "%s: too many buses/n", usbcore_name);

    731                 mutex_unlock(&usb_bus_list_lock);

    732                 return -E2BIG;

    733         }

    734

    735         bus->class_dev = class_device_create(usb_host_class, NULL, MKDEV(0,0),

    736                                              bus->controller, "usb_host%d", busnum);

    737         if (IS_ERR(bus->class_dev)) {

    738                 clear_bit(busnum, busmap.busmap);

    739                 mutex_unlock(&usb_bus_list_lock);

    740                 return PTR_ERR(bus->class_dev);

    741         }

    742

    743         class_set_devdata(bus->class_dev, bus);

    744

    745        

    746         list_add (&bus->bus_list, &usb_bus_list);

    747         mutex_unlock(&usb_bus_list_lock);

    748

    749         usb_notify_add_bus(bus);

    750

    751         dev_info (bus->controller, "new USB bus registered, assigned bus number %d/n", bus->busnum);

    752         return 0;

    753 }

Linux中名字裡帶一個register的函數那是數不勝數,也許這是一種時尚,随着你對Linux核心漸漸的熟悉,你慢慢就會覺得其實叫register的函數都很簡單,簡單得就像90後的女生作人流一樣.甚至你會發現,Linux核心中的子產品沒有不用register函數的,就像新聞聯播裡:開會沒有不隆重的,閉幕沒有不勝利的,講話沒有不重要的,決議沒有不通過的,鼓掌沒有不熱烈的,人心沒有不鼓舞的,上司沒有不重視的,進展沒有不順利的,問題沒有不解決的,完成沒有不超額的,成就沒有不巨大的,竣工沒有不提前的,接見沒有不親切的,中日沒有不友好的,中美沒有不合作的,交涉沒有不嚴正的,會談沒有不圓滿的.

其實一路走來的兄弟們應該能夠很容易的看懂這個函數,這個函數首先讓我們想起了在hub驅動中講的那個choose_address.當時我們有一個devicemap,而現在有一個busmap.很顯然,原理是一樣的.在drivesr/usb/core/hcd.c中有定義:

     88

     89 #define USB_MAXBUS              64

     90 struct usb_busmap {

     91         unsigned long busmap [USB_MAXBUS / (8*sizeof (unsigned long))];

     92 };

     93 static struct usb_busmap busmap;

和當時我們在hub驅動中對devicemap的分析一樣,當時的結論是該map一共有128位,同理可知這裡busmap則一共有64位.也就是說一共可以有64條USB總線.我想,對我們這些凡夫俗子來說,這麼多條足夠了吧.不夠?你以為你在織十字繡?

735行, class_device_create,這個函數是Linux裝置模型中一個很基礎的函數,我家Intel的企業文化中有六大價值觀,去Intel面試的時候我特逗的一點就是把那六大價值觀給背了下來,然後面試的時候跟面試官一條一條說,把人家逗樂了.這六大價值觀中有一個叫做Result Orientation,用中文說就是以結果為導向.那現在我想是該使用這一價值觀的時候了.Linux 2.6裝置模型中提供了大把大把的基礎函數,它們都在drivers/base目錄下面,這下面的函數你如果有興趣當然可以看一看,不過我不推薦你這麼做,除非你的目的就是要徹底研究這個裝置模型是如何實作的.對這些基礎函數,我覺得比較好的認識方法就是以結果為導向,看看它們執行之後具體有什麼效果,用直覺的效果來展現它們的作用.

那好,那麼這裡class_device_create的效果是什麼?我們知道裝置模型和sysfs結合相當緊密,最能反映裝置模型的效果的就是sysfs.是以憑一種男人的直覺,我們應該到/sysfs下面去看效果.不過我現在更願意給你提供更多的背景,

首先,什麼是class?C++的高手們一定不會不知道class吧,雖說哥們兒從未寫過C++程式,可是好歹也看過兩遍那本Thinking in C++的第一卷,是以class還是知道的.class就是類,裝置模型中引入類的意義在于讓一些模糊的東西變得更加清晰,更加直覺,比如同樣是scsi裝置,可能你是磁盤她是錄音帶,但你們都屬于一類,這一類就是scsi_device.于是就可以在/sys/class下面建立一個檔案夾,從這裡來展現同一個類别的各種裝置.比如,偶的某台機器裡/sys/class下面可以看到這些類,此時此刻還沒有加載usbcore.

localhost:~ # ls /sys/class/

backlight  graphics  mem   net      spi_master  vc

dma        input     misc  pci_bus  tty         vtconsole

而咱們這裡的class_device_create的第一個參數是usb_host_class,它是什麼東西呢?

讓我們把鏡頭切給usbcore的初始化函數,usb_init().我們在hub driver中已經說過,usb_init是Linux中整個usb子系統的起點.一切的一切都從這裡開始.而這個函數中有這麼一段:

    877         retval = usb_host_init();

    878         if (retval)

    879                 goto host_init_failed;

我們來看它具體做了什麼事情,usb_host_init來自drivers/usb/core/hcd.c:

    671 static struct class *usb_host_class;

    672

    673 int usb_host_init(void)

    674 {

    675         int retval = 0;

    676

    677         usb_host_class = class_create(THIS_MODULE, "usb_host");

    678         if (IS_ERR(usb_host_class))

    679                 retval = PTR_ERR(usb_host_class);

    680         return retval;

    681 }

讓我們在上面提到的那台機器機器中加載usbcore,看看在/sys/class/下面會發生點什麼.

localhost:~ # modprobe usbcore

localhost:~ # ls /sys/class/

backlight  dma  graphics  input  mem  misc  net  pci_bus  spi_master  tty  usb_device  usb_host  vc  vtconsole

看出差別了麼?多了兩個目錄,usb_host和usb_device,換言之,多了兩個類.是以我們不難知道,這裡class_create函數的效果就是在/sys/class/下面建立一個叫做usb_host的目錄.而usb_device是在另一個函數中建立的,usb_devio_init.方法是一樣的,也是調用class_create函數.關于usb_device我們就不去多說了,繼續看我們這個usb_host.我們這裡調用class_create然後傳回值賦給了usb_host_class,而這正是我們傳遞給class_device_create的第一個參數,是以你不看代碼也應該知道我們的目标是在/sys/class/usb_host/下面建立一個檔案或者是一個目錄,那麼結合代碼來看,你就不難發現我們要建立的是一個叫做usb_hostn的檔案或者是目錄,具體是什麼,讓我們用結果來說話, 首先我們沒有加載uhci-hcd,這時候可以看出這個目錄是空的.

localhost:~ # ls /sys/class/usb_host/

然後我們把這個子產品加載,再來看看效果.

localhost:~ # modprobe uhci-hcd

localhost:~ # ls -l /sys/class/usb_host/

total 0

drwxr-xr-x 2 root root 0 Oct  4 22:26 usb_host1

drwxr-xr-x 2 root root 0 Oct  4 22:26 usb_host2

drwxr-xr-x 2 root root 0 Oct  4 22:26 usb_host3

drwxr-xr-x 2 root root 0 Oct  4 22:26 usb_host4

因為我這台機器有4個uhci主機控制器,是以我們可以看出,分别為每個主機控制器建立了一個目錄.而usb_host後面的這個1,2,3,4就是剛才說的busnum,即總線編号,因為一個主機控制器控制着一條總線.同時我們把class_device_create的傳回值賦給了bus->class_dev.struct usb_bus中有一個成員struct class_device *class_dev,這個成員被稱作class device,這個結構體對寫驅動的人來說意義不大,但是從裝置模型的角度來說是必要的,實際上對寫驅動的人來說,你完全可以不理睬裝置模型中class這個部分,你可以我行我素,你可以盡可能少的支援裝置模型,因為這對你通路裝置沒有太多影響.你甚至可以讓你的裝置根本就不在/sysfs下面展現出來,你有權這麼做,因為你是叛逆的80後.但是一個聰明的人,你應該知道,有些東西是互相的,你用代碼去支援裝置模型,将來你使用裝置的時候就能享受到裝置模型為你提供的友善.相反,如果你為了省事不去支援裝置模型,那麼将來你會遭報應的,因為你使用裝置的時候會發現有很多不便.這道理就像我們中華民族廣大婦女的傳統美德善待婆婆一樣!雖然是今非昔比,已沒有了主仆之分,但亦應該在互相尊重人格平等的基礎上,更加地善待婆婆,才是最聰明的選擇.因為每一個女人都有可能成為将來的婆婆.如果世上的女人都能為後人作出榜樣,這個社會就會變的和諧起來,請做一個善良的聰明女人吧!

是以這裡有743行這麼一個舉動,調用class_set_devdata,這就算是寫代碼的人對裝置模型的支援,因為struct class_device中也有一個成員void *class_data,被稱為class-specific data,而在include/linux/device.h中定義了class_set_devdata和一個與之對應的函數class_get_devdata.

    279 static inline void *

    280 class_get_devdata (struct class_device *dev)

    281 {

    282         return dev->class_data;

    283 }

    284

    285 static inline void

    286 class_set_devdata (struct class_device *dev, void *data)

    287 {

    288         dev->class_data = data;

    289 }

結合我們這裡具體對這個函數調用的代碼可知,最終咱們這個host對應的class_device的class_data被指派為bus.這樣有朝一日我們要通過class_device找到對應的bus的時候我們隻要調用class_get_devdata即可.裝置模型的精髓就在于把一個裝置相關聯的種種元素都給聯系起來,裝置模型提供了大量建立這種紐帶的函數,我們要做的就是調用這些函數.

好,繼續,746行,很顯然又是隊列操作. usb_bus_list是一個全局隊列,在drivers/usb/core/hcd.c中定義:

     84

     85 LIST_HEAD (usb_bus_list);

     86 EXPORT_SYMBOL_GPL (usb_bus_list);

每次注冊一條總線就是往這個隊列裡添加一個元素.struct usb_bus中有一個成員struct list_head bus_list.是以這裡直接調用list_add即可.

749行,usb_notify_add_bus,看到這個函數我幾乎暈阙.因為細講這個函數意味着我得少打兩盤麻将而你卻未必會更能了解usb子系統.真的,時間有限,生命有限,你别天真的以為擠時間就真的像擠乳溝一樣容易.我隻想用一句話來解釋這個函數,按我們Intel以結果為導向的理論來說,這個函數在此情此景執行的結果是/proc/bus/usb下面會多一些檔案,比如,

localhost:~ # ls /proc/bus/usb/

001  002  003  004  devices

這幾個檔案都是剛才這個函數執行之後的效果.

好了,usb_register_bus算是看完了,再一次回到usb_add_hcd中來.1580行,usb_alloc_dev被調用,這個函數我們可不陌生.不過我們下節再看吧.這節剩下的時間我們為usb_notify_add_bus再多說兩句話,這個函數牽涉到Linux中的notify機制,這是Linux核心中一種常用的事件回調處理機制.傳說中的那個神奇的核心調試工具kdb中就是利用了這種機制進入kdb的.這種機制在網絡裝置驅動中的應用,那就像”成都小吃”在北京的分布一樣,滿大街都是.而在usb子系統中,以前并沒有使用這種機制,隻是Greg同學在2005年底提出來要加進來的.其實我個人覺得,usb中不使用這種機制也能照樣轉.隻不過Greg是Linux中USB掌門人,就算地球不轉了大家還是要圍着他轉.

繼續閱讀