Linux 设备驱动的固件加载

作为一个驱动作者, 你可能发现你面对一个设备必须在它能支持工作前下载固件到它里面. 硬件市场的许多地方的竞争是如此得强烈, 以至于甚至一点用作设备控制固件的 EEPROM 的成本制造商都不愿意花费. 因此固件发布在随硬件一起的一张 CD 上, 并且操作系统负责传送固件到设备自身.

     硬件越来越复杂,硬件的许多功能使用了程序实现,与直接硬件实现相比,固件拥有处理复杂事物的灵活性和便于升级、维护等优点固件(firmware)就是这样的一段在设备硬件自身中执行的程序,通过固件标准驱动程序才能实现特定机器的操作,如:光驱、刻录机等都有内部的固件。

      固件一般存放在设备上的flash存储器中,但出于成本和灵活性考虑,许多设备都将固件的映像(image)以文件的形式存放在硬盘中,设备驱动程序初始化时再装载到设备内部的存储器中。这样,方便了固件的升级,并省略了设备的flash存储器


一、驱动和固件的区别

       从计算机领域来说,驱动和固件从来没有过明确的定义,就好像今天我们说内存,大部分人用来表示SDRAM,但也有人把Android里的“固化的Flash/Storage"称为“内存”,你不能说这样说就错了,因为这确实是一种“内部存储”。

      但在Linux Kernel中,Driver和Firmware是有明确含义的,

1、驱动

     Driver是控制被操作系统管理的外部设备(Device)的代码段。很多时候Driver会被实现为LKM,但这不是必要条件。driver通过driver_register()注册到总线(bus_type)上,代表系统具备了驱动某种设备(device)的能力。当某个device被注册到同样的总线的时候(通常是总线枚举的时候发现了这个设备),总线驱动会对driver和device会通过一定的策略进行binding(即进行匹配),如果Binding成功,总线驱动会调用driver的probe()函数,把设备的信息(例如端口,中断号等)传递给驱动,驱动就可以对真实的物理部件进行初始化,并把对该设备的控制接口注册到Linux的其他子系统上(例如字符设备,v4l2子系统等)。这样操作系统的其他部分就可以通过这些通用的接口来访问设备了。

2、固件

     Firmware,是表示运行在非“控制处理器”(指不直接运行操作系统的处理器,例如外设中的处理器,或者被用于bare metal的主处理器的其中一些核)中的程序。这些程序很多时候使用和操作系统所运行的处理器完全不同的指令集。这些程序以二进制形式存在于Linux内核的源代码树中,生成目标系统的时候,通常拷贝在/lib/firmware目录下。当driver对device进行初始化的时候,通过request_firmware()等接口,在一个用户态helper程序的帮助下,可以把指定的firmware加载到内存中,由驱动传输到指定的设备上。

     所以,总的来说,其实driver和firmware没有什么直接的关系,但firmware通常由驱动去加载。我们讨论的那个OS,一般不需要理解firmware是什么,只是把它当做数据。firmware是什么,只有使用这些数据的那个设备才知道。好比你用一个电话,电话中有一个软件,这个软件你完全不关心如何工作的,你换这个软件的时候,就可以叫这个软件是“固件”,但如果你用了一个智能手机,你要细细关系什么是上面的应用程序,Android平台,插件之类的细节内容,你可能就不叫这个东西叫“固件”了。


     如何解决固件问题呢?你可能想解决固件问题使用这样的一个声明:

     static char my_firmware[] = { 0x34, 0x78, 0xa4, ... }; 

    但是, 这个方法几乎肯定是一个错误. 将固件编码到一个驱动扩大了驱动的代码, 使固件升级困难, 并且非常可能产生许可问题. 供应商不可能已经发布固件映象在 GPL 之下, 因此和 GPL-许可的代码混合常常是一个错误. 为此, 包含内嵌固件的驱动不可能被接受到主流内核或者被 Linux 发布者包含.

二、内核固件接口

        正确的方法是当你需要它时从用户空间获取它. 但是, 请抵制试图从内核空间直接打开包含固件的文件的诱惑; 那是一个易出错的操作, 并且它安放了策略(以一个文件名的形式)到内核. 相反, 正确的方法时使用固件接口, 它就是为此而创建的:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #include <linux/firmware.h>  
  2.   
  3. int request_firmware(const struct firmware **fw, char *name, struct device *device);  

     函数request_firmware向用户空间请求提供一个名为name固件映像文件并等待完成。参数device为固件装载的设备。文件内容存入request_firmware 返回,如果固件请求成功,返回0。该函数从用户空间得到的数据未做任何检查,用户在编写驱动程序时,应对固件映像做数据安全检查,检查方向由设备固件提供商确定,通常有检查标识符、校验和等方法。

    调用 request_firmware 要求用户空间定位并提供一个固件映象给内核; 我们一会儿看它如何工作的细节. name 应当标识需要的固件; 正常的用法是供应者提供的固件文件名. 某些象 my_firmware.bin 的名子是典型的. 如果固件被成功加载, 返回值是 0(负责常用的错误码被返回), 并且 fw 参数指向一个这些结构:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct firmware {  
  2.  size_t size;  
  3.  u8 *data;   
  4. };  

    那个结构包含实际的固件, 它现在可被下载到设备中. 小心这个固件是来自用户空间的未被检查的数据; 你应当在发送它到硬件之前运用任何并且所有的你能够想到的检查来说服你自己它是正确的固件映象. 设备固件常常包含标识串, 校验和, 等等; 在信任数据前全部检查它们.

    在你已经发送固件到设备前, 你应当释放 in-kernel 结构, 使用:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void release_firmware(struct firmware *fw);  

    因为 request_firmware 请求用户空间来帮忙, 它保证在返回前睡眠. 如果你的驱动当它必须请求固件时不在睡眠的位置, 异步的替代方法可能要使用:
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int request_firmware_nowait(struct module *module,  
  2.        char *name, struct device *device, void *context,  
  3.              void (*cont)(const struct firmware *fw, void *context));  

   这里额外的参数是 moudle( 它将一直是 THIS_MODULE), context (一个固件子系统不使用的私有数据指针), 和 cont. 如果都进行顺利, request_firmware_nowait 开始固件加载过程并且返回 0. 在将来某个时间, cont 将用加载的结果被调用. 如果由于某些原因固件加载失败, fw 是 NULL.


三、固件如何工作

       固件子系统使用 sysfs 和热插拔机制. 当调用 request_firmware, 一个新目录在 /sys/class/firmware 下使用你的驱动的名子被创建. 那个目录包含 3 个属性:

loading

     这个属性应当被加载固件的用户空间进程设置为 1. 当加载进程完成, 它应当设为 0. 写一个值 -1 到 loading 会中止固件加载进程.

data

      data 是一个二进制的接收固件数据自身的属性. 在设置 loading 后, 用户空间进程应当写固件到这个属性.

device

      这个属性是一个符号连接到 /sys/devices 下面的被关联入口项.

       一旦创建了 sysfs 入口项, 内核为你的设备产生一个热插拔事件. 传递给热插拔处理者的环境包括一个变量 FIRMWARE, 它被设置为提供给 request_firmware 的名子. 这个处理者应当定位固件文件, 并且拷贝它到内核使用提供的属性. 如果这个文件无法找到, 处理者应当设置 loading 属性为 -1.

      如果一个固件请求在 10 秒内没有被服务, 内核就放弃并返回一个失败状态给驱动. 超时周期可通过 sysfs 属性 /sys/class/firmware/timeout 属性改变.

      使用 request_firmware 接口允许你随你的驱动发布设备固件. 当正确地集成到热插拔机制, 固件加载子系统允许设备简化工作"在盒子之外" 显然这是处理问题的最好方法.

      但是, 请允许我们提出多一条警告: 设备固件没有制造商的许可不应当发布. 许多制造商会同意在合理的条款下许可它们的固件, 如果客气地请求; 一些其他的可能不何在. 无论如何, 在没有许可时拷贝和发布它们的固件是对版权法的破坏并且招致麻烦.


四、固件接口函数的使用方法

       当驱动程序需要使用固件驱动时,在驱动程序的初始化化过程中需要加下如下的代码:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. if(request_firmware(&fw_entry, $FIRMWARE, device) == 0)  /*从用户空间请求映像数据*/  
  2.   
  3. /*将固件映像拷贝到硬件的存储器,拷贝函数由用户编写*/  
  4. copy_fw_to_device(fw_entry->data, fw_entry->size);     
  5. release(fw_entry);  

    用户还需要在用户空间提供脚本通过文件系统sysfs中的文件data将固件映像文件读入到内核的缓冲区中。脚本样例列出如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #变量$DEVPATH(固件设备的路径)和$FIRMWARE(固件映像名)应已在环境变量中提供  
  2.    
  3. HOTPLUG_FW_DIR=/usr/lib/hotplug/firmware/    #固件映像文件所在目录  
  4.    
  5. echo 1 > /sys/$DEVPATH/loading  
  6. cat $HOTPLUG_FW_DIR/$FIRMWARE > /sysfs/$DEVPATH/data  
  7. echo 0 > /sys/$DEVPATH/loading  

    

五、固件请求函数request_firmware

     函数request_firmware请求从用户空间拷贝固件映像文件到内核缓冲区。该函数的工作流程列出如下:

a -- 在文件系统sysfs中创建文件/sys/class/firmware/xxx/loading和data,"xxx"表示固件的名字,给文件loading和data附加读写函数,设置文件属性,文件loading表示开/关固件映像文件装载功能;文件data的写操作将映像文件的数据写入内核缓冲区,读操作从内核缓冲区读取数据。

b -- 将添加固件的uevent事件(即"add")通过内核对象模型发送到用户空间。

c -- 用户空间管理uevent事件的后台进程udevd接收到事件后,查找udev规则文件,运行规则所定义的动作,与固件相关的规则列出如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. $ /etc/udev/rules.d/50-udev-default.rules  
  2. ……  
  3. # firmware class requests  
  4. SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware.sh"  
  5. ……  

    从上述规则可以看出,固件添加事件将引起运行脚本firmware.sh。

d -- 脚本firmware.sh打开"装载"功能,同命令"cat 映像文件 > /sys/class/firmware/xxx/data"将映像文件数据写入到内核的缓冲区。

e -- 映像数据拷贝完成后,函数request_firmware从文件系统/sysfs注销固件设备对应的目录"xxx"。如果请求成功,函数返回0。

 f -- 用户就将内核缓冲区的固件映像数据拷贝到固件的内存中。然后,调用函数release_firmware(fw_entry)释放给固件映像分配的缓冲区。


函数request_firmware列出如下(在drivers/base/firmware_class.c中):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int request_firmware(const struct firmware **firmware_p, const char *name,  
  2.                  struct device *device)  
  3. {  
  4.         int uevent = 1;  
  5.         return _request_firmware(firmware_p, name, device, uevent);  
  6. }  
  7.    
  8. static int _request_firmware(const struct firmware **firmware_p, const char *name,  
  9.          struct device *device, int uevent)  
  10. {  
  11.     struct device *f_dev;  
  12.     struct firmware_priv *fw_priv;  
  13.     struct firmware *firmware;  
  14.     struct builtin_fw *builtin;  
  15.     int retval;  
  16.    
  17.     if (!firmware_p)  
  18.         return -EINVAL;  
  19.    
  20.     *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);  
  21.     ……  //省略出错保护  
  22.    
  23.    /*如果固件映像在内部__start_builtin_fw指向的地址,拷贝数据到缓冲区*/  
  24.     for (builtin = __start_builtin_fw; builtin != __end_builtin_fw;  
  25.          builtin++) {  
  26.         if (strcmp(name, builtin->name))  
  27.             continue;  
  28.         dev_info(device, "firmware: using built-in firmware %s\n", name);  /*打印信息*/  
  29.         firmware->size = builtin->size;  
  30.         firmware->data = builtin->data;  
  31.         return 0;  
  32.     }  
  33.     ……//省略打印信息  
  34.     /*在文件系统sysfs建立xxx目录及文件*/  
  35.     retval = fw_setup_device(firmware, &f_dev, name, device, uevent);   
  36.     if (retval)  
  37.         goto error_kfree_fw;  
  38.    
  39.     fw_priv = dev_get_drvdata(f_dev);  
  40.    
  41.     if (uevent) {  
  42.         if (loading_timeout > 0) {   /*加载定时器*/  
  43.             fw_priv->timeout.expires = jiffies + loading_timeout * HZ;  
  44.             add_timer(&fw_priv->timeout);  
  45.         }  
  46.    
  47.         kobject_uevent(&f_dev->kobj, KOBJ_ADD);     /*发送事件KOBJ_ADD*/  
  48.         wait_for_completion(&fw_priv->completion);  
  49.         set_bit(FW_STATUS_DONE, &fw_priv->status);  
  50.         del_timer_sync(&fw_priv->timeout);  
  51.     } else  
  52.         wait_for_completion(&fw_priv->completion);   /*等待完成固件映像数据的装载*/  
  53.    
  54.     mutex_lock(&fw_lock);  
  55.     /*如果装载出错,释放缓冲区*/  
  56.     if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) {  
  57.         retval = -ENOENT;  
  58.         release_firmware(fw_priv->fw);  
  59.         *firmware_p = NULL;  
  60.     }  
  61.     fw_priv->fw = NULL;  
  62.     mutex_unlock(&fw_lock);  
  63.     device_unregister(f_dev);   /*在文件系统sysfs注销xxx目录*/  
  64.     goto out;  
  65.    
  66. error_kfree_fw:  
  67.     kfree(firmware);  
  68.     *firmware_p = NULL;  
  69. out:  
  70.     return retval;  
  71. }  

函数fw_setup_device在文件系统sysfs中创建固件设备的目录和文件,其列出如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static int fw_setup_device(struct firmware *fw, struct device **dev_p,  
  2.                const char *fw_name, struct device *device,  
  3.                int uevent)  
  4. {  
  5.     struct device *f_dev;  
  6.     struct firmware_priv *fw_priv;  
  7.     int retval;  
  8.    
  9.     *dev_p = NULL;  
  10.     retval = fw_register_device(&f_dev, fw_name, device);  
  11.     if (retval)  
  12.         goto out;  
  13.    
  14.     ……  
  15.     fw_priv = dev_get_drvdata(f_dev);  /*从设备结构中得到私有数据结构*/  
  16.    
  17.     fw_priv->fw = fw;  
  18.     retval = sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data);  /*在sysfs中创建可执行文件*/  
  19.     ……  //省略出错保护  
  20.    
  21.     retval = device_create_file(f_dev, &dev_attr_loading);   /*在sysfs中创建一般文件*/  
  22.     ……  //省略出错保护  
  23.    
  24.     if (uevent)  
  25.         f_dev->uevent_suppress = 0;  
  26.     *dev_p = f_dev;  
  27.     goto out;  
  28.    
  29. error_unreg:  
  30.     device_unregister(f_dev);  
  31. out:  
  32.     return retval;  
  33. }  

函数fw_register_device注册设备,在文件系统sysfs中创建固件设备对应的设备类,存放固件驱动程序私有数据。其列出如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static int fw_register_device(struct device **dev_p, const char *fw_name,  
  2.                   struct device *device)  
  3. {  
  4.     int retval;  
  5.     struct firmware_priv *fw_priv = kzalloc(sizeof(*fw_priv),  
  6.                         GFP_KERNEL);  
  7.     struct device *f_dev = kzalloc(sizeof(*f_dev), GFP_KERNEL);  
  8.    
  9.     *dev_p = NULL;  
  10.    
  11.     …… //省略出错保护  
  12.     init_completion(&fw_priv->completion);    /*初始化completion机制的等待队列*/  
  13.     fw_priv->attr_data = firmware_attr_data_tmpl;   /*设置文件的属性结构*/  
  14.     strlcpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX);  
  15.    
  16.     fw_priv->timeout.function = firmware_class_timeout; /*超时装载退出函数*/  
  17.     fw_priv->timeout.data = (u_long) fw_priv;  
  18.     init_timer(&fw_priv->timeout);    /*初始化定时器*/  
  19.    
  20.     fw_setup_device_id(f_dev, device);  /*拷贝device ->bus_id到f_dev中*/  
  21.     f_dev->parent = device;      
  22.     f_dev->class = &firmware_class;    /*设备类实例*/  
  23.     dev_set_drvdata(f_dev, fw_priv);   /*存放设备驱动的私有数据:f_dev ->driver_data = fw_priv*/  
  24.     f_dev->uevent_suppress = 1;  
  25.     retval = device_register(f_dev);  
  26.     if (retval) {  
  27.         dev_err(device, "%s: device_register failed\n", __func__);  
  28.         goto error_kfree;  
  29.     }  
  30.     *dev_p = f_dev;  
  31.     return 0;  
  32.      ……  //省略了出错保护  
  33. }  

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. /*文件属性结构实例,设置文件系统sysfs中data文件的模式和读/写函数*/  
  2. static struct bin_attribute firmware_attr_data_tmpl = {  
  3.     .attr = {.name = "data", .mode = 0644},  
  4.     .size = 0,  
  5.     .read = firmware_data_read,    /*从内核缓冲区读出数据*/  
  6.     .write = firmware_data_write,   /*用于将固件映像文件的数据写入到内核缓冲区*/  
  7. };  
  8.    
  9. /*设备类结构实例,含有发送uevent事件函数和释放设备的函数*/  
  10. static struct class firmware_class = {  
  11.     .name       = "firmware",      /*设备类的名字*/  
  12.     .dev_uevent = firmware_uevent, /*设备发送uevent事件的函数*/  
  13.     .dev_release    = fw_dev_release, /*释放设备的函数*/  
  14. };

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/401940.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux USB 驱动开发(二)—— USB 驱动几个重要数据结构

前面我们学习了USB 驱动的一个描述符&#xff0c;下面来学习 USB 驱动的几个重要数据结构 一、struct usb_interface 接口函数 [cpp] view plaincopy struct usb_interface { struct usb_host_interface *altsetting; struct usb_host…

Linux USB 驱动开发(三)—— 编写USB 驱动程序

前面学习了USB驱动的一些基础概念与重要的数据结构&#xff0c;那么究竟如何编写一个USB 驱动程序呢&#xff1f;编写与一个USB设备驱动程序的方法和其他总线驱动方式类似&#xff0c;驱动程序把驱动程序对象注册到USB子系统中&#xff0c;稍后再使用制造商和设备标识来判断是否…

Linux USB 驱动开发(一)—— USB设备基础概念

在终端用户看来&#xff0c;USB设备为主机提供了多种多样的附加功能&#xff0c;如文件传输&#xff0c;声音播放等&#xff0c;但对USB主机来说&#xff0c;它与所有USB设备的接口都是一致的。一个USB设备由3个功能模块组成&#xff1a;USB总线接口、USB逻辑设备和功能单元&am…

Linux USB 驱动开发(四)—— 热插拔那点事

学习USB热插拔之前&#xff0c;先学习一些USB的硬件知识&#xff1a; 一、USB基础概念 1、硬件知识&#xff08;USB插座和插头&#xff09; 在最初的标准里&#xff0c;USB接头有4条线&#xff1a;电源&#xff0c;D-,D,地线。我们暂且把这样的叫做标准的USB接头吧。后来OTG出现…

Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结

设备驱动程序是操作系统内核和机器硬件之间的接口&#xff0c;由一组函数和一些私有数据组成&#xff0c;是应用程序和硬件设备之间的桥梁。在应用程序看来&#xff0c;硬件设备只是一个设备文件&#xff0c;应用程序可以像操作普通文件一样对硬件设备进行操作。 设备驱动程序是…

android软键盘上推ui解决

为什么80%的码农都做不了架构师&#xff1f;>>> http://bbs.csdn.net/topics/340198955 android软键盘上推ui解决 good job 转载于:https://my.oschina.net/macleo/blog/204882

Linux USB 驱动开发实例(一) —— USB摄像头驱动实现源码分析

Spac5xx的实现是按照标准的USB VIDEO设备的驱动框架编写&#xff08;其具体的驱动框架可参照/usr/src/linux/drivers/usb/usbvideo.c文件&#xff09;&#xff0c;整个源程序由四个主体部分组成&#xff1a; 设备模块的初始化模块和卸载模块&#xff0c;上层软件接口模块&#…

System Center 2012R2之SCVMM云部署SCOM(2-2)

SCVMM云部署SCOM安装过程1、在SCVMM中&#xff0c;使用WINDOWS SERVER 2012母盘创建云主机SCOM在SCVMM中先创建到一个私有云指定一个私有云名称选择资源主机指定逻辑网络默认选负载衡器跳过VIP模板跳过选择端口&#xff0c;下一步选择存储分类指定存储的VM路径各只读共享默认设…

Linux USB 驱动开发实例(二)—— USB 鼠标驱动注解及测试

参考2.6.14版本中的driver/usb/input/usbmouse.c。鼠标驱动可分为几个部分&#xff1a;驱动加载部分、probe部分、open部分、urb回调函数处理部分。 一、驱动加载部分 [cpp] view plaincopy static int __init usb_mouse_init(void) { int retval usb_register(&a…

MySQL5.6 更改字段属性仍旧会锁全表,注意这个坑!

如图&#xff1a;如果开发让修改表字段属性&#xff0c;建议用pt-online-schema-change。MySQL5.6的在线DDL会锁全表。注意这个坑。另外&#xff0c;增加、删除字段或索引不会锁全表&#xff0c;删除主键会锁全表。

Linux USB 驱动开发实例 (三)—— 基于USB总线的无线网卡浅析

回顾一下USB的相关知识 USB(Universal Serial Bus)总线又叫通用串行外部总线&#xff0c;它是20世纪90年代发展起来的。USB接口现在得到了广泛的应用和普及&#xff0c;现在的PC机中都带有大量的USB接口。它最大的特点就是方便通用、支持热插拔并且可以在一个接口上插上多个设备…

Linux 设备驱动开发思想 —— 驱动分层与驱动分离

前面我们学习I2C、USB、SD驱动时&#xff0c;有没有发现一个共性&#xff0c;就是在驱动开发时&#xff0c;每个驱动都分层三部分&#xff0c;由上到下分别是&#xff1a; 1、XXX 设备驱动 2、XXX 核心层 3、XXX 主机控制器驱动 而需要我们编写的主要是设备驱动部分&#xff0c…

CortexM0开发 —— UART时序分析

通用异步收发传输器(UniversalAsynchronousReceiver/Transmitter)&#xff0c;通常称作UART&#xff0c;是一种异步收发传输器。将数据由串行通信与并行通信间作传输转换&#xff0c;作为并行输入成为串行输出的芯片UART是一种通用串行数据总线&#xff0c;用于异步通信。该总线…

CortexM0开发 —— LPC11C14的UART使用方法

LPC1100系列微控制器UART LPC1100系列Cortex-M0微控制器具有一个符合16C550工业标准的异步串行口&#xff08;UART&#xff09;。此口同时增加了调制解调器&#xff08;Modem&#xff09;接口&#xff0c;DSR、DCD和RI Modem信号是只用于LQFP48和PLCC44封装的管脚配置。 特性…

Linux SD卡驱动开发(一) —— SD 相关基础概念

一.SD/MMC卡基础概念 1.1.什么是MMC卡 MMC&#xff1a;MMC就是MultiMediaCard的缩写&#xff0c;即多媒体卡。它是一种非易失性存储器件&#xff0c;体积小巧(24mm*32mm*1.4mm)&#xff0c;容量大,耗电量低,传输速度快&#xff0c;广泛应用于消费类电子产品中。 1.2.什么是SD卡…

Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇

回顾一下前面的知识&#xff0c;MMC 子系统范围三个部分&#xff1a; HOST 部分是针对不同主机的驱动程序&#xff0c;这一部是驱动程序工程师需要根据自己的特点平台来完成的。 CORE 部分: 这是整个MMC 的核心存&#xff0c;这部分完成了不同协议和规范的实现&#xff0c;并为…

MVC应用程序显示RealPlayer(rm)视频

本篇博文是演示MVC应用程序显示RealPlayer视频。 客户端能观看到RealPlayer视频&#xff0c;前提条件是需要安装RealPlayer客户端&#xff0c;就是想看Falsh或理WMV视频一样&#xff0c;均要安装客户端或相关插件等。 Insus.NET实现方法&#xff0c;还是在控制器中Render RealP…

Linux从入门到精通系列之PPTP

Linux从入门到精通系列之PPTP今天我们来说下怎么在linux环境下如何搭建PPTP-&#xff0c;PPTP&#xff08;Point to Point Tunneling Protocol&#xff09;&#xff0c;即点对点隧道协议。该协议是在PPP协议的基础上开发的一种新的增强型安全协议&#xff0c;支持多协议虚拟专用…

Linux SD卡驱动开发(三) —— SD 卡驱动分析CORE篇

废话不多说&#xff0c;直接切进主题&#xff1a; Linux在内核源码的drivers/mmc/core文件夹下为我们的提供了一系列SD卡的接口服务函数。可以查看Makefile如下 可见&#xff0c;core文件夹下有针对总线的服务bus.c&#xff0c;针对主控制器的服务host.c&#xff0c;针对SD卡的…

Python数值计算:一 使用Pylab绘图(1)

Pylab的使用 学习使用Python进行科学计算&#xff0c;然而很难找到简单实用&#xff0c;又循序渐进的例子。正好手边有一本《Matlab可视化大学物理学》&#xff0c;里面的例子非常清晰地解释了Matlab在物理学中的应用。重新使用Python实现这些例子&#xff0c;学习了Python&…