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





 

發佈了85 篇原創文章 · 獲贊 12 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章