了解一下热插拔
Ldd3的话:
现在, 随着 USB的出现, CardBus, PCMCIA,IEEE1394,和 PCI 热插拔控制器, Linux内核需要能够可靠地运行不管什么硬件从系统中增加或去除.这产生了一个额外的负担给设备驱动作者,因为现在他们必须一直处理一个没有任何通知而突然从地下冒出来的设备.
从这句话我们可以看出,热插拔有两个层次,一个是内核设备模型中提供的机制,一个是硬件提供的功能。
我们的实例主要是设备模型中提供的机制。硬件方面有时间的话我举个例子。
一些基础知识如udev和mdev我就不说了,论坛有。
还是linux-3.2.36
在kobject中有个结构struct kset_uevent_ops,我没有列出,因为总线已有这个结构,我们是在总线层所以不要去管理这个结构。
简单说一下热插拔过程(论坛也有这些分析)。
./drivers/base/core.c
device_register()->device_add()->kobject_uevent()->
./lib/ kobject_uevent.c
kobject_uevent_env()->call_usermodehelper()和netlink_broadcast_filtered()
一个是uevent_helper调用一个是netlink调用,这两个东西又够我写几遍文章了,等有时间我再写吧。现在简单了解。
netlink是为udev实现机制用的
uevent_helper是为mdev实现机制用的
我的arm平台用的是mdev
call_usermodehelper()第一个参数叫path
path --- 用户空间所要启用的应用程序路径
用mdev时,会echo “/sbin/mdev”> /proc/sys/kernel/hotplug把uevent_helper改为“/sbin/mdev”,再把uevent_helper赋给call_usermodehelper(),最终调用mdev。
说实例之前,先说一下mdev配置。//用udev的同学自己搞定,我就不说了
# vi /etc/mdev.conf
$MODALIAS=.* root:root777 @modprobe "$MODALIAS"
保存
MODALIAS要和驱动中一致,马上能看到。
这条规则指的是:当收到的环境变量中含有MODALIAS,那么加载MODALIAS代表的模块。
实例:
这个是在上一文章的实例基础上做的,如果要了解,你还要先简单了解一下上一实例(设备模型5)。
在my_dev.c中修改,我们要答到的目的是加载my_dvc.ko时自动加载my_dvr.ko
修改,红色为增加
MODULE_DEVICE_TABLE(my_dvc, my_dvc_table);
static ssize_t show_name(struct device *dev, structdevice_attribute *attr, char *buf)
{
struct my_device*mydev = (struct my_device*)dev->platform_data;
return sprintf(buf,"%s\n", mydev->name);
}
static ssize_t show_modalias(struct device *dev, structdevice_attribute *attr, char *buf)
{
return sprintf(buf,"%s\n", "my_dvr");
}
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static DEVICE_ATTR(modalias, S_IRUGO, show_modalias, NULL);
//这两个文件只是为了输出一些信息,可以不管
static struct attribute *my_dvc_attrs[] = {
&dev_attr_name.attr,
&dev_attr_modalias.attr,
NULL
};
static struct attribute_group my_dvc_attr_group =
{
.attrs = my_dvc_attrs,
};
static const struct attribute_group *my_dvc_attr_groups[] =
{
&my_dvc_attr_group,
NULL
};
static int my_dvc_uevent(struct device *dev, structkobj_uevent_env *env)
{
struct my_device*mydev = (struct my_device*)dev->platform_data;
int i = 0;
MY_DEBUG("%s_uevent\n",mydev->name);
//实际一般用一些方法获得driver的name,这里就直接写了
if(add_uevent_var(env, "MODALIAS=%s", "my_dvr"))// MODALIAS和mdev.conf一致
return -ENOMEM;
while(env->envp[i]!= NULL)//我们做一个调试信息
{
MY_DEBUG("%s\n",env->envp[i]);//打印环境变量
i++;
}
return 0;
}
static struct device_type my_dvc_type = {
.name = "my_dvc_type",
.groups = my_dvc_attr_groups,
.uevent = my_dvc_uevent,
};
static ssize_t my_dvc_show_info(structdevice *devp,struct device_attribute *attr,char *buf)
{
…
…
static struct my_device my_dvcs[2] =
{
{
.name = "my_dev0",
.id = my_dvc_table,
.dev =
{
.platform_data =&my_dvcs[0],
.release =my_dev_release,
.init_name = "my_dev0",
.type =&my_dvc_type,
},
.attr = &dev_attr_info,
},
{
.name = "my_dev1",
.id = &my_dvc_table[1],
.dev =
{
.platform_data =&my_dvcs[1],
.release =my_dev_release,
.init_name = "my_dev1",
.type = &my_dvc_type,
},
},
};
调试
# insmod my_bus.ko
# insmod my_dvc.ko
# dmesg
//my_dev0的加载打印
my_bus_uevent
my_dev0_uevent
//这是生成的环境变量
ACTION=add
DEVPATH=/devices/mine0/my_dev0
SUBSYSTEM=mine
DEVTYPE=my_dvc_type
MODALIAS=my_dvr
////my_dev1的加载打印
my_bus_uevent
my_dev1_uevent
ACTION=add
DEVPATH=/devices/mine0/my_dev1
SUBSYSTEM=mine
DEVTYPE=my_dvc_type
MODALIAS=my_dvr
//加载my_dvr的打印
my_bus_match
my_dvr_probe
my_bus_match
my_dvr_probe
special_init
# cat /proc/modules
my_dvr 1245 0 - Live 0xbf00f000 (O)
my_dvc 2023 0 - Live 0xbf00b000 (O)
my_bus 2274 2 my_dvr,my_dvc, Live 0xbf007000 (O)
my_dvr自动加载//过程从上面的分析应该能看出来吧。
看完这个例子,可能会有两种感觉——失望和不解。可能说:
这就是热插拔呀!
这是热插拔吗?
产生这样的心理,可能是读者带着usb等硬件热插拔的影子去看待热插拔。所以看完之后感觉还是没达到自己的目的。我在开始就说热插拔可以看成两层。我上面的例子是设备结构层体现的。
我想很多人看热插拔,总会附带usb的热插拔的问题。好吧,我承认我也想知道,下面看一下usb的(终于看一些实际的东西了)。
我的模块(我把usb从linux系统中单独出来编译):usb的ohci
usb-common.ko usbcore.ko ohci-hcd.ko s3c2440_add_usb.ko
s3c2440_add_usb.ko主要是s3c_device_ohci加载,一般的平台在系统初始化时加载,我把它单独出来了。还有s3c2440的usb有个报错是usb 1-1: device descriptor read/64, error -62。网上有人是在uboot中upll初始化前加延时,我现在文件系统都有了还叫我动uboot,不就初始化upll,所以我在s3c2440_add_usb.ko做了一个upll的初始化工作。解决问题。开心!
Ldd3: USB 总线
任何在 USB总线上的设备有参数 name和 SUBSYSTEM环境变量设置为 usb.
USB 子系统也总是一直添加下列的环境变量:
PRODUCT
一个字符串, idVendor/idProduct/bcdDevice的格式,来指定这些 USB
设备特定的成员.
TYPE
一个 bDeviceClass/bDeviceSubClass/bDeviceProtocol格式的字符串,
指定这些 USB设备特定的成员.
如果 bDeviceClass成员设置为 0,下列的环境变量也被设置:
INTERFACE
一个 bInterfaceClass/bInterfaceSubClass/bInterfaceProtocol格式
的字符串, 指定这些 USB 设备特定成员.
如果这个内核建立选项, CONFIG_USB_DEVICEFS,它选择 usbfs文件系统来在
内核中建立, 被选中, 下列环境变量也被设置:
DEVICE
一个字符串, 在设备所在的 usbfs 文件系统中出现.这个字串以
/proc/bus/usb/USB_BUS_NUMBER/USB_DEVICE_NUMBER的格式,其中
USB_BUS_NUMBER 是这个设备所在的 USB 总线的 3个数,
USB_DEVICE_NUMBER 是已由内核分配给 USB 设备的 3位数.
Linux3.2.36的USB_BUS_NUMBER为BUSNUM
USBDEVICE_NUMBER为DEVNUM
主义你要在驱动中定义CONFIG_HOTPLUG才会有。
你可以学着我上面的例子,根据这些环境变量在mdev.conf中加一些东西执行一些热插拔事件,我简单说一下PRODUCT、TYPE、DEVICE是总线产生的,INTERFACE和BUSBUM和DEVNUM产生的,这点分析就是为了和我们上面说的联系一下,下面我们装载:
# insmod usb-common.ko
# insmod usbcore.ko
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
# ls /sys/bus/usb/drivers
hub usb usbfs
我相信看过我之前写的总线实例的,应该对这些东西的生成过程不会陌生吧!不管你信不信反正我信了。
# insmod ohci-hcd.ko
ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
# insmod s3c2440_add_usb.ko
s3c2440-ohci s3c2440-ohci: s3c2440-ohci
s3c2440-ohci s3c2440-ohci: new USB bus registered, assigned bus number 1
s3c2440-ohci s3c2440-ohci: irq 42, io mem 0x49000000
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
# ls /sys/bus/usb/devices/
1-0:1.0 usb1
usb1就是我的usb主机控制器,1-0-1.0就是root-hub
这里还没有出现热插拔,再申明一下,这里主角是热插拔,所以有些关于usb的知识我会跳过。首先在初始化时有个内核线程
khubd_task = kthread_run(hub_thread, NULL, "khubd");
if (!IS_ERR(khubd_task))
return 0;
hub_thread调用hub_events()
主要的代码
static void hub_events(void)
{
…
while (1) {
…
if (list_empty(&hub_event_list)) {//hub_event_list为空不执行
spin_unlock_irq(&hub_event_lock);
break;
}
tmp = hub_event_list.next;
list_del_init(tmp);
hub = list_entry(tmp, struct usb_hub, event_list);//从event_list获取struct usb_hub结构数据
…
/* deal with port status changes */
//处理各个口事件
for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {
…
if (portchange & USB_PORT_STAT_C_CONNECTION) {
…
connect_change = 1;
}
…//还有其它事件,在这就不细说了
if (connect_change)
hub_port_connect_change(hub, i,
portstatus, portchange);
//我们主要演示设备插上和拔出,所以就看它。
} /* end for i */
…
}
看过程hub_probe()->hub_configure()->hub_activate()->kick_khubd()
kick_khubd()主要是list_add_tail(&hub->event_list, &hub_event_list);和wake_up(&khubd_wait);执行之后hub_events就会去处理事件。
对于这个话题你只要知道,hub_events()会去处理hub事件,调用kick_khubd()就是告诉hub_events()有事件要取处理。再看hub_configure中会执行
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
hub, endpoint->bInterval);
按一定的周期调用hub_irq
static void hub_irq(struct urb *urb)
{
…
kick_khubd(hub);//hub_events()会执行
…
}
看到这我想说hub中断不是插入拔出usb设备时产生的,而是定时执行hub_irq如果有usb插入拔出时进行相应的处理。
看一下
插入usb设备
# usb 1-1: new full-speed USB device number 2 using s3c2440-ohci
# ls /sys/bus/usb/devices/
1-0:1.0 1-1 1-1:1.0 usb1
多出1-1
# ls -l /sys/bus/usb/drivers/usb/
lrwxrwxrwx 1 0 0 0 Jan 1 00:14 1-1 -> ../../../../devices/platform/s3c2440-ohci/usb1/1-1
--w------- 1 0 0 4096 Jan 1 00:14 bind
lrwxrwxrwx 1 0 0 0 Jan 1 00:14 module -> ../../../../module/usbcore
--w------- 1 0 0 4096 Jan 1 00:14 uevent
--w------- 1 0 0 4096 Jan 1 00:14 unbind
lrwxrwxrwx 1 0 0 0 Jan 1 00:14 usb1 -> ../../../../devices/platform/s3c2440-ohci/usb1
拔出usb设备
# ls /sys/bus/usb/devices/usb 1-1: USB disconnect, device number 2
# ls /sys/bus/usb/devices/
1-0:1.0 usb1
恢复
到这我可以说usb的热插拔分析完了,当然还是在软件上,至于上面如何生成1-1.1.0等连接或文件和usb枚举过程在这就不讨论了。
下期预告:
固件开发