- 浏览: 1019501 次
文章分类
浅析firmware完整生存和使用流程
浅析firmware完整生存和使用流程
1. http://blog.chinaunix.net/u1/38994/showart_1288259.html
request_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;
}
int
request_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 int
flush_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;
}
相关推荐
浅析firmware完整生存和使用流程.rar
PCI Firmware Specification 3.0
Embedded Firmware Solutions: Development Best Practices for the Internet of Things is the perfect introduction and daily-use field guide--for the thousands of firmware designers, hardware engineers, ...
firmware hg8247
sony firmware extension parser device
ESP 32 WROOM 32 LUA Firmware
详细介绍如何使用GD32单片机的硬件库文件
Rare firmware for BRUBURG SPM amplifier plate using PIC24 MCU
PCI Firmware 规范 3.0 仅供个人学习参考之用。 商用请向PCI-SIG规范组购买。
firmware_ps3111
HP Server ILO3 firmware 1.9.1. 打开文件会解压出一个文件夹,使用文件夹中的ilo3_191.bin在ILO界面中firmware选项页进行升级
Firmware Restoration 华硕拯救工具
先确保添加了non-free软件源,然后终端输入apt install firmware-realtek重启。该包支持 * Realtek RTL8192E boot code (RTL8192E/boot.img) * Realtek RTL8192E init data (RTL8192E/data.img) * Realtek RTL...
linux-firmware-20210208.tar.gz linux firmware 20210208版本
《The Firmware handbook 》 源下载 http://www.ebooks-share.info/?p=325 来自www.amazon.com的介绍: Review This handbook will be the standard reference in the field, it provides a much-needed ...
PCI Firmware Specification Revision 3.2 January 26, 2015
barracuda firmware
使用doxygen生成的scp-firmware的网页,其中包含有各个结构体关系图以及函数调用流程图。比网上其他地方更加详细
firmware 开发,多看看总是有用的,英文看得太慢。
需要拆机就能对产品进行固件升级是很多人想要的效果,不仅方便而且节省精力和成本。那么如何完成这项工作呢?接下来所介绍的Bootloader就可以...下面来浅析STM32Bootloader设计。首先谈谈stm32的ISP和IAP区别和联系。