原文地址:http://www.4ucode.com/Study/Topic/1388056
浅析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 ] .