嵌入式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,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().





 

你可能感兴趣的:(shell,U盘,程序更新,u盘更新,u盘升级)