嵌入式linux下u盤升級的設計
一.核心配置,配置使其支援u盤
make menu_config
Device Drivers --->
[*]USB support -->
<*> USB Mass Storage support
u盤底層依賴scsi,所以scsi的配置也要配置好
二.設計更新程式碼
我是這麼設計的:寫個應用程式存放在檔案系統的/bin目錄下,取名update,執行這個程式會遍歷 /dev/sd[drive][partition],
執行裡面定義好的指令碼檔案,檔名約定為UpDate,指令碼檔案就可以呼叫busybox的通用Linux命令,rm,mkdir,cp,touch等命令
將u盤上的新二進位制或其他檔案替換掉舊的檔案系統的檔案.
2.1 update程式碼
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <string.h>
- unsigned char ch[8]={'a','b','c','d','e','f','g','h'}; //sda,sdb,sdc
- int main(int argc,
- {
- int fd;
- unsigned char DEV[64]; //u盤等磁碟裝置的裝置檔案路徑
- unsigned char PATH[64]; //Update檔案的路徑
- unsigned char cmd[64]; //系統呼叫的命令
- int i=0;
- int j=0;
- for(j=0;j<4;j++){ //最多支援4個分割槽
- for(i=0;i<8;i++){ //最多8個硬碟
- sprintf(PATH,"/media/sd%c%d/UpDate",ch[i],j);
- sprintf(DEV,"/media/sd%c%d",ch[i],j); //對應的裝置檔案路徑"/media/sda1","/media/sda2"...
- fd=open(PATH,O_RDWR); //開啟檔案全路徑
- if(fd==-1){ //判斷檔案是否存在
- //printf("can not open '%s'\n",PATH);
- continue; //不存在繼續掃描下一個分割槽
- }
- else{ //檔案存在則跳出子迴圈
- printf("open device '%s'\n",PATH);
- break;
- }
- }
- if(fd!=-1) //判斷檔案是否存在
- break; //存在則跳出第二個for迴圈,不存在則繼續下一個磁碟掃描
- }
- if(fd==-1){ //判斷檔案是否存在
- printf("can not find any u pan!\n "); //這表示所有磁碟所有分割槽都沒有UpDate檔案
- }
- else{ //檔案存在
- close(fd); //關閉檔案描述符
- sprintf(cmd,"sh %s %s",PATH,DEV); //設計執行指令碼命令,例如"sh /media/sda1/UpDate /media/sda1"
- system(cmd); //執行該指令碼
- }
- return 0;
- }
這裡cmd將裝置檔案路徑作為第一個引數傳遞給指令碼
那麼執行的腳本里面就可以通過$1獲取DEV(/media/sda1)
可以寫個簡單的指令碼測試下
- #! /bin/sh
- echo -e "update file!"
- echo $1
- ls -l $1
- touch /opt/1234
- echo -e "update file done!"
ok交叉編譯應用程式update然後放在/bin下面吧
到這裡可以在啟動程式碼的執行指令碼中執行/bin/update檔案來執行u盤中UpDate更新程式了
但是事先必須先插上u盤才能在啟動過程中執行啟動指令碼呼叫到update --重啟自動
或者只能插上u盤手工執行update來更新程式. --手動
三.下面來做不用重啟的自動,也就是插上u盤自動執行update
先測試下u盤插入到識別的原理吧
3.1插入u盤列印
- usb 1-1: new high speed USB device using musb-hdrc and address 5
- usb 1-1: New USB device found, idVendor=0951, idProduct=1643
- usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
- usb 1-1: Product: DataTraveler G3
- usb 1-1: Manufacturer: Kingston
- usb 1-1: SerialNumber: 001CC0EC34F1FB90F71729FF
- scsi5 : usb-storage 1-1:1.0
- scsi 5:0:0:0: Direct-Access Kingston DataTraveler G3 1.00 PQ: 0 ANSI: 0 CCS
- sd 5:0:0:0: Attached scsi generic sg0 type 0
- sd 5:0:0:0: [sdb] 15644912 512-byte logical blocks: (8.01 GB/7.45 GiB)
- sd 5:0:0:0: [sdb] Write Protect is off
- sd 5:0:0:0: [sdb] Assuming drive cache: write through
- sd 5:0:0:0: [sdb] Assuming drive cache: write through
- sdb: sdb1
- sd 5:0:0:0: [sdb] Assuming drive cache: write through
- sd 5:0:0:0: [sdb] Attached SCSI removable disk
- FAT: invalid media value (0xb9)
- VFS: Can't find a valid FAT filesystem on dev sdb.
- EXT3-fs (sdb): error: can't find ext3 filesystem on dev sdb.
- EXT2-fs (sdb): error: can't find an ext2 filesystem on dev sdb.
- FAT: invalid media value (0xb9)
- VFS: Can't find a valid FAT filesystem on dev sdb.
- ISOFS: Unable to identify CD-ROM format.
hub_thread守護執行緒[khubd]檢測到hub狀態變化,根hub列舉新的usb裝置,獲取新的usb裝置資訊,建立usb_device(usb裝置),呼叫usb_bus_type的match方法,找到對應的usb驅動(usb_driver),這裡就是usb_storage_driver.匹配之後呼叫usb_storage_driver的probe方法storage_probe,接著usb_stor_probe2函式,接著建立一個usb_stor_scan_thread執行緒來掃描u盤
- usb_stor_scan_thread
- scsi_scan_host
- do_scsi_scan_host
- scsi_scan_host_selected
- scsi_scan_channel
- __scsi_scan_target
- scsi_probe_and_add_lun
- scsi_add_lun
- scsi_sysfs_add_sdev
接著呼叫sisc匯流排scsi_bus_type的match方法,匹配接著sd_probe,接著sd_probe_async同步
接著呼叫/bin/mount建立裝置節點
update的掃描是掃描裝置節點,mount命令會呼叫系統呼叫sys_mount
所以我在sys_mount的方法中呼叫update
sys_mount定義在fs/namespace.c中
- SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
- char __user *, type, unsigned long, flags, void __user *, data)
- {
- int ret;
- char *kernel_type;
- char *kernel_dir;
- char *kernel_dev;
- unsigned long data_page;
- ret = copy_mount_string(type, &kernel_type);
- if (ret < 0)
- goto out_type;
- kernel_dir = getname(dir_name);
- if (IS_ERR(kernel_dir)) {
- ret = PTR_ERR(kernel_dir);
- goto out_dir;
- }
- ret = copy_mount_string(dev_name, &kernel_dev);
- if (ret < 0)
- goto out_dev;
- ret = copy_mount_options(data, &data_page);
- if (ret < 0)
- goto out_data;
- ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,
- (void *) data_page);
- call_usermodehelper ("/bin/update", NULL, NULL, 1); //MHB update ---就加了這句ok
- free_page(data_page);
- out_data:
- kfree(kernel_dev);
- out_dev:
- putname(kernel_dir);
- out_dir:
- kfree(kernel_type);
- out_type:
- return ret;
- }
雖然感覺硬體--應用層--裝置驅動--應用程--核心--應用程--shell這樣的路子彆扭彆扭的,但我覺得對我來說是最簡單的招吧
另一種方法:修改udev規則在/etc/udev/scripts下的mount.sh檔案在"add"分支中新增/bin/update
四.簡單的補充下u盤驅動的分析
1.入口函式
- module_init(usb_stor_init);
- staticint __init usb_stor_init(void)
- {
- int retval;
- printk("usb --- usb_stor_init start\n");
- retval = usb_register(&usb_storage_driver); //註冊u盤裝置驅動
- if (retval == 0)
- printk("ENE USB Mass Storage support registered.\n");
- return retval;
- }
2.u盤裝置驅動
- staticstruct usb_driver usb_storage_driver = {
- .name = "usb-storage", //驅動名
- .probe = storage_probe, //probe方法(u盤插入)
- .disconnect = usb_stor_disconnect, //斷開方法
- .suspend = usb_stor_suspend, //掛起
- .resume = usb_stor_resume, //喚醒
- .reset_resume = usb_stor_reset_resume, //復位喚醒
- .pre_reset = usb_stor_pre_reset, //預復位
- .post_reset = usb_stor_post_reset, //
- .id_table = usb_storage_usb_ids, //支援id表
- .supports_autosuspend = 1,
- .soft_unbind = 1,
- };
3.支援裝置id表
- struct usb_device_id usb_storage_usb_ids[] = {
- # include "unusual_devs.h" //包含標頭檔案unusual_devs.h
- { } /* Terminating entry */
- };
包含了一個unusual_devs.h標頭檔案
該標頭檔案包含了特殊的u盤裝置id資訊,也包含了通用的u盤裝置類資訊
- /* Control/Bulk transport for all SubClass values *///控制/bulk傳輸子類
- USUAL_DEV(USB_SC_RBC, USB_PR_CB, USB_US_TYPE_STOR), //典型的flash裝置
- USUAL_DEV(USB_SC_8020, USB_PR_CB, USB_US_TYPE_STOR), //CD-ROM
- USUAL_DEV(USB_SC_QIC, USB_PR_CB, USB_US_TYPE_STOR), //QIC-157
- USUAL_DEV(USB_SC_UFI, USB_PR_CB, USB_US_TYPE_STOR), //磁碟
- USUAL_DEV(USB_SC_8070, USB_PR_CB, USB_US_TYPE_STOR), //可移動媒介
- USUAL_DEV(USB_SC_SCSI, USB_PR_CB, USB_US_TYPE_STOR), //Transparent
- /* Control/Bulk/Interrupt transport for all SubClass values *///控制/bulk/中斷傳輸子類
- USUAL_DEV(USB_SC_RBC, USB_PR_CBI, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_8020, USB_PR_CBI, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_QIC, USB_PR_CBI, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_UFI, USB_PR_CBI, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_8070, USB_PR_CBI, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_SCSI, USB_PR_CBI, USB_US_TYPE_STOR),
- /* Bulk-only transport for all SubClass values *///bulk傳輸子類
- USUAL_DEV(USB_SC_RBC, USB_PR_BULK, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_8020, USB_PR_BULK, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_QIC, USB_PR_BULK, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_UFI, USB_PR_BULK, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_8070, USB_PR_BULK, USB_US_TYPE_STOR),
- USUAL_DEV(USB_SC_SCSI, USB_PR_BULK, 0),
u盤驅動probe方法
1.probe方法storage_probe
- staticint storage_probe(struct usb_interface *intf,conststruct usb_device_id *id)
- {
- struct us_data *us;
- int result;
- if (usb_usual_check_type(id, USB_US_TYPE_STOR) || usb_usual_ignore_device(intf)) //檢測u盤裝置型別
- return -ENXIO;
- /*
- * Call the general probe procedures.
- */
- result = usb_stor_probe1(&us, intf, id,(id - usb_storage_usb_ids) + us_unusual_dev_list);
- if (result)
- return result;
- /* No special transport or protocol settings in the main module */
- result = usb_stor_probe2(us);
- return result;
- }
storage_probe分成了兩部分,第一部分由usb_stor_probe1完成通用的配置,第二部分由usb_stor_probe2
2.第一部分probe usb_stor_probe1
- int usb_stor_probe1(struct us_data **pus,struct usb_interface *intf,conststruct usb_device_id *id,struct us_unusual_dev *unusual_dev)
- {
- struct Scsi_Host *host;
- struct us_data *us;
- int result;
- US_DEBUGP("USB Mass Storage device detected\n");
- host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us)); //分配Scsi_Host,結構體物件尾部分配us_data物件記憶體
- if (!host) {
- dev_warn(&intf->dev,"Unable to allocate the scsi host\n");
- return -ENOMEM;
- }
- host->max_cmd_len = 16;
- host->sg_tablesize = usb_stor_sg_tablesize(intf);
- *pus = us = host_to_us(host); //(struct us_data *) host->hostdata;
- memset(us, 0, sizeof(struct us_data)); //初始化us_data物件
- mutex_init(&(us->dev_mutex));
- init_completion(&us->cmnd_ready); //初始化completion物件cmnd_ready
- init_completion(&(us->notify)); //初始化completion物件notify
- init_waitqueue_head(&us->delay_wait); //初始化等待佇列物件delay_wait
- init_completion(&us->scanning_done); //初始化completion物件scanning_done
- result = associate_dev(us, intf); //捆綁usb裝置的一些資料
- if (result)
- goto BadDevice;
- result = get_device_info(us, id, unusual_dev); //獲取unusual_dev入口和描述符
- if (result)
- goto BadDevice;
- get_transport(us); //設定傳輸標準
- get_protocol(us); //設定協議
- return 0;
- BadDevice:
- US_DEBUGP("storage_probe() failed\n");
- release_everything(us);
- return result;
- }
- EXPORT_SYMBOL_GPL(usb_stor_probe1);
這裡主要是呼叫了scsi_host_alloc分配了Scsi_host物件
2.1 scsi_host_template物件
- struct scsi_host_template usb_stor_host_template = {
- .name = "usb-storage",
- .proc_name = "usb-storage",
- .proc_info = proc_info,
- .info = host_info,
- .queuecommand = queuecommand,
- .eh_abort_handler = command_abort,
- .eh_device_reset_handler = device_reset,
- .eh_bus_reset_handler = bus_reset,
- .can_queue = 1,
- .cmd_per_lun = 1,
- .this_id = -1,
- .slave_alloc = slave_alloc,
- .slave_configure = slave_configure,
- .sg_tablesize = SCSI_MAX_SG_CHAIN_SEGMENTS,
- .max_sectors = 240,
- .use_clustering = 1,
- .emulated = 1,
- .skip_settle_delay = 1,
- .sdev_attrs = sysfs_device_attr_list,
- .module = THIS_MODULE
- };
在scsi介面底層會呼叫到它的多個方法
3.第二部分probe usb_stor_probe2
- int usb_stor_probe2(struct us_data *us)
- {
- struct task_struct *th;
- int result;
- struct device *dev = &us->pusb_intf->dev;
- if (!us->transport || !us->proto_handler) { //判斷傳輸方式和協議是否設定好
- result = -ENXIO;
- goto BadDevice;
- }
- US_DEBUGP("Transport: %s\n", us->transport_name);
- US_DEBUGP("Protocol: %s\n", us->protocol_name);
- if (us->fflags & US_FL_SINGLE_LUN)
- us->max_lun = 0;
- result = get_pipes(us);
- if (result)
- goto BadDevice;
- result = usb_stor_acquire_resources(us);
- if (result)
- goto BadDevice;
- snprintf(us->scsi_name, sizeof(us->scsi_name), "usb-storage %s",dev_name(&us->pusb_intf->dev));
- result = scsi_add_host(us_to_host(us), dev); //新增Scsi_Host物件
- if (result) {
- dev_warn(dev,"Unable to add the scsi host\n");
- goto BadDevice;
- }
- th = kthread_create(usb_stor_scan_thread, us, "usb-stor-scan"); //建立執行緒
- if (IS_ERR(th)) {
- dev_warn(dev,"Unable to start the device-scanning thread\n");
- complete(&us->scanning_done);
- quiesce_and_remove_host(us);
- result = PTR_ERR(th);
- goto BadDevice;
- }
- usb_autopm_get_interface_no_resume(us->pusb_intf);
- wake_up_process(th);
- return 0;
- BadDevice:
- US_DEBUGP("storage_probe() failed\n");
- release_everything(us);
- return result;
- }
- EXPORT_SYMBOL_GPL(usb_stor_probe2);
這裡添加了Scsi_Host物件並建立了一個執行緒
u盤掃描執行緒
- staticint usb_stor_scan_thread(void * __us)
- {
- struct us_data *us = (struct us_data *)__us;
- struct device *dev = &us->pusb_intf->dev;
- dev_dbg(dev, "device found\n");
- set_freezable();
- if (delay_use > 0) { //延時等待裝置準備好
- dev_dbg(dev, "waiting for device to settle before scanning\n");
- wait_event_freezable_timeout(us->delay_wait,test_bit(US_FLIDX_DONT_SCAN, &us->dflags),delay_use * HZ);
- }
- if (!test_bit(US_FLIDX_DONT_SCAN, &us->dflags)) {
- if (us->protocol == USB_PR_BULK && !(us->fflags & US_FL_SINGLE_LUN)) {
- mutex_lock(&us->dev_mutex);
- us->max_lun = usb_stor_Bulk_max_lun(us);
- mutex_unlock(&us->dev_mutex);
- }
- scsi_scan_host(us_to_host(us)); //掃描scsi介面卡
- dev_dbg(dev, "scan complete\n");
- }
- usb_autopm_put_interface(us->pusb_intf);
- complete_and_exit(&us->scanning_done, 0);
- }
線上程裡會呼叫scsi_scan_host函式方法
這裡面可以總結出u'盤的probe方法與下一層scsi介面的呼叫關係
- -- storage_probe
- scsi_host_alloc -- usb_stor_probe1
- scsi_add_host -- usb_stor_probe2
- scsi_scan_host -- usb_stor_scan_thread執行緒
- |
- slave_alloc()
- slave_configure()
在參考scsi的文件中有介紹這種模型
- Hotplug initialization model
- ============================
- In this model an LLD controls when SCSI hosts are introduced and removed
- from the SCSI subsystem. Hosts can be introduced as early as driver
- initialization and removed as late as driver shutdown. Typically a driver
- will respond to a sysfs probe() callback that indicates an HBA has been
- detected. After confirming that the new device is one that the LLD wants
- to control, the LLD will initialize the HBA and then register a new host
- with the SCSI mid level.
- During LLD initialization the driver should register itself with the
- appropriate IO bus on which it expects to find HBA(s) (e.g. the PCI bus).
- This can probably be done via sysfs. Any driver parameters (especially
- those that are writable after the driver is loaded) could also be
- registered with sysfs at this point. The SCSI mid level first becomes
- aware of an LLD when that LLD registers its first HBA.
- At some later time, the LLD becomes aware of an HBA and what follows
- is a typical sequence of calls between the LLD and the mid level.
- This example shows the mid level scanning the newly introduced HBA for 3
- scsi devices of which only the first 2 respond:
- HBA PROBE: assume 2 SCSI devices found in scan
- LLD mid level LLD
- ===-------------------=========--------------------===------
- scsi_host_alloc() -->
- scsi_add_host() ---->
- scsi_scan_host() -------+
- |
- slave_alloc()
- slave_configure() --> scsi_adjust_queue_depth()
- |
- slave_alloc()
- slave_configure()
- |
- slave_alloc() ***
- slave_destroy() ***
- ------------------------------------------------------------
- If the LLD wants to adjust the default queue settings, it can invoke
- scsi_adjust_queue_depth() in its slave_configure() routine.
- *** For scsi devices that the mid level tries to scan but do not
- respond, a slave_alloc(), slave_destroy() pair is called.
- When an HBA is being removed it could be as part of an orderly shutdown
- associated with the LLD module being unloaded (e.g. with the "rmmod"
- command) or in response to a "hot unplug" indicated by sysfs()'s
- remove() callback being invoked. In either case, the sequence is the
- same:
- HBA REMOVE: assume 2 SCSI devices attached
- LLD mid level LLD
- ===----------------------=========-----------------===------
- scsi_remove_host() ---------+
- |
- slave_destroy()
- slave_destroy()
- scsi_host_put()
- ------------------------------------------------------------
- It may be useful for a LLD to keep track of struct Scsi_Host instances
- (a pointer is returned by scsi_host_alloc()). Such instances are "owned"
- by the mid-level. struct Scsi_Host instances are freed from
- scsi_host_put() when the reference count hits zero.
- Hot unplugging an HBA that controls a disk which is processing SCSI
- commands on a mounted file system is an interesting situation. Reference
- counting logic is being introduced into the mid level to cope with many
- of the issues involved. See the section on reference counting below.
- The hotplug concept may be extended to SCSI devices. Currently, when an
- HBA is added, the scsi_scan_host() function causes a scan for SCSI devices
- attached to the HBA's SCSI transport. On newer SCSI transports the HBA
- may become aware of a new SCSI device _after_ the scan has completed.
- An LLD can use this sequence to make the mid level aware of a SCSI device:
- SCSI DEVICE hotplug
- LLD mid level LLD
- ===-------------------=========--------------------===------
- scsi_add_device() ------+
- |
- slave_alloc()
- slave_configure() [--> scsi_adjust_queue_depth()]
- ------------------------------------------------------------
- In a similar fashion, an LLD may become aware that a SCSI device has been
- removed (unplugged) or the connection to it has been interrupted. Some
- existing SCSI transports (e.g. SPI) may not become aware that a SCSI
- device has been removed until a subsequent SCSI command fails which will
- probably cause that device to be set offline by the mid level. An LLD that
- detects the removal of a SCSI device can instigate its removal from
- upper layers with this sequence:
- SCSI DEVICE hot unplug
- LLD mid level LLD
- ===----------------------=========-----------------===------
- scsi_remove_device() -------+
- |
- slave_destroy()
- ------------------------------------------------------------
- It may be useful for an LLD to keep track of struct scsi_device instances
- (a pointer is passed as the parameter to slave_alloc() and
- slave_configure() callbacks). Such instances are "owned" by the mid-level.
- struct scsi_device instances are freed after slave_destroy().