转自:
浅析firmware完整生存和使用流程1. http://blog.chinaunix/u1/38994/showart_1288259.htmlrequest_firmware=>_request_firmware=>fw_setup_device=>fw_register_device=>static int fw_register_device(struct device **dev_p, const char *fw_name, struct device *device){ ... fw_priv->attr_data = firmware_attr_data_tmpl;//sysfs中本firmware传输,使用的bin类型文件定义. ... f_dev->uevent_suppress = 1;//该设备在device_register中,过滤掉uevent事件,不发布到netlink上[luther.gliethttp] retval = device_register(f_dev); ...}static struct bin_attribute firmware_attr_data_tmpl = { .attr = { .name = "data", .mode = 0644}, .size = 0,//现在文件大小,因为还没有读入任何数据,所以这里大小为0 .read = firmware_data_read,//调用的读方法 .write = firmware_data_write,//调用的写方法};int device_register(struct device *dev){ device_initialize(dev); return device_add(dev);}void device_initialize(struct device *dev){ dev->kobj.kset = devices_kset;//归属devices_kset来管理 kobject_init(&dev->kobj, &device_ktype); klist_init(&dev->klist_children, klist_children_get, klist_children_put); INIT_LIST_HEAD(&dev->dma_pools); INIT_LIST_HEAD(&dev->node); init_MUTEX(&dev->sem); spin_lock_init(&dev->devres_lock); INIT_LIST_HEAD(&dev->devres_head); device_init_wakeup(dev, 0); set_dev_node(dev, -1);}int device_add(struct device *dev)//向/sys文件系统注册生成dev相关的目录和文件,然后uevent到用户空间[luther.gliethttp]{ struct device *parent = NULL; struct class_interface *class_intf; int error; dev = get_device(dev); if (!dev || !strlen(dev->bus_id)) { error = -EINVAL; goto Done; } pr_debug("device: '%s': %s/n", dev->bus_id, __FUNCTION__); parent = get_device(dev->parent); setup_parent(dev, parent);//执行之后
dev->kobj.parent将等于
/class/firmware /* first, register with generic layer. */ error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id);//创建/class/firmware/mmc1:0001:1/目录 if (error) goto Error; /* notify platform of device entry */ if (platform_notify) platform_notify(dev); /* notify clients of device entry (new way) */ if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev); error = device_create_file(dev, &uevent_attr); if (error) goto attrError; if (MAJOR(dev->devt)) { error = device_create_file(dev, &devt_attr); if (error) goto ueventattrError; } error = device_add_class_symlinks(dev); if (error) goto SymlinkError; error = device_add_attrs(dev); if (error) goto AttrsError; error = dpm_sysfs_add(dev); if (error) goto PMError; device_pm_add(dev); error = bus_add_device(dev); if (error) goto BusError; kobject_uevent(&dev->kobj, KOBJ_ADD);//向用户空间发送uevent事件,如果kset和class bus_attach_device(dev); if (parent) klist_add_tail(&dev->knode_parent, &parent->klist_children); if (dev->class) { down(&dev->class->sem); /* tie the class to the device */ list_add_tail(&dev->node, &dev->class->devices); /* notify any interfaces that the device is here */ list_for_each_entry(class_intf, &dev->class->interfaces, node) if (class_intf->add_dev) class_intf->add_dev(dev, class_intf); up(&dev->class->sem); } Done: put_device(dev); return error; BusError: device_pm_remove(dev); PMError: if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_DEL_DEVICE, dev); device_remove_attrs(dev); AttrsError: device_remove_class_symlinks(dev); SymlinkError: if (MAJOR(dev->devt)) device_remove_file(dev, &devt_attr); ueventattrError: device_remove_file(dev, &uevent_attr); attrError: kobject_uevent(&dev->kobj, KOBJ_REMOVE); kobject_del(&dev->kobj); Error: cleanup_device_parent(dev); if (parent) put_device(parent); goto Done;}int kobject_uevent(struct kobject *kobj, enum kobject_action action){ return kobject_uevent_env(kobj, action, NULL);//发布uevent事件}int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[]){ struct kobj_uevent_env *env; const char *action_string = kobject_actions[action]; const char *devpath = NULL; const char *subsystem; struct kobject *top_kobj; struct kset *kset; struct kset_uevent_ops *uevent_ops; u64 seq; int i = 0; int retval = 0; pr_debug("kobject: '%s' (%p): %s/n", kobject_name(kobj), kobj, __FUNCTION__); /* search the kset we belong to */ top_kobj = kobj; while (!top_kobj->kset && top_kobj->parent) top_kobj = top_kobj->parent; if (!top_kobj->kset) { pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " "without kset!/n", kobject_name(kobj), kobj, __FUNCTION__); return -EINVAL; }//对于device_register(),//kset = devices_kset;//uevent_ops = device_uevent_ops;[luther.gliethttp] kset = top_kobj->kset; uevent_ops = kset->uevent_ops; /* skip the event, if the filter returns zero. */ if (uevent_ops && uevent_ops->filter) if (!uevent_ops->filter(kset, kobj)) {//该uevent是否被过滤了,//对于device_register(),如果dev->uevent_suppress = 1;//那么表示用户希望过滤掉该uevent,所以在这里直接返回即可.//对于上面request_firmware中fw_register_device的//retval = device_register(f_dev);在执行之前,调用了f_dev->uevent_suppress = 1;//就表示在这里将直接返回,它不希望产生uevent事件到用户空间,它会自己选择时机//调用kobject_uevent()来让uevent事件发送给用户空间[luther.gliethttp]. pr_debug("kobject: '%s' (%p): %s: filter function " "caused the event to drop!/n", kobject_name(kobj), kobj, __FUNCTION__); return 0; } /* originating subsystem */ if (uevent_ops && uevent_ops->name) subsystem = uevent_ops->name(kset, kobj); else subsystem = kobject_name(&kset->kobj); if (!subsystem) { pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the " "event to drop!/n", kobject_name(kobj), kobj, __FUNCTION__); return 0; } /* environment buffer */ env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); if (!env) return -ENOMEM; /* complete object path */ devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit; } /* default keys */ retval = add_uevent_var(env, "ACTION=%s", action_string); if (retval) goto exit; retval = add_uevent_var(env, "DEVPATH=%s", devpath); if (retval) goto exit; retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); if (retval) goto exit; /* keys passed in from the caller */ if (envp_ext) { for (i = 0; envp_ext[i]; i++) { retval = add_uevent_var(env, envp_ext[i]); if (retval) goto exit; } } /* let the kset specific function add its stuff */ if (uevent_ops && uevent_ops->uevent) {//对于device_register()来说,就是对于f_dev这个kobj来说,//就是调用dev_uevent添加major和minor等操作[luther.gliethttp] retval = uevent_ops->uevent(kset, kobj, env); if (retval) { pr_debug("kobject: '%s' (%p): %s: uevent() returned " "%d/n", kobject_name(kobj), kobj, __FUNCTION__, retval); goto exit; } } ...}int __init devices_init(void){ devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);//在sysfs文件系统的根目录下建立deviecs这个kset可视文件,比如/sys/devices//该kset的uevent处理函数为device_uevent_ops if (!devices_kset) return -ENOMEM; return 0;}static struct kset_uevent_ops device_uevent_ops = { .filter = dev_uevent_filter, .name = dev_uevent_name, .uevent = dev_uevent,};static int dev_uevent_filter(struct kset *kset, struct kobject *kobj){ struct kobj_type *ktype = get_ktype(kobj); if (ktype == &device_ktype) {//为默认的device_ktype管理//在device_register=>device_initialize=>kobject_init(&dev->kobj, &device_ktype); struct device *dev = to_dev(kobj); if (dev->uevent_suppress)//调用device_register()函数的驱动不希望dev的uevent发布到用户空间 return 0; if (dev->bus) return 1; if (dev->class) return 1; } return 0;}static int dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env){ struct device *dev = to_dev(kobj); int retval = 0; /* add the major/minor if present */ if (MAJOR(dev->devt)) {//填充major和minor设备号,以便接收uevent事件的init进程,能够mknod来创建相应的节点文件在/dev目录下[luther.gliethttp]. add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt)); add_uevent_var(env, "MINOR=%u", MINOR(dev->devt)); } if (dev->type && dev->type->name) add_uevent_var(env, "DEVTYPE=%s", dev->type->name); if (dev->driver) add_uevent_var(env, "DRIVER=%s", dev->driver->name);#ifdef CONFIG_SYSFS_DEPRECATED if (dev->class) { struct device *parent = dev->parent; /* find first bus device in parent chain */ while (parent && !parent->bus) parent = parent->parent; if (parent && parent->bus) { const char *path; path = kobject_get_path(&parent->kobj, GFP_KERNEL); if (path) { add_uevent_var(env, "PHYSDEVPATH=%s", path); kfree(path); } add_uevent_var(env, "PHYSDEVBUS=%s", parent->bus->name); if (parent->driver) add_uevent_var(env, "PHYSDEVDRIVER=%s", parent->driver->name); } } else if (dev->bus) { add_uevent_var(env, "PHYSDEVBUS=%s", dev->bus->name); if (dev->driver) add_uevent_var(env, "PHYSDEVDRIVER=%s", dev->driver->name); }#endif /* have the bus specific function add its stuff */ if (dev->bus && dev->bus->uevent) { retval = dev->bus->uevent(dev, env); if (retval) pr_debug("device: '%s': %s: bus uevent() returned %d/n", dev->bus_id, __FUNCTION__, retval); } /* have the class specific function add its stuff */ if (dev->class && dev->class->dev_uevent) { retval = dev->class->dev_uevent(dev, env); if (retval) pr_debug("device: '%s': %s: class uevent() " "returned %d/n", dev->bus_id, __FUNCTION__, retval); } /* have the device type specific fuction add its stuff */ if (dev->type && dev->type->uevent) { retval = dev->type->uevent(dev, env); if (retval) pr_debug("device: '%s': %s: dev_type uevent() " "returned %d/n", dev->bus_id, __FUNCTION__, retval); } return retval;}static int fw_setup_device(struct firmware *fw, struct device **dev_p, const char *fw_name, struct device *device, int uevent){ struct device *f_dev; struct firmware_priv *fw_priv; int retval; *dev_p = NULL; retval = fw_register_device(&f_dev, fw_name, device); if (retval) goto out; /* Need to pin this module until class device is destroyed */ __module_get(THIS_MODULE); fw_priv = dev_get_drvdata(f_dev); fw_priv->fw = fw; retval = sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data);//在sysfs中创建bin类型文件,即:firmware_attr_data_tmpl//sysfs_create_bin_file//直接向sysfs的'内存磁盘'创建'磁盘文件'-firmware_attr_data_tmpl if (retval) { printk(KERN_ERR "%s: sysfs_create_bin_file failed/n", __FUNCTION__); goto error_unreg; }//device_create_file=>sysfs_create_file//直接向sysfs的'内存磁盘'创建'磁盘文件'-dev_attr_loading retval = device_create_file(f_dev, &dev_attr_loading);//firmware处理状态提示文件 if (retval) { printk(KERN_ERR "%s: device_create_file failed/n", __FUNCTION__); goto error_unreg; } if (uevent)//如果希望该request_firmware发送uevent到用户空间,那么f_dev->uevent_suppress清0[luther.gliethttp] f_dev->uevent_suppress = 0; *dev_p = f_dev; goto out;error_unreg: device_unregister(f_dev);out: return retval;}static int_request_firmware(const struct firmware **firmware_p, const char *name, struct device *device, int uevent){ struct device *f_dev; struct firmware_priv *fw_priv; struct firmware *firmware; int retval; if (!firmware_p) return -EINVAL; *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL); if (!firmware) { printk(KERN_ERR "%s: kmalloc(struct firmware) failed/n", __FUNCTION__); retval = -ENOMEM; goto out; } retval = fw_setup_device(firmware, &f_dev, name, device, uevent); if (retval) goto error_kfree_fw; fw_priv = dev_get_drvdata(f_dev); if (uevent) { if (loading_timeout > 0) { fw_priv->timeout.expires = jiffies + loading_timeout * HZ; add_timer(&fw_priv->timeout); }//因为上面device_register时,dev->uevent_suppress = 1;//所以device_register将uevent过滤掉了,没有将uevent发送到用户空间,//后来dev->uevent_suppress = 0;所以所以经过上面乱七八糟的设置之后,现在它认为可以安全//向用户空间发送uevent了,即:它现在希望通过uevent告知等待该类型netlink的init进程可以安全执行uevent事件对应的动作了,于是现在这里再次调用kobject_uevent将uevent事件发送给用户空间[luther.gliethttp] kobject_uevent(&f_dev->kobj, KOBJ_ADD); wait_for_completion(&fw_priv->completion);//等待完成 set_bit(FW_STATUS_DONE, &fw_priv->status); del_timer_sync(&fw_priv->timeout); } else wait_for_completion(&fw_priv->completion); mutex_lock(&fw_lock); if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) { retval = -ENOENT; release_firmware(fw_priv->fw); *firmware_p = NULL; } fw_priv->fw = NULL; mutex_unlock(&fw_lock); device_unregister(f_dev);//因为已经完成了导入使命,所以这个提供给用户空间传递数据进入kernel的入口可以删除掉了,这里调用
device_unregister(f_dev);将创建的所有相关目录和文件从sysfs这个'内存物理磁盘'系统中删除掉!
goto out;error_kfree_fw: kfree(firmware); *firmware_p = NULL;out: return retval;}intrequest_firmware(const struct firmware **firmware_p, const char *name, struct device *device){ int uevent = 1; return _request_firmware(firmware_p, name, device, uevent);}
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
static ssize_t firmware_loading_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct firmware_priv *fw_priv = dev_get_drvdata(dev); int loading = simple_strtol(buf, NULL, 10); switch (loading) { case 1://开始下载firmware, mutex_lock(&fw_lock); if (!fw_priv->fw) { mutex_unlock(&fw_lock); break; } vfree(fw_priv->fw->data); fw_priv->fw->data = NULL; fw_priv->fw->size = 0; fw_priv->alloc_size = 0; set_bit(FW_STATUS_LOADING, &fw_priv->status); mutex_unlock(&fw_lock); break; case 0://成功完成下载 if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) { complete(&fw_priv->completion);//唤醒等待着的kernel clear_bit(FW_STATUS_LOADING, &fw_priv->status); break; } /* fallthrough */ default: printk(KERN_ERR "%s: unexpected value (%d)/n", __FUNCTION__, loading); /* fallthrough */ case -1://init进程写入-1,表示错误,超时时也会调用下面这个函数 fw_load_abort(fw_priv); break; } return count;}==========================================================让我们看看用户空间的open,write怎么和sysfs文件系统中的'物理文件'对应起来的[luther.gliethttp]使用sysfs_lookup来向这个内存式的'物理文件系统'查找是否在'物理磁道'上存在dentry对应的文件,//当lib库中的open系统调用sys_open执行之后,//sys_open会现查找dentry是否在kernel的内存中存在,如果不存在,那么将//real_lookup=>truct dentry * dentry = d_alloc(parent, name);//result = dir->i_op->lookup(dir, dentry, nd);//对于sysfs文件系统就是sysfs_lookup了.static struct dentry * sysfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd){ struct dentry *ret = NULL; struct sysfs_dirent *parent_sd = dentry->d_parent->d_fsdata; struct sysfs_dirent *sd; struct inode *inode; mutex_lock(&sysfs_mutex); sd = sysfs_find_dirent(parent_sd, dentry->d_name.name);//sysfs的'内存磁盘'中查找,是否已经有了name对应的'内存物理文件', /* no such entry */ if (!sd) { ret = ERR_PTR(-ENOENT); goto out_unlock; } /* attach dentry and inode */ inode = sysfs_get_inode(sd);//该文件确实已经在sysfs的'内存磁盘'被创建了,所以这里引用它,同时如果inode没有在kernel内存创建那么创建inode,同时根据sd的mode,来初始化对应的inode操作方法集[luther.gliethttp]. if (!inode) { ret = ERR_PTR(-ENOMEM); goto out_unlock; } /* instantiate and hash dentry */ dentry->d_op = &sysfs_dentry_ops; dentry->d_fsdata = sysfs_get(sd);//将sd放入dentry的d_fsdata,以供open,read,write时使用. d_instantiate(dentry, inode); d_rehash(dentry); out_unlock: mutex_unlock(&sysfs_mutex); return ret;}struct inode * sysfs_get_inode(struct sysfs_dirent *sd){ struct inode *inode; inode = iget_locked(sysfs_sb, sd->s_ino); if (inode && (inode->i_state & I_NEW)) sysfs_init_inode(sd, inode);//订制该inode为sysfs个性式的inode return inode;} static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode){ struct bin_attribute *bin_attr; inode->i_blocks = 0; inode->i_mapping->a_ops = &sysfs_aops; inode->i_mapping->backing_dev_info = &sysfs_backing_dev_info; inode->i_op = &sysfs_inode_operations; inode->i_ino = sd->s_ino; lockdep_set_class(&inode->i_mutex, &sysfs_inode_imutex_key); if (sd->s_iattr) { /* sysfs_dirent has non-default attributes * get them for the new inode from persistent copy * in sysfs_dirent */ set_inode_attr(inode, sd->s_iattr); } else set_default_inode_attr(inode, sd->s_mode); /* initialize inode according to type */ switch (sysfs_type(sd)) { case SYSFS_DIR: inode->i_op = &sysfs_dir_inode_operations; inode->i_fop = &sysfs_dir_operations; inode->i_nlink = sysfs_count_nlink(sd); break; case SYSFS_KOBJ_ATTR: inode->i_size = PAGE_SIZE; inode->i_fop = &sysfs_file_operations; break; case SYSFS_KOBJ_BIN_ATTR: bin_attr = sd->s_bin_attr.bin_attr; inode->i_size = bin_attr->size; inode->i_fop = &bin_fops;//这就是firmare操作文件函数集了. break; case SYSFS_KOBJ_LINK: inode->i_op = &sysfs_symlink_inode_operations; break; default: BUG(); } unlock_new_inode(inode);}所以到这里我们就可以给出一个open调用图谱了:open=>sys_open=>bin_fops.open=>将执行bb = kzalloc(sizeof(*bb), GFP_KERNEL);等操作write=>sys_write=>bin_fops.write=>flush_write=>static intflush_write(struct dentry *dentry, char *buffer, loff_t offset, size_t count){ struct sysfs_dirent *attr_sd = dentry->d_fsdata;//还记得上面sysfs_lookup的
dentry->d_fsdata = sysfs_get(sd);吧
struct bin_attribute *attr = attr_sd->s_bin_attr.bin_attr; struct kobject *kobj = attr_sd->s_parent->s_dir.kobj; int rc; /* need attr_sd for attr, its parent for kobj */ if (!sysfs_get_active_two(attr_sd)) return -ENODEV; rc = -EIO; if (attr->write) rc = attr->write(kobj, attr, buffer, offset, count);//调用firmware_attr_data_tmpl的firmware_data_write方法 sysfs_put_active_two(attr_sd); return rc;}==========================================================最后回到driver,看看如何使用request_firmware接口函数struct firmware { size_t size; u8 *data;};1. request_firmware(&priv->firmware, fw_name, priv->hotplug_device);获得firmware数据2. priv->firmware->data即为通过vmalloc申请到的物理内存空间首地址,priv->firmware->size为固件大小3. 将data开头的size大小的数据下发到硬件cpu之后,vmalloc的data就可以释放掉了4. release_firmware(priv->firmware);释放内存,不然就出现内存泄露了[luther.gliethttp].==========================================================另外一个就是firmware固件驱动存储位置,这是由接收处理uevent事件的用户态进程指定的,我的是init进程来解析,在用户空间的init进程里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))这样加载
static int load_firmware(int fw_fd, int loading_fd, int data_fd){ struct stat st; long len_to_copy; int ret = 0; if(fstat(fw_fd, &st) < 0) return -1; len_to_copy = st.st_size;//开始传递firmware到kernel write(loading_fd, "1", 1); /* start transfer */ while (len_to_copy > 0) { char buf[PAGE_SIZE]; ssize_t nr; nr = read(fw_fd, buf, sizeof(buf)); if(!nr) break; if(nr < 0) { ret = -1; break; } len_to_copy -= nr; while (nr > 0) { ssize_t nw = 0; nw = write(data_fd, buf + nw, nr); if(nw <= 0) { ret = -1; goto out; } nr -= nw; } }out: if(!ret)
//firmware成功传递到内核
write(loading_fd, "0", 1); /* successful end of transfer */ else write(loading_fd, "-1", 2); /* abort transfer */ return ret;}