浅析frmware的加载和init通过netlink处理uevent事件的一般流程
当总线检测代id相macth的设备或者驱动时调用,wlan_probe
=>wlan_probe
=>wlan_add_card
=>sbi_register_dev
=>priv->hotplug_device = &func->dev;这样priv->hotplug_device就指向了/sys/bus/sdio/devices下的设备节点描述结构体
接下来下载wlan的firmware固件驱动,
=>wlan_init_fw
=>request_firmware(&priv->firmware, fw_name, priv->hotplug_device);给priv->hotplug_device设备申请名字为fw_name的firmware
数据,让后将结果放到&priv->firmware中,
struct firmware {
size_t size;
u8 *data;
};
可以看到,如果应用层的程序成功load了firmware固件文件,那么firmware.data将指向固件数据,firmware.size为固件大小.
module_param(fw_name, charp, 0);
MODULE_PARM_DESC(fw_name, "Firmware name");这里可以看到fw_name是作为参数可以自由指定的,如果没有指定,那么将使用如下默认名:
#define DEFAULT_FW_NAME "mrvl/sd8688.bin"
=================================================================
request_firmware
=>_request_firmware
=>fw_setup_device
=>fw_register_device//添加临时download firmware的dev设备节点,注册该inode的class为
=>fw_priv->attr_data = firmware_attr_data_tmpl;//文件属性操作函数集
=>strlcpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX);//拷贝参数就是我们的fw_name即默认的"mrvl/sd8688.bin"
=>sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data);//创建DEVPATH/data文件,操作该文件的方法为firmware_attr_data_tmpl
//这样init程序就可以打开这个DEVPATH/data文件,然后向这个DEVPATH/data文件写入firmware固件bin内容,然后kernel的driver就可以通过
//firmware->data和firmware->size来读取有uevent处理程序init加载进来的firmware数据了[luther.gliethttp]
=>f_dev->class = &firmware_class;
=>if (uevent) kobject_uevent(&f_dev->kobj, KOBJ_ADD);发送uevent消息.
=>wait_for_completion(&fw_priv->completion);等待完成
static struct bin_attribute firmware_attr_data_tmpl = {
.attr = {.name = "data", .mode = 0644},
.size = 0,
.read = firmware_data_read,
.write = firmware_data_write,
};
=>firmware_data_write
=>fw_realloc_buffer//类似realloc实现,释放原有的,申请新加的
=>fw_priv->fw->data = new_data;
=>kobject_uevent
=>kobject_uevent_env
/*
devices_init
=>devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
static struct kset_uevent_ops device_uevent_ops = {
.filter = dev_uevent_filter,
.name = dev_uevent_name,
.uevent = dev_uevent,
};
=>dev_uevent
=>dev->class->dev_uevent
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
...
}
*/
=>uevent_ops = kset->uevent_ops;//这里uevent_ops就指向device_uevent_ops了
=>uevent_ops->uevent(kset, kobj, env);建立环境变量
=>device_uevent_ops
=>dev_uevent
=>dev->class->dev_uevent就是firmware_class的dev_uevent,即:firmware_uevent
=>firmware_uevent
=>
/*
static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
if (add_uevent_var(env, "FIRMWARE=%s", fw_priv->fw_id))即默认的"mrvl/sd8688.bin"
return -ENOMEM;
if (add_uevent_var(env, "TIMEOUT=%i", loading_timeout))
return -ENOMEM;
return 0;
}
*/
=>if (uevent_sock) { 那么通过netlink将该uevent事件广播出去,
=>netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL);
=>在include/linux/autoconf.h中
#define CONFIG_UEVENT_HELPER_PATH "/sbin/hotplug"
=>但是我们的root文件系统/sbin/下没有hotplug这个文件,只有一个adbd程序,所以这样看来就不能运行了,所以这里应该将char uevent_helper[UEVENT_HELPER_PATH_LEN] = 清0才对,让if(uevent_helper[0])失败,进而不继续执行和hotplug的相关操作,但是现在autoconf.h中
include/linux/autoconf.h
#define CONFIG_NET 1
#define CONFIG_HOTPLUG 1
仍然被设置成1,不知道为什么kernel team还要这样设置[luther.gliethttp].
.
所以我们的系统使用的是netlink接收事件广播,我的ubuntu8.04的/sbin下也没有找到hotplug这个文件,可能正如大家所说的,hotplug的诸多
缺陷导致它已经淡出了linux世界,而天生丽质的netlink已经在linx世界中全面开花[luther.gliethttp].
然后init进程开始处理这个firmware请求,
init
=>main
=>handle_device_fd调用uevent的NETLINK_KOBJECT_UEVENT的socket处理函数
=>parse_event
=>handle_firmware_event
=>pid = fork();子进程执行process_firmware_event
=>process_firmware_event
#define SYSFS_PREFIX "/sys"
=>asprintf(&root, SYSFS_PREFIX"%s/", uevent->path);
//这里的uevent->path是parse_event函数解析时对应的"DEVPATH="节内容,也就是dev设备路径
=>asprintf(&loading, "%sloading", root);//在该路径下创建loading文件
=>asprintf(&data, "%sdata", root);//该路径下的data文件
=>loading_fd = open(loading, O_WRONLY);//创建该loading文件,然后向其中写入"1"表示开始加载,加载成功写入"0",失败写入"-1".
=>data_fd = open(data, O_WRONLY
#define FIRMWARE_DIR "/system/lib/firmware" 原来路径是/etc/firmware,我的mrvl/sd8688.bin也放在那里,
//但是虽然ramdisk虽然经过压缩,可是存储ramdisk.img的总大小才512k,所以不能将有可能不断扩大大小的firmware放到那里,
//于是最近将init进程搜索路径改为"/system/lib/firmware".
=>asprintf(&file, FIRMWARE_DIR"/%s", uevent->firmware);
=>fw_fd = open(file, O_RDONLY);//打开通过uevent传递过来的firmware文件,然后拷贝过去
=>load_firmware(fw_fd, loading_fd, data_fd))这样加载
=================================================================
#define module_param_named(name, value, type, perm) \
param_check_##type(name, &(value)); \
module_param_call(name, param_set_##type, param_get_##type, &value, perm); \
__MODULE_PARM_TYPE(name, #type)
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
//对应param_check_##type检测参数类型函数如下,可以检测如下参数类型[luther.gliehttp]
param_check_bool param_check_int param_check_short param_check_ushort
param_check_byte param_check_invbool param_check_uint param_check_proto_abbrev
param_check_charp param_check_long param_check_ulong param_check_scroll
=================================================================
比如usb通用驱动的使用,这样所有usb设备都可以通过serial方式进行访问,因为不是该usb设备专有的usb驱动,所以速度可能慢一些[luther.gliethttp].
sudo insmod /lib/modules/2.6.22-14-generic/kernel/drivers/usb/serial/usbserial.ko vendor=0x8086 product=0xd001
在drivers/usb/serial/generic.c驱动中,
module_param(vendor, ushort, 0);
MODULE_PARM_DESC(vendor, "User specified USB idVendor");
module_param(product, ushort, 0);
MODULE_PARM_DESC(product, "User specified USB idProduct");
所以通过module_param可以方便的给ko驱动传递参数,很方便的咚咚,kernel那群人真能整[luther.gliethttp].