嵌入式linux下u盘升级的设计

来源:互联网 发布:新媒体营销的数据 编辑:程序博客网 时间:2024/05/17 00:53

一.内核配置,配置使其支持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,sdcint main(int argc,char **argv){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); //"/media/sda1/UpDate","/media/sda2/UpDate"...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/shecho -e "update file!"echo $1ls -l $1touch /opt/1234echo -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 5usb 1-1: New USB device found, idVendor=0951, idProduct=1643usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3usb 1-1: Product: DataTraveler G3usb 1-1: Manufacturer: Kingstonusb 1-1: SerialNumber: 001CC0EC34F1FB90F71729FFscsi5 : usb-storage 1-1:1.0scsi 5:0:0:0: Direct-Access     Kingston DataTraveler G3  1.00 PQ: 0 ANSI: 0 CCSsd 5:0:0:0: Attached scsi generic sg0 type 0sd 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 offsd 5:0:0:0: [sdb] Assuming drive cache: write throughsd 5:0:0:0: [sdb] Assuming drive cache: write through sdb: sdb1sd 5:0:0:0: [sdb] Assuming drive cache: write throughsd 5:0:0:0: [sdb] Attached SCSI removable diskFAT: 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_threadscsi_scan_hostdo_scsi_scan_hostscsi_scan_host_selectedscsi_scan_channel__scsi_scan_targetscsi_probe_and_add_lunscsi_add_lunscsi_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 ---就加了这句okfree_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);static int __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盘设备驱动

static struct 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-ROMUSUAL_DEV(USB_SC_QIC, USB_PR_CB, USB_US_TYPE_STOR),//QIC-157USUAL_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

static int storage_probe(struct usb_interface *intf,const struct 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,const struct 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_readyinit_completion(&(us->notify));//初始化completion对象notifyinit_waitqueue_head(&us->delay_wait);//初始化等待队列对象delay_waitinit_completion(&us->scanning_done);//初始化completion对象scanning_doneresult = 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盘扫描线程

static int 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_probescsi_host_alloc--  usb_stor_probe1scsi_add_host--  usb_stor_probe2scsi_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 removedfrom the SCSI subsystem. Hosts can be introduced as early as driverinitialization and removed as late as driver shutdown. Typically a driverwill respond to a sysfs probe() callback that indicates an HBA has beendetected. After confirming that the new device is one that the LLD wantsto control, the LLD will initialize the HBA and then register a new hostwith the SCSI mid level.During LLD initialization the driver should register itself with theappropriate 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 (especiallythose that are writable after the driver is loaded) could also beregistered with sysfs at this point. The SCSI mid level first becomesaware of an LLD when that LLD registers its first HBA.At some later time, the LLD becomes aware of an HBA and what followsis 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 scanLLD                   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 invokescsi_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 shutdownassociated with the LLD module being unloaded (e.g. with the "rmmod"command) or in response to a "hot unplug" indicated by sysfs()'sremove() callback being invoked. In either case, the sequence is thesame:        HBA REMOVE: assume 2 SCSI devices attachedLLD                      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 fromscsi_host_put() when the reference count hits zero.Hot unplugging an HBA that controls a disk which is processing SCSIcommands on a mounted file system is an interesting situation. Referencecounting logic is being introduced into the mid level to cope with manyof the issues involved. See the section on reference counting below.The hotplug concept may be extended to SCSI devices. Currently, when anHBA is added, the scsi_scan_host() function causes a scan for SCSI devicesattached to the HBA's SCSI transport. On newer SCSI transports the HBAmay 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 hotplugLLD                   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 beenremoved (unplugged) or the connection to it has been interrupted. Someexisting SCSI transports (e.g. SPI) may not become aware that a SCSIdevice has been removed until a subsequent SCSI command fails which willprobably cause that device to be set offline by the mid level. An LLD thatdetects the removal of a SCSI device can instigate its removal fromupper layers with this sequence:                  SCSI DEVICE hot unplugLLD                      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() andslave_configure() callbacks). Such instances are "owned" by the mid-level.struct scsi_device instances are freed after slave_destroy().