一.内核配置,配置使其支持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,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/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); 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-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
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_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盘扫描线程
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_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().