版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
Linux設(shè)備驅(qū)動(dòng)之USBhub驅(qū)動(dòng)Linux設(shè)備驅(qū)動(dòng)之USBhub驅(qū)動(dòng)------------------------------------------本文系本站原創(chuàng),歡迎轉(zhuǎn)載!轉(zhuǎn)載請(qǐng)注明出處:://------------------------------------------一:前言繼UHCI的驅(qū)動(dòng)之后,我們對(duì)USBControl的運(yùn)作有了一定的了解.在接下來(lái)的分析中,我們對(duì)USB設(shè)備的驅(qū)動(dòng)做一個(gè)全面的分析,我們先從HUB的驅(qū)動(dòng)說(shuō)起.關(guān)于HUB,usb2.0spec上有詳細(xì)的定義,基于這部份的代碼位于linux-2.6.25/drivers/usb/core下,也就是說(shuō),這部份代碼是位于core下,和具體設(shè)備是無(wú)關(guān)的,因?yàn)楦鲝S(chǎng)商的hub都是按照spec的要求來(lái)設(shè)計(jì)的.二:UHCI驅(qū)動(dòng)中的roothub記得在分析UHCI驅(qū)動(dòng)的時(shí)候,曾詳細(xì)分析過(guò)roothub的初始化操作.為了分析方便,將代碼片段列出如下:usb_add_hcd()àusb_alloc_dev():structusb_device*usb_alloc_dev(structusb_device*parent,structusb_bus*bus,unsignedport1){…………//usb_device,內(nèi)嵌有structdevice結(jié)構(gòu),對(duì)這個(gè)結(jié)構(gòu)進(jìn)行初始化device_initialize(&dev->dev);dev->dev.bus=&usb_bus_type;dev->dev.type=&usb_device_type;…………}一看到前面對(duì)dev的賦值,根據(jù)我們對(duì)設(shè)備模型的理解,一旦這個(gè)device進(jìn)行注冊(cè),就會(huì)發(fā)生driver和device的匹配過(guò)程了.不過(guò),現(xiàn)在還不是分析這個(gè)過(guò)程的時(shí)候,我們先來(lái)看一下,USB子系統(tǒng)中的兩種驅(qū)動(dòng).三:USB子系統(tǒng)中的兩種驅(qū)動(dòng)linux-2.6.25/drivers/usb/core/driver.c中,我們可以找到兩種registerdriver的方式,分別為usb_register_driver()和usb_register_device_driver().分別來(lái)分析一下這兩個(gè)接口.usb_register_device_driver()接口的代碼如下:intusb_register_device_driver(structusb_device_driver*new_udriver,structmodule*owner){intretval=0;if(usb_disabled())return-ENODEV;new_udriver->drvwrap.for_devices=1;new_udriver->=(char*)new_udriver->name;new_udriver->drvwrap.driver.bus=&usb_bus_type;new_udriver->be=usb_probe_device;new_udriver->drvwrap.driver.remove=usb_unbind_device;new_udriver->drvwrap.driver.owner=owner;retval=driver_register(&new_udriver->drvwrap.driver);if(!retval){pr_info(“%s:registerednewdevicedriver%s\n”,usbcore_name,new_udriver->name);usbfs_update_special();}else{printk(KERN_ERR“%s:error%dregisteringdevice““driver%s\n”,usbcore_name,retval,new_udriver->name);}returnretval;}首先,通過(guò)usb_disabled()來(lái)判斷一下usb是否被禁用,如果被禁用,當(dāng)然就不必執(zhí)行下面的流程了,直接退出即可.從上面的代碼,很明顯可以看到,structusb_device_driver對(duì)structdevice_driver進(jìn)行了一次封裝,我們注意一下這里的賦值操作:new_udriver->drvwrap.for_devices=1.等等.這些在后面都是用派上用場(chǎng)的.usb_register_driver()的代碼如下:intusb_register_driver(structusb_driver*new_driver,structmodule*owner,constchar*mod_name){intretval=0;if(usb_disabled())return-ENODEV;new_driver->drvwrap.for_devices=0;new_driver->=(char*)new_driver->name;new_driver->drvwrap.driver.bus=&usb_bus_type;new_driver->be=usb_probe_interface;new_driver->drvwrap.driver.remove=usb_unbind_interface;new_driver->drvwrap.driver.owner=owner;new_driver->drvwrap.driver.mod_name=mod_name;spin_lock_init(&new_driver->dynids.lock);INIT_LIST_HEAD(&new_driver->dynids.list);retval=driver_register(&new_driver->drvwrap.driver);if(!retval){pr_info(“%s:registerednewinterfacedriver%s\n”,usbcore_name,new_driver->name);usbfs_update_special();usb_create_newid_file(new_driver);}else{printk(KERN_ERR“%s:error%dregisteringinterface““driver%s\n”,usbcore_name,retval,new_driver->name);}returnretval;}很明顯,在這里接口里,將new_driver->drvwrap.for_devices設(shè)為了0.而且兩個(gè)接口的porbe()函數(shù)也不一樣.其實(shí),對(duì)于usb_register_driver()可以看作是usb設(shè)備中的接口驅(qū)動(dòng),而usb_register_device_driver()是一個(gè)單純的USB設(shè)備驅(qū)動(dòng).四:hub的驅(qū)動(dòng)分析4.1:usb_bus_type->match()的匹配過(guò)程usb_bus_type->match()用來(lái)判斷驅(qū)動(dòng)和設(shè)備是否匹配,它的代碼如下:staticintusb_device_match(structdevice*dev,structdevice_driver*drv){/*整理by*///usbdevice的情況if(is_usb_device(dev)){/*interfacedriversnevermatchdevices*/if(!is_usb_device_driver(drv))return0;/*TODO:Addrealmatchingcode*/return1;}//interface的情況else{structusb_interface*intf;structusb_driver*usb_drv;conststructusb_device_id*id;/**/if(is_usb_device_driver(drv))return0;intf=to_usb_interface(dev);usb_drv=to_usb_driver(drv);id=usb_match_id(intf,usb_drv->id_table);if(id)return1;id=usb_match_dynamic_id(intf,usb_drv);if(id)return1;}return0;}這里的match會(huì)區(qū)分上面所說(shuō)的兩種驅(qū)動(dòng),即設(shè)備的驅(qū)動(dòng)和接口的驅(qū)動(dòng).is_usb_device()的代碼如下:staticinlineintis_usb_device(conststructdevice*dev){returndev->type==&usb_device_type;}很明顯,對(duì)于roothub來(lái)說(shuō),這個(gè)判斷是肯定會(huì)滿(mǎn)足的.staticinlineintis_usb_device_driver(structdevice_driver*drv){returncontainer_of(drv,structusbdrv_wrap,driver)->for_devices;}回憶一下,我們?cè)诜治鰑sb_register_device_driver()的時(shí)候,不是將new_udriver->drvwrap.for_devices置為了1么?所以對(duì)于usb_register_device_driver()注冊(cè)的驅(qū)動(dòng)來(lái)說(shuō),這里也是會(huì)滿(mǎn)足的.因此,對(duì)應(yīng)roothub的情況,從第一個(gè)if就會(huì)匹配到usb_register_device_driver()注冊(cè)的驅(qū)動(dòng).對(duì)于接口的驅(qū)動(dòng),我們等遇到的時(shí)候再來(lái)進(jìn)行分析.4.2:roothub的驅(qū)動(dòng)入口既然我們知道,roothub會(huì)匹配到usb_bus_type->match()的驅(qū)動(dòng),那這個(gè)驅(qū)動(dòng)到底是什么呢?我們從usb子系統(tǒng)的初始化開(kāi)始說(shuō)起.在linux-2.6.25/drivers/usb/core/usb.c中.有這樣的一段代碼:subsys_initcall(usb_init);對(duì)于subsys_initcall()我們已經(jīng)不陌生了,在很多地方都會(huì)遇到它.在系統(tǒng)初始化的時(shí)候,會(huì)調(diào)用到它對(duì)應(yīng)的函數(shù).在這里,即為usb_init().在usb_init()中,有這樣的代碼片段:staticint__initusb_init(void){…………retval=usb_register_device_driver(&usb_generic_driver,THIS_MODULE);if(!retval)gotoout;……}在這里終于看到usb_register_device_driver()了.usb_generic_driver會(huì)匹配到所有usb設(shè)備.定義如下:structusb_device_driverusb_generic_driver={.name=“usb”,.probe=generic_probe,.disconnect=generic_disconnect,#ifdefCONFIG_PM.suspend=generic_suspend,.resume=generic_resume,#endif.supports_autosuspend=1,};現(xiàn)在是到分析probe()的時(shí)候了.我們這里說(shuō)的并不是usb_generic_driver中的probe,而是封裝在structusb_device_driver中的driver對(duì)應(yīng)的probe函數(shù).在上面的分析,usb_register_device_driver()將封裝的driver的probe()函數(shù)設(shè)置為了usb_probe_device().代碼如下:staticintusb_probe_device(structdevice*dev){structusb_device_driver*udriver=to_usb_device_driver(dev->driver);structusb_device*udev;interror=-ENODEV;dev_dbg(dev,“%s\n”,__FUNCTION__);//再次判斷dev是否是usbdeviceif(!is_usb_device(dev))/*Sanitycheck*/returnerror;udev=to_usb_device(dev);/*TODO:Addrealmatchingcode*//*Thedeviceshouldalwaysappeartobeinuse*unlessthedriversuportsautosuspend.*///pm_usage_cnt:autosuspend計(jì)數(shù).如果此計(jì)數(shù)為1,則不允許autosuspendudev->pm_usage_cnt=!(udriver->supports_autosuspend);error=udriver->probe(udev);returnerror;}首先,可以通過(guò)container_of()將封裝的structdevice,structdevice_driver轉(zhuǎn)換為structusb_device和structusb_device_driver.然后,再執(zhí)行一次安全檢查,判斷dev是否是屬于一個(gè)usbdevice.在這里,我們首次接觸到了hubsuspend.如果不支持suspend(udriver->supports_autosuspend為0),則udev->pm_usage_cnt被設(shè)為1,也就是說(shuō),它不允許設(shè)備suspend.否則,將其初始化為0.最后,正如你所看到的,流程轉(zhuǎn)入到了usb_device_driver->probe().對(duì)應(yīng)到roothub,流程會(huì)轉(zhuǎn)入到generic_probe().代碼如下:staticintgeneric_probe(structusb_device*udev){interr,c;/*putdevice-specificfilesintosysfs*/usb_create_sysfs_dev_files(udev);/*Chooseandsettheconfiguration.Thisregisterstheinterfaces*withthedrivercoreandletsinterfacedriversbindtothem.*/if(udev->authorized==0)dev_err(&udev->dev,“Deviceisnotauthorizedforusage\n”);else{//選擇和設(shè)定一個(gè)配置c=usb_choose_configuration(udev);if(c>=0){err=usb_set_configuration(udev,c);if(err){dev_err(&udev->dev,“can’tsetconfig#%d,error%d\n”,c,err);/*Thisneednotbefatal.Theusercantryto*setotherconfigurations.*/}}}/*USBdevicestate==configured...usable*/usb_notify_add_device(udev);return0;}usb_create_sysfs_dev_files()是在sysfs中顯示幾個(gè)屬性文件,不進(jìn)行詳細(xì)分析,有興趣的可以結(jié)合之前分析的>來(lái)看下代碼.usb_notify_add_device()是有關(guān)notify鏈表的操作,這里也不做詳細(xì)分析.至于udev->authorized,在roothub的初始化中,是會(huì)將其初始化為1的.后面的邏輯就更簡(jiǎn)單了.為roothub選擇一個(gè)配置然后再設(shè)定這個(gè)配置.還記得我們?cè)诜治鰎oothub的時(shí)候,在usb_new_device()中,會(huì)將設(shè)備的所有配置都取出來(lái),然后將它們放到了usb_device->config.現(xiàn)在這些信息終于會(huì)派上用場(chǎng)了.不太熟悉的,可以看下本站之前有關(guān)usb控制器驅(qū)動(dòng)的文檔.Usb2.0spec上規(guī)定,對(duì)于hub設(shè)備,只能有一個(gè)config,一個(gè)interface,一個(gè)endpoint.實(shí)際上,在這里,對(duì)hub的選擇約束不大,反正就一個(gè)配置,不管怎么樣,選擇和設(shè)定都是這個(gè)配置.不過(guò),為了方便以后的分析,我們還是跟進(jìn)去看下usb_choose_configuration()和usb_set_configuration()的實(shí)現(xiàn).實(shí)際上,經(jīng)過(guò)這兩個(gè)函數(shù)之后,設(shè)備的probe()過(guò)程也就會(huì)結(jié)束了.4.2.1:usb_choose_configuration()函數(shù)分析usb_choose_configuration()的代碼如下://為usbdevice選擇一個(gè)合適的配置intusb_choose_configuration(structusb_device*udev){inti;intnum_configs;intinsufficient_power=0;structusb_host_config*c,*best;best=NULL;//config數(shù)組c=udev->config;//config項(xiàng)數(shù)num_configs=udev->descriptor.bNumConfigurations;//遍歷所有配置項(xiàng)for(i=0;istructusb_interface_descriptor*desc=NULL;/*It’spossiblethataconfighasnointerfaces!*///配置項(xiàng)的接口數(shù)目//取配置項(xiàng)的第一個(gè)接口if(c->desc.bNumInterfaces>0)desc=&c->intf_cache[0]->altsetting->desc;/**HP’sUSBbus-poweredkeyboardhasonlyoneconfiguration*anditclaimstobeself-powered;otherdevicesmayhave*similarerrorsintheirdescriptors.Ifthenexttest*wereallowedtoexecute,suchconfigurationswouldalways*berejectedandthedeviceswouldnotworkasexpected.*Inthemeantime,weruntheriskofselectingaconfig*thatrequiresexternalpoweratatimewhenthatpower*isn’tavailable.Itseemstobethelesseroftwoevils.**Bugzilla#6448reportsadevicethatappearstocrash*whenitreceivesaGET_DEVICE_STATUSrequest!Wedon’t*haveanyotherwaytotellwhetheradeviceisself-powered,*butsincewedon’tusethatinformationanywherebuthere,*thecallhasbeenremoved.**MaybetheGET_DEVICE_STATUScallandthetestbelowcan*bereinstatedwhendevicefirmwaresbecomemorereliable.*Don’tholdyourbreath.*/#if0/*Ruleoutself-poweredconfigsforabus-powereddevice*/if(bus_powered&&(c->desc.bmAttributes&USB_CONFIG_ATT_SELFPOWER))continue;#endif/**Thenexttestmaynotbeaseffectiveasitshouldbe.*Somehubshaveerrorsintheirdescriptor,claiming*tobeself-poweredwhentheyarereallybus-powered.*Wewilloverestimatetheamountofcurrentsuchhubs*makeavailableforeachport.**Thisisafairlybenignsortoffailure.Itwon’t*causeustorejectconfigurationsthatweshouldhave*accepted.*//*Ruleoutconfigsthatdrawtoomuchbuscurrent*///電源不足.配置描述符中的電力是所需電力的1/2if(c->desc.bMaxPower*2>udev->bus_mA){insufficient_power++;continue;}/*Whenthefirstconfig’sfirstinterfaceisoneofMicrosoft’s*petnonstandardEthernet-over-USBprotocols,ignoreitunless*thiskernelhasenabledthenecessaryhostsidedriver.*/if(i==0&&desc&&(is_rndis(desc)||is_activesync(desc))){#if!defined(CONFIG_USB_NET_RNDIS_HOST)&&!defined(CONFIG_USB_NET_RNDIS_HOST_MODULE)continue;#elsebest=c;#endif}/*Fromtheremainingconfigs,choosethefirstonewhose*firstinterfaceisforanon-vendor-specificclass.*Reason:Linuxismorelikelytohaveaclassdriver*thanavendor-specificdriver.*///選擇一個(gè)不是USB_CLASS_VENDOR_SPEC的配置elseif(udev->descriptor.bDeviceClass!=USB_CLASS_VENDOR_SPEC&&(!desc||desc->bInterfaceClass!=USB_CLASS_VENDOR_SPEC)){best=c;break;}/*Ifalltheremainingconfigsarevendor-specific,*choosethefirstone.*/elseif(!best)best=c;}if(insufficient_power>0)dev_info(&udev->dev,“rejected%dconfiguration%s““duetoinsufficientavailablebuspower\n”,insufficient_power,plural(insufficient_power));//如果選擇好了配置,返回配置的序號(hào),否則,返回-1if(best){i=best->desc.bConfigurationValue;dev_info(&udev->dev,“configuration#%dchosenfrom%dchoice%s\n”,i,num_configs,plural(num_configs));}else{i=-1;dev_warn(&udev->dev,“noconfigurationchosenfrom%dchoice%s\n”,num_configs,plural(num_configs));}returni;}Linux按照自己的喜好選擇好了配置之后,返回配置的序號(hào).不過(guò)對(duì)于HUB來(lái)說(shuō),它有且僅有一個(gè)配置.4.2.2:usb_set_configuration()函數(shù)分析既然已經(jīng)選好配置了,那就告訴設(shè)備選好的配置,這個(gè)過(guò)程是在usb_set_configuration()中完成的.它的代碼如下:intusb_set_configuration(structusb_device*dev,intconfiguration){inti,ret;structusb_host_config*cp=NULL;structusb_interface**new_interfaces=NULL;intn,nintf;if(dev->authorized==0||configuration==-1)configuration=0;else{for(i=0;idescriptor.bNumConfigurations;i++){if(dev->config.desc.bConfigurationValue==configuration){cp=&dev->config;break;}}}if((!cp&&configuration!=0))return-EINVAL;/*TheUSBspecsaysconfiguration0meansunconfigured.*Butifadeviceincludesaconfigurationnumbered0,*wewillacceptitasacorrectlyconfiguredstate.*Use-1ifyoureallywanttounconfigurethedevice.*/if(cp&&configuration==0)dev_warn(&dev->dev,“config0descriptor??\n”);首先,根據(jù)選擇好的配置號(hào)找到相應(yīng)的配置,在這里要注意了,dev->config[]數(shù)組中的配置并不是按照配置的序號(hào)來(lái)存放的,而是按照遍歷到順序來(lái)排序的.因?yàn)橛行┰O(shè)備在發(fā)送配置描述符的時(shí)候,并不是按照配置序號(hào)來(lái)發(fā)送的,例如,配置2可能在第一次GET_CONFIGURATION就被發(fā)送了,而配置1可能是在第二次GET_CONFIGURATION才能發(fā)送.取得配置描述信息之后,要對(duì)它進(jìn)行有效性判斷,注意一下本段代碼的最后幾行代碼:usb2.0spec上規(guī)定,0號(hào)配置是無(wú)效配置,但是可能有些廠(chǎng)商的設(shè)備并末按照這一約定,所以在linux中,遇到這種情況只是打印出警告信息,然后嘗試使用這一配置./*Allocatememoryfornewinterfacesbeforedoinganythingelse,*sothatifwerunoutthennothingwillhavechanged.*/n=nintf=0;if(cp){//接口總數(shù)nintf=cp->desc.bNumInterfaces;//interface指針數(shù)組,new_interfaces=kmalloc(nintf*sizeof(*new_interfaces),GFP_KERNEL);if(!new_interfaces){dev_err(&dev->dev,“Outofmemory\n”);return-ENOMEM;}for(;nnew_interfaces[n]=kzalloc(sizeof(structusb_interface),GFP_KERNEL);if(!new_interfaces[n]){dev_err(&dev->dev,“Outofmemory\n”);ret=-ENOMEM;free_interfaces:while(--n>=0)kfree(new_interfaces[n]);kfree(new_interfaces);returnret;}}//如果總電源小于所需電流,打印警告信息i=dev->bus_mA-cp->desc.bMaxPower*2;if(idev_warn(&dev->dev,“newconfig#%dexceedspower““l(fā)imitby%dmA\n”,configuration,-i);}在這里,注要是為new_interfaces分配空間,要這意的是,new_interfaces是一個(gè)二級(jí)指針,它的最終指向是structusb_interface結(jié)構(gòu).特別的,如果總電流數(shù)要小于配置所需電流,則打印出警告消息.實(shí)際上,這種情況在usb_choose_configuration()中已經(jīng)進(jìn)行了過(guò)濾./*WakeupthedevicesowecansendittheSet-Configrequest*///要對(duì)設(shè)備進(jìn)行配置了,先喚醒它ret=usb_autoresume_device(dev);if(ret)gotofree_interfaces;/*ifit’salreadyconfigured,clearoutoldstatefirst.*gettingridofoldinterfacesmeansunbindingtheirdrivers.*///不是處于A(yíng)DDRESS狀態(tài),先清除設(shè)備的狀態(tài)if(dev->state!=USB_STATE_ADDRESS)usb_disable_device(dev,1);/*Skipep0*///發(fā)送控制消息,選取配置ret=usb_control_msg(dev,usb_sndctrlpipe(dev,0),USB_REQ_SET_CONFIGURATION,0,configuration,0,NULL,0,USB_CTRL_SET_TIMEOUT);if(ret/*Alltheoldstateisgone,sowhatelsecanwedo?*Thedeviceisprobablyuselessnowanyway.*/cp=NULL;}//dev->actconfig存放的是當(dāng)前設(shè)備選取的配置dev->actconfig=cp;if(!cp){usb_set_device_state(dev,USB_STATE_ADDRESS);usb_autosuspend_device(dev);gotofree_interfaces;}//將狀態(tài)設(shè)為CONFIGUREDusb_set_device_state(dev,USB_STATE_CONFIGURED);接下來(lái),就要對(duì)設(shè)備進(jìn)行配置了,首先,將設(shè)備喚醒.回憶一下我們?cè)诜治鯱HCI驅(qū)動(dòng)時(shí),列出來(lái)的設(shè)備狀態(tài)圖.只有在A(yíng)DDRESS狀態(tài)才能轉(zhuǎn)入到CONFIG狀態(tài).(SUSPEND狀態(tài)除外).所以,如果設(shè)備當(dāng)前不是處于A(yíng)DDRESS狀態(tài),就需要將設(shè)備的狀態(tài)初始化.usb_disable_device()函數(shù)是個(gè)比較重要的操作,在接下來(lái)再對(duì)它進(jìn)行詳細(xì)分析.接著,發(fā)送SET_CONFIGURATION的Control消息給設(shè)備,用來(lái)選擇配置最后,將dev->actconfig指向選定的配置,將設(shè)備狀態(tài)設(shè)為CONFIG/*Initializethenewinterfacestructuresandthe*hc/hcd/usbcoreinterface/endpointstate.*///遍歷所有的接口for(i=0;istructusb_interface_cache*intfc;structusb_interface*intf;structusb_host_interface*alt;cp->interface=intf=new_interfaces;intfc=cp->intf_cache;intf->altsetting=intfc->altsetting;intf->num_altsetting=intfc->num_altsetting;//是否關(guān)聯(lián)的接口描述符,定義在minorusb2.0spec中intf->intf_assoc=find_iad(dev,cp,i);kref_get(&intfc->ref);//選擇0號(hào)設(shè)置alt=usb_altnum_to_altsetting(intf,0);/*Noaltsetting0?We’llassumethefirstaltsetting.*WecoulduseaGetInterfacecall,butifadeviceis*sonon-compliantthatitdoesn’thavealtsetting0*thenIwouldn’ttrustitsreplyanyway.*///如果0號(hào)設(shè)置不存在,選排在第一個(gè)設(shè)置if(!alt)alt=&intf->altsetting[0];//當(dāng)前的配置intf->cur_altsetting=alt;usb_enable_interface(dev,intf);intf->dev.parent=&dev->dev;intf->dev.driver=NULL;intf->dev.bus=&usb_bus_type;intf->dev.type=&usb_if_device_type;intf->dev.dma_mask=dev->dev.dma_mask;device_initialize(&intf->dev);mark_quiesced(intf);sprintf(&intf->dev.bus_id[0],“%d-%s:%d.%d”,dev->bus->busnum,dev->devpath,configuration,alt->desc.bInterfaceNumber);}kfree(new_interfaces);if(cp->string==NULL)cp->string=usb_cache_string(dev,cp->desc.iConfiguration);之前初始化的new_interfaces在這里終于要派上用場(chǎng)了.初始化各接口,從上面的初始化過(guò)程中,我們可以看出:Intf->altsetting,表示接口的各種設(shè)置Intf->num_altsetting:表示接口的設(shè)置數(shù)目Intf->intf_assoc:接口的關(guān)聯(lián)接口(定義于minorusb2.0spec)Intf->cur_altsetting:接口的當(dāng)前設(shè)置.結(jié)合之前在UHCI中的分析,我們總結(jié)一下:Usb_dev->config,其實(shí)是一個(gè)數(shù)組,存放設(shè)備的配置.usb_dev->config[m]->interface[n]表示第m個(gè)配置的第n個(gè)接口的intercace結(jié)構(gòu).(m,bsp;dev->bus->busnum,dev->devpath,configuration,alt->desc.bInterfaceNumber);dev指的是這個(gè)接口所屬的usb_dev,結(jié)合我們之前在UHCI中關(guān)于usb設(shè)備命名方式的描述.可得出它的命令方式如下:USB總線(xiàn)號(hào)-設(shè)備路徑:配置號(hào).接口號(hào).例如,在我的虛擬機(jī)上:[root@localhostdevices]#pwd/sys/bus/usb/devices[root@localhostdevices]#ls1-0:1.0usb1[root@localhostdevices]#可以得知,系統(tǒng)只有一個(gè)usbcontrol.1-0:1.0:表示,第一個(gè)usbcont意思上看來(lái),它是標(biāo)記接口為停止?fàn)顟B(tài).它的”反函數(shù)”是mark_active().兩個(gè)函數(shù)如下示:staticinlinevoidmark_active(structusb_interface*f){f->is_active=1;f->dev.power.power_state.event=PM_EVENT_ON;}staticinlinevoidmark_quiesced(structusb_interface*f){f->is_active=0;f->dev.power.power_state.event=PM_EVENT_SUSPEND;}從代碼看來(lái),它只是對(duì)接口的活動(dòng)標(biāo)志(is_active)進(jìn)行了設(shè)置./*Nowthatalltheinterfacesaresetup,registerthem*totriggerbindingofdriverstobe()*routinesmayinstalldifferentaltsettingsandmay*claim()anyinterfacesnotyetbound.Manyclassdrivers*needthat:CDC,audio,video,etc.*///注冊(cè)每一個(gè)接口?for(i=0;istructusb_interface*intf=cp->interface;dev_dbg(&dev->dev,“adding%s(config#%d,interface%d)\n”,intf->dev.bus_id,configuration,intf->cur_altsetting->desc.bInterfaceNumber);ret=device_add(&intf->dev);if(ret!=0){dev_err(&dev->dev,“device_add(%s)-->%d\n”,intf->dev.bus_id,ret);continue;}usb_create_sysfs_intf_files(intf);}//使設(shè)備suspendusb_autosuspend_device(dev);return0;}最后,注冊(cè)intf內(nèi)嵌的device結(jié)構(gòu).設(shè)備配置完成了,為了省電,可以將設(shè)備置為SUSPEND狀態(tài).這個(gè)函數(shù)中還有幾個(gè)比較重要的子函數(shù),依次分析如下:1:usb_disable_device()函數(shù).顧名思義,這個(gè)函數(shù)是將設(shè)備disable掉.代碼如下:voidusb_disable_device(structusb_device*dev,intskip_ep0){inti;dev_dbg(&dev->dev,“%snuking%sURBs\n”,__FUNCTION__,skip_ep0?“non-ep0”:“all”);for(i=skip_ep0;iusb_disable_endpoint(dev,i);usb_disable_endpoint(dev,i+USB_DIR_IN);}dev->toggle[0]=dev->toggle[1]=0;/*gettingridofinterfaceswilldisconnect*anydriversboundtothem(akeysideeffect)*/if(dev->actconfig){for(i=0;iactconfig->desc.bNumInterfaces;i++){structusb_interface*interface;/*removethisinterfaceifithasbeenregistered*/interface=dev->actconfig->interface;if(!device_is_registered(&interface->dev))continue;dev_dbg(&dev->dev,“unregisteringinterface%s\n”,interface->dev.bus_id);usb_remove_sysfs_intf_files(interface);device_del(&interface->dev);}/*Nowthattheinterfacesareunbound,nobodyshould*trytoaccessthem.*/for(i=0;iactconfig->desc.bNumInterfaces;i++){put_device(&dev->actconfig->interface->dev);dev->actconfig->interface=NULL;}dev->actconfig=NULL;if(dev->state==USB_STATE_CONFIGURED)usb_set_device_state(dev,USB_STATE_ADDRESS);}}第二個(gè)參數(shù)是skip_ep0.是表示是否跳過(guò)ep0.為1表示跳過(guò),為0表示清除掉設(shè)備中的所有endpoint.這個(gè)函數(shù)可以分為兩個(gè)部份,一部份是對(duì)usb_dev中的endpoint進(jìn)行操作,一方面是釋放usb_dev的選定配置項(xiàng).對(duì)于第一部份:從代碼中可能看到,如果skip_ep0為1,那就是從1開(kāi)始循環(huán),所以,就跳過(guò)了ep0.另外,一個(gè)端點(diǎn)號(hào)對(duì)應(yīng)了兩個(gè)端點(diǎn),一個(gè)IN,一個(gè)OUT.IN端點(diǎn)比OUT端點(diǎn)要大USB_DIR_IN.另外,既然設(shè)備都已經(jīng)被禁用了,那toggle也應(yīng)該回歸原位了.因些將兩個(gè)方向的toggle都設(shè)為0.usb_disable_endpoint()是一個(gè)很有意思的處理.它的代碼如下:voidusb_disable_endpoint(structusb_device*dev,unsignedintepaddr){unsignedintepnum=epaddr&USB_ENDPOINT_NUMBER_MASK;structusb_host_endpoint*ep;if(!dev)return;//在dev->ep_out和dev->ep_in刪除endpointif(usb_endpoint_out(epaddr)){ep=dev->ep_out[epnum];dev->ep_out[epnum]=NULL;}else{ep=dev->ep_in[epnum];dev->ep_in[epnum]=NULL;}//禁用掉此ep.包括刪除ep上提交的urb和ep上的QHif(ep){ep->enabled=0;usb_hcd_flush_endpoint(dev,ep);usb_hcd_disable_endpoint(dev,ep);}}在dev->ep_in[]/dev->ep_out[]中刪除endpoint,這點(diǎn)很好理解.比較難以理解的是后面的兩個(gè)操作,即usb_hcd_flush_endpoint()和usb_hcd_disable_endpoint().根據(jù)之前分析的UHCI的驅(qū)動(dòng),我們得知,對(duì)于每個(gè)endpoint都有一個(gè)傳輸?shù)膓h,這個(gè)qh上又掛上了要傳輸?shù)膗rb.因此,這兩個(gè)函數(shù)一個(gè)是刪除urb,一個(gè)是刪除qh.usb_hcd_flush_endpoint()的代碼如下:voidusb_hcd_flush_endpoint(structusb_device*udev,structusb_host_endpoint*ep){structusb_hcd*hcd;structurb*urb;if(!ep)return;might_sleep();hcd=bus_to_hcd(udev->bus);/*Nomoresubmitscanoccur*///在提交urb時(shí),將urb加到ep->urb_list上的時(shí)候要持鎖//因此,這里持鎖的話(huà),無(wú)法發(fā)生中斷和提交urbspin_lock_irq(&hcd_urb_list_lock);rescan://將掛在ep->urb_list上的所有urbunlink.注意這里unlink一般只會(huì)設(shè)置urb->unlinked的//值,不會(huì)將urb從ep->urb_list上刪除.只有在UHCI的中斷處理的時(shí)候,才會(huì)調(diào)用//uhci_giveback_urb()將其從ep->urb_list中刪除list_for_each_entry(urb,&ep->urb_list,urb_list){intis_in;if(urb->unlinked)continue;usb_get_urb(urb);is_in=usb_urb_dir_in(urb);spin_unlock(&hcd_urb_list_lock);/*kickhcd*/unlink1(hcd,urb,-ESHUTDOWN);dev_dbg(hcd->self.controller,“shutdownurb%pep%d%s%s\n”,urb,usb_endpoint_num(&ep->desc),is_in?“in”:“out”,({char*s;switch(usb_endpoint_type(&ep->desc)){caseUSB_ENDPOINT_XFER_CONTROL:s=““;break;caseUSB_ENDPOINT_XFER_BULK:s=“-bulk”;break;caseUSB_ENDPOINT_XFER_INT:s=“-intr”;break;default:s=“-iso”;break;};s;}));usb_put_urb(urb);/*listcontentsmayhavechanged*///在這里解開(kāi)鎖了,對(duì)應(yīng)ep->urb_list上又可以提交urb.//這里釋放釋的話(huà),主要是為了能夠產(chǎn)生中斷spin_lock(&hcd_urb_list_lock);gotorescan;}spin_unlock_irq(&hcd_urb_list_lock);/*Waituntiltheendpointqueueiscompletelyempty*///等待urb被調(diào)度完while(!list_empty(&ep->urb_list)){spin_lock_irq(&hcd_urb_list_lock);/*Thelistmayhavechangedwhileweacquiredthespinlock*/urb=NULL;if(!list_empty(&ep->urb_list)){urb=list_entry(ep->urb_list.prev,structurb,urb_list);usb_get_urb(urb);}spin_unlock_irq(&hcd_urb_list_lock);if(urb){usb_kill_urb(urb);usb_put_urb(urb);}}}仔細(xì)體會(huì)這里的代碼,為什么在前一個(gè)循環(huán)中,要使用gotorescan重新開(kāi)始這個(gè)循環(huán)呢?這是因?yàn)樵诤竺嬉呀?jīng)將自旋鎖釋放了,因此,就會(huì)有這樣的可能,在函數(shù)中操作的urb,可能已經(jīng)被調(diào)度完釋放了.因此,再對(duì)這個(gè)urb操作就會(huì)產(chǎn)生錯(cuò)誤.所以,需要重新開(kāi)始這個(gè)循環(huán).那后一個(gè)循環(huán)又是干什么的呢?后一個(gè)循環(huán)就是等待urb被調(diào)度完.有人就會(huì)有這樣的疑問(wèn)了,這里一邊等待,然后endpoint一邊還提交urb,那這個(gè)函數(shù)豈不是要耗掉很長(zhǎng)時(shí)間?在這里,不要忘記了前面的操作,在調(diào)這個(gè)函數(shù)之前,usb_disable_endpoint()已經(jīng)將這個(gè)endpoint禁用了,也就是說(shuō)該endpoint不會(huì)產(chǎn)生新的urb.因?yàn)?在后一個(gè)循環(huán)中,只需要等待那些被unlink的urb調(diào)度完即可.在usb_kill_urb()中,會(huì)一直等待,直到這個(gè)urb被調(diào)度完成為止.可能有人又會(huì)有這樣的疑問(wèn):Usb_kill_urb()中也有unlinkurb的操作,為什么這里要分做兩個(gè)循環(huán)呢?另外的一個(gè)函數(shù)是usb_hcd_disable_endpoint().代碼如下:voidusb_hcd_disable_endpoint(structusb_device*udev,structusb_host_endpoint*ep){structusb_hcd*hcd;might_sleep();hcd=bus_to_hcd(udev->bus);if(hcd->driver->endpoint_disable)hcd->driver->endpoint_disable(hcd,ep);}從上面的代碼可以看到,操作轉(zhuǎn)向了hcd->driver的endpoint_disable()接口.以UHCI為例.在UHCI中,對(duì)應(yīng)的接口為:staticvoiduhci_hcd_endpoint_disable(structusb_hcd*hcd,structusb_host_endpoint*hep){structuhci_hcd*uhci=hcd_to_uhci(hcd);structuhci_qh*qh;spin_lock_irq(&uhci->lock);qh=(structuhci_qh*)hep->hcpriv;if(qh==NULL)gotodone;while(qh->state!=QH_STATE_IDLE){++uhci->num_waiting;spin_unlock_irq(&uhci->lock);wait_event_interruptible(uhci->waitqh,qh->state==QH_STATE_IDLE);spin_lock_irq(&uhci->lock);--uhci->num_waiting;}uhci_free_qh(uhci,qh);done:spin_unlock_irq(&uhci->lock);}這個(gè)函數(shù)沒(méi)啥好說(shuō)的,就是在uhci->waitqh上等待隊(duì)列狀態(tài)變?yōu)镼H_STATE_IDLE.來(lái)回憶一下,qh在什么情況下才會(huì)變?yōu)镼H_STATE_IDLE呢?是在qh沒(méi)有待傳輸?shù)膗rb的時(shí)候.然后,將qh釋放.現(xiàn)在我們來(lái)接著看usb_disable_device()的第二個(gè)部份.第二部份主要是針對(duì)dev->actconfig進(jìn)行的操作,dev->actconfig存放的是設(shè)備當(dāng)前的配置,現(xiàn)在要將設(shè)備設(shè)回Address狀態(tài).就些東西當(dāng)然是用不了上的了.釋放dev->actconfig->interface[]中的各元素,注意不要將dev->actconfig->interface[]所指向的信息釋放了,它都是指向dev->config[]->intf_cache[]中的東西,這些東西一釋放,usbdevice在Get_Configure所獲得的信息就會(huì)部丟失了.就這樣,usb_disable_device()函數(shù)也走到了尾聲.2:usb_cache_string()函數(shù)這個(gè)函數(shù)我們?cè)诜治鯱HCI的時(shí)候已經(jīng)接觸過(guò),但末做詳細(xì)的分析.首先了解一下這個(gè)函數(shù)的作用,有時(shí)候,為了形象的說(shuō)明,會(huì)提供一個(gè)字符串形式的說(shuō)明.例如,對(duì)于配置描述符來(lái)說(shuō),它的iConfiguration就表示一個(gè)字符串索引,然后用Get_String就可以取得這個(gè)索引所對(duì)應(yīng)的字串了.不過(guò),事情并不是這么簡(jiǎn)單.因?yàn)樽址畬?duì)應(yīng)不同的編碼,所以這里還會(huì)對(duì)應(yīng)有編碼的處理.來(lái)看具體的代碼:char*usb_cache_string(structusb_device*udev,intindex){char*buf;char*smallbuf=NULL;intlen;if(indexreturnNULL;//不知道字符到底有多長(zhǎng),就按最長(zhǎng)256字節(jié)處理buf=kmalloc(256,GFP_KERNEL);if(buf){len=usb_string(udev,index,buf,256);//取到字符了,分配合適的長(zhǎng)度if(len>0){smallbuf=kmalloc(++len,GFP_KERNEL);if(!smallbuf)returnbuf;//將字符copy過(guò)去memcpy(smallbuf,buf,len);}//釋放舊空間kfree(buf);}returnsmallbuf;}這個(gè)函數(shù)沒(méi)啥好說(shuō)的,流程轉(zhuǎn)入到usb_string中.代碼如下:intusb_string(structusb_device*dev,intindex,char*buf,size_tsize){unsignedchar*tbuf;interr;unsignedintu,idx;if(dev->state==USB_STATE_SUSPENDED)return-EHOSTUNREACH;if(sizereturn-EINVAL;buf[0]=0;tbuf=kmalloc(256,GFP_KERNEL);if(!tbuf)return-ENOMEM;/*getlangidforstringsifit’snotyetknown*///先取得設(shè)備支持的編碼IDif(!dev->have_langid){//以0號(hào)序號(hào)和編碼0,Get_String就可得到設(shè)備所支持的編碼列表err=usb_string_sub(dev,0,0,tbuf);//如果發(fā)生了錯(cuò)誤,或者是取得的數(shù)據(jù)超短(最短為4字節(jié))if(errdev_err(&dev->dev,“stringdescriptor0readerror:%d\n”,err);gotoerrout;}elseif(errdev_err(&dev->dev,“stringdescriptor0tooshort\n”);err=-EINVAL;gotoerrout;}//取設(shè)備支持的第一個(gè)編碼else{dev->have_langid=1;dev->string_langid=tbuf[2]|(tbuf[3]/*alwaysusethefirstlangidlisted*/dev_dbg(&dev->dev,“defaultlanguage0x%04x\n”,dev->string_langid);}}//以編碼ID和序號(hào)Index作為參數(shù)Get_String取得序號(hào)對(duì)應(yīng)的字串err=usb_string_sub(dev,dev->string_langid,index,tbuf);if(errgotoerrout;//空一個(gè)字符來(lái)用來(lái)存放結(jié)束符size--;/*leaveroomfortrailingNULLcharinoutputbuffer*///兩字節(jié)一組,(Unicode編碼的)for(idx=0,u=2;uif(idx>=size)break;//如果高字節(jié)有值,說(shuō)明它不是ISO-8859-1編碼的,將它置為?//否則,就將低位的值存放到buf中if(tbuf[u+1])/*highbyte*/buf[idx++]=‘?’;/*nonISO-8859-1character*/elsebuf[idx++]=tbuf;}//在最后一位賦0,字串結(jié)尾buf[idx]=0;//返回字串的長(zhǎng)度,(算上了最后的結(jié)尾字符)err=idx;//如果該描述符不是STRING描述符,打印出錯(cuò)誤提示if(tbuf[1]!=USB_DT_STRING)dev_dbg(&dev->dev,“wrongdescriptortype%02xforstring%d(\”%s\”)\n”,tbuf[1],index,buf);errout://釋放空間,返回長(zhǎng)度kfree(tbuf);returnerr;}結(jié)合代碼中的注釋,就很容易理解這一函數(shù)了,在此不對(duì)這一函數(shù)做詳細(xì)分析.跟蹤進(jìn)usb_string_sub().代碼如下:staticintusb_string_sub(structusb_device*dev,unsignedintlangid,unsignedintindex,unsignedchar*buf){intrc;/*Trytoreadthestringdescriptorbyaskingforthemaximum*possiblenumberofbytes*///如果設(shè)備不需要Fixup就發(fā)出Get_Stringif(dev->quirks&USB_QUIRK_STRING_FETCH_255)rc=-EIO;elserc=usb_get_string(dev,langid,index,buf,255);/*Ifthatfailedtrytoreadthedescriptorlength,then*askforjustthatmanybytes*///如果Get_String失敗或者取得長(zhǎng)度有問(wèn)題.就先取字符描述符的頭部//再以實(shí)際的長(zhǎng)度和參數(shù),再次Get_Stringif(rcrc=usb_get_string(dev,langid,index,buf,2);if(rc==2)rc=usb_get_string(dev,langid,index,buf,buf[0]);}//如果成功if(rc>=2){//如果前兩個(gè)字節(jié)為空.則需要找到數(shù)據(jù)的有效起始位置if(!buf[0]&&!buf[1])usb_try_string_workarounds(buf,&rc);/*Theremightbeextrajunkattheendofthedescriptor*///整調(diào)一下描述符的長(zhǎng)度if(buf[0]rc=buf[0];//將rc置為了一個(gè)偶數(shù).rc=rc-(rc&1);/*forceamultipleoftwo*/}//長(zhǎng)度最終小于2.返回錯(cuò)誤值if(rcrc=(rcreturnrc;}在這個(gè)地方,有個(gè)錯(cuò)誤處理,可能有的設(shè)備你一次用255的長(zhǎng)度去取它對(duì)字符串會(huì)返回一個(gè)錯(cuò)誤,所以,在用255長(zhǎng)度返回錯(cuò)誤的時(shí)候,先以2為長(zhǎng)度取它的描述符頭度,再以描述符的實(shí)際長(zhǎng)度去取字符串描述符串.另外,在描述符的前兩個(gè)字節(jié)都為空的情況下,就需要計(jì)算它的有效長(zhǎng)度.在代碼中,這一工作是由usb_try_string_workarounds()完成的.staticvoidusb_try_string_workarounds(unsignedchar*buf,int*length){intnewlength,oldlength=*length;//前兩個(gè)字節(jié)是描述符頭部,所以從2開(kāi)始循環(huán).//Unicode編碼用兩個(gè)字節(jié)來(lái)表示一個(gè)字符.所以每次循環(huán)完了之后要+2for(newlength=2;newlength+1//低字節(jié)是不可打印字符,或者高字節(jié)不為空(不是ISO-8859-1),就退出循環(huán).if(!isprint(buf[newlength])||buf[newlength+1])break;//修正字符串描述符的實(shí)際長(zhǎng)度.//如果newlength等于2.說(shuō)明字符中描述符沒(méi)有帶字串if(newlength>2){buf[0]=newlength;*length=newlength;}}這個(gè)函數(shù)涉及到編碼方面的東東,建議參閱fudan_abc的>,上面的較詳細(xì)的描述.至此,usb_cache_string()完分析完了.到這里,usbdevicedriver的probe過(guò)程也就完成了.五:hub接口驅(qū)動(dòng)分析5.1:接口驅(qū)動(dòng)架構(gòu)是時(shí)候來(lái)分析接口驅(qū)動(dòng)的架構(gòu)了.我們?cè)谏厦婵吹搅私涌谠O(shè)備的注冊(cè).在開(kāi)篇的時(shí)候分析了接口驅(qū)動(dòng)的注冊(cè).我們首先來(lái)分析接口驅(qū)備和接口驅(qū)動(dòng)的匹配.代碼還是在usb_bus_type->match().只不過(guò)是對(duì)應(yīng)另外的一種情況了.將相關(guān)代碼列出:staticintusb_device_match(structdevice*dev,structdevice_driver*drv){……if(is_usb_device(dev)){……}//interface的情況else{structusb_interface*intf;structusb_driver*usb_drv;conststructusb_device_id*id;/*devicedriversnevermatchinterfaces*/if(is_usb_device_driver(drv))return0;intf=to_usb_interface(dev);usb_drv=to_usb_driver(drv);id=usb_match_id(intf,usb_drv->id_table);if(id)return1;id=usb_match_dynamic_id(intf,usb_drv);if(id)return1;}return0;}經(jīng)過(guò)前面的分析,因?yàn)樵谧?cè)接口設(shè)備的時(shí)候,是將type設(shè)為usb_if_device_type,因此,這個(gè)函數(shù)第一個(gè)if是不會(huì)滿(mǎn)足的.首先,將structdevice和structdevice_driver轉(zhuǎn)換為被封裝的structusb_interface和structusb_driver.緊接著,我們看到了兩個(gè)匹配,一個(gè)是usb_match_id().另外一個(gè)是usb_match_dynamic_id().后者只有在前者沒(méi)有匹配成功的情況下才能調(diào)用.我們也可以看到,structusb_driver中一個(gè)structusb_device_id類(lèi)型的數(shù)組(id_table字段)和一個(gè)dynids鏈表.哪id和dyname_id有什
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度新型農(nóng)業(yè)機(jī)械推廣與應(yīng)用合作協(xié)議4篇
- 2025年拆遷還建住宅產(chǎn)權(quán)變更協(xié)議范本4篇
- 二零二四臺(tái)媒披露大S汪小菲離婚協(xié)議財(cái)產(chǎn)分割與子女撫養(yǎng)權(quán)爭(zhēng)奪戰(zhàn)3篇
- 元素周期表解析
- 2025年度企業(yè)搬遷拆遷工程承包合同書(shū)(產(chǎn)業(yè)升級(jí)支持協(xié)議)3篇
- 2025年度智能化廠(chǎng)房租賃及運(yùn)營(yíng)管理合同4篇
- 二零二五版城區(qū)交通隔離護(hù)欄定制采購(gòu)合同3篇
- 個(gè)人健身教練2024年度勞動(dòng)協(xié)議樣本版A版
- 2025年度大數(shù)據(jù)分析公司100%股權(quán)轉(zhuǎn)讓及數(shù)據(jù)共享協(xié)議3篇
- 2025年度水上樂(lè)園場(chǎng)管理辦公室安全管理及運(yùn)營(yíng)合同4篇
- 河南省鄭州外國(guó)語(yǔ)高中-【高二】【上期中】【把握現(xiàn)在 蓄力高三】家長(zhǎng)會(huì)【課件】
- 天津市武清區(qū)2024-2025學(xué)年八年級(jí)(上)期末物理試卷(含解析)
- 《徐霞客傳正版》課件
- 江西硅博化工有限公司年產(chǎn)5000噸硅樹(shù)脂項(xiàng)目環(huán)境影響評(píng)價(jià)
- 2025年中煤電力有限公司招聘筆試參考題庫(kù)含答案解析
- 企業(yè)內(nèi)部控制與財(cái)務(wù)風(fēng)險(xiǎn)防范
- 高端民用航空復(fù)材智能制造交付中心項(xiàng)目環(huán)評(píng)資料環(huán)境影響
- 建設(shè)項(xiàng)目施工現(xiàn)場(chǎng)春節(jié)放假期間的安全管理方案
- 量子醫(yī)學(xué)成像學(xué)行業(yè)研究報(bào)告
- 胃潴留護(hù)理查房
- 污水處理廠(chǎng)運(yùn)營(yíng)方案計(jì)劃
評(píng)論
0/150
提交評(píng)論