【原创】《Linux设备驱动程序》学习之循序渐进 --- 与硬件通信
2014-07-24 14:38
281 查看
【原创】《Linux设备驱动程序》学习之循序渐进 --- 与硬件通信
一个驱动必须确保没有进行缓冲并且在存取寄存器时没有发生读或写的重编排.
硬件缓冲的问题是最易面对的:底层的硬件已经配置(或者自动地或者通过 Linux 初始化代码)成禁止任何硬件缓冲, 当存取 I/O 区时(不管它们是内存还是端口区域).
对编译器优化和硬件重编排的解决方法是安放一个内存屏障在必须以一个特殊顺序对硬件(或者另一个处理器)可见的操作之间.
因为内存屏障影响性能, 它们应当只用在确实需要它们的地方. 屏障的不同类型也有不同的性能特性, 因此值得使用最特定的可能类型.
值得注意大部分的其他的处理同步的内核原语, 例如自旋锁和原子的 _t 操作, 如同内存屏障一样是函数.
用来存取 I/O 内存的包装函数在所有平台上是安全的并且在任何时候直接的指针解引用能够进行操作时, 会被优化掉.
如果你通览内核源码, 你可看到许多调用旧的一套函数, 当使用 I/O 内存时. 这些函数仍然可以工作, 但是它们在新代码中的使用不鼓励. 除了别的外, 它们较少安全因为它们不进行同样的类型检查.
void barrier(void)
这个"软件"内存屏蔽要求编译器对待所有内存是跨这个指令而非易失的.
#include <asm/system.h>
void rmb(void);
void read_barrier_depends(void);
void wmb(void);
void mb(void);
硬件内存屏障. 它们请求 CPU(和编译器)来检查所有的跨这个指令的内存读, 写, 或都有.
#include <asm/io.h>
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
unsigned inl(unsigned port);
void outl(unsigned doubleword, unsigned port);
用来读和写 I/O 端口的函数. 它们还可以被用户空间程序调用, 如果它们有正当的权限来存取端口.
unsigned inb_p(unsigned port);
如果在一次 I/O 操作后需要一个小延时, 你可以使用在前一项中介绍的这些函数的 6 个暂停对应部分; 这些暂停函数有以 _p 结尾的名子.
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
这些"字串函数"被优化为传送数据从一个输入端口到一个内存区, 或者其他的方式. 这些传送通过读或写到同一端口 count 次来完成.
#include <linux/ioport.h>
struct resource *request_region(unsigned long start, unsigned long len, char *name);
void release_region(unsigned long start, unsigned long len);
int check_region(unsigned long start, unsigned long len);
I/O 端口的资源分配器. 这个检查函数成功返回 0 并且在错误时小于 0.
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
void release_mem_region(unsigned long start, unsigned long len);
int check_mem_region(unsigned long start, unsigned long len);
为内存区处理资源分配的函数
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void *virt_addr);
ioremap 重映射一个物理地址范围到处理器的虚拟地址空间, 使它对内核可用. iounmap 释放映射当不再需要它时.
#include <asm/io.h>
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
用来使用 I/O 内存的存取者函数.
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
I/O 内存原语的"重复"版本.
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
memset_io(address, value, count);
memcpy_fromio(dest, source, nbytes);
memcpy_toio(dest, source, nbytes);
旧的, 类型不安全的存取 I/O 内存的函数.
void *ioport_map(unsigned long port, unsigned int count);
void ioport_unmap(void *addr);
一个想对待 I/O 端口如同它们是 I/O 内存的驱动作者, 可以传递它们的端口给 ioport_map. 这个映射应当在不需要的时候恢复( 使用
ioport_unmap )
第九章 --- 与硬件通信
I/O 端口和 I/O 内存
I/O 寄存器和 RAM 的主要不同是 I/O 操作有边际效果, 而内存操作没有: 一个内存写的唯一效果是存储一个值到一个位置, 并且一个内存读返回最近写到那里的值.一个驱动必须确保没有进行缓冲并且在存取寄存器时没有发生读或写的重编排.
硬件缓冲的问题是最易面对的:底层的硬件已经配置(或者自动地或者通过 Linux 初始化代码)成禁止任何硬件缓冲, 当存取 I/O 区时(不管它们是内存还是端口区域).
对编译器优化和硬件重编排的解决方法是安放一个内存屏障在必须以一个特殊顺序对硬件(或者另一个处理器)可见的操作之间.
因为内存屏障影响性能, 它们应当只用在确实需要它们的地方. 屏障的不同类型也有不同的性能特性, 因此值得使用最特定的可能类型.
值得注意大部分的其他的处理同步的内核原语, 例如自旋锁和原子的 _t 操作, 如同内存屏障一样是函数.
串操作
有件事要记住, 当使用字串函数时: 它们移动一个整齐的字节流到或自端口. 当端口和主系统有不同的字节对齐规则, 结果可能是令人惊讶的. 使用 inw 读取一个端口交换这些字节, 如果需要, 来使读取的值匹配主机字节序. 字串函数, 相反, 不进行这个交换.暂停式 I/O
解决方法是插入一个小的延时在每个 I/O 指令后面, 如果跟随着另一个指令. 在 x86 上, 这个暂停是通过进行一个 outb 指令到端口 0x80 ( 正常地不是常常用到 )实现的, 或者通过忙等待.使用 I/O 内存
依赖计算机平台和使用的总线, I/O 内存可以或者不可以通过页表来存取. 当通过页表存取, 内核必须首先安排从你的驱动可见的物理地址, 并且这常常意味着你必须调用 ioremap 在做任何 I/O 之前. 如果不需要页表, I/O 内存位置看来很象 I/O 端口, 并且你只可以使用正确的包装函数读和写它们.用来存取 I/O 内存的包装函数在所有平台上是安全的并且在任何时候直接的指针解引用能够进行操作时, 会被优化掉.
访问 I/O 内存
在一些平台上, 你可能逃过作为一个指针使用 ioremap 的返回值的惩罚. 这样的使用不是可移植的, 并且, 更加地, 内核开发者已经努力来消除任何这样的使用. 使用 I/O 内存的正确方式是通过一系列为此而提供的函数(通过 <asm/io.h> 定义的).如果你通览内核源码, 你可看到许多调用旧的一套函数, 当使用 I/O 内存时. 这些函数仍然可以工作, 但是它们在新代码中的使用不鼓励. 除了别的外, 它们较少安全因为它们不进行同样的类型检查.
快速参考
#include <linux/kernel.h>void barrier(void)
这个"软件"内存屏蔽要求编译器对待所有内存是跨这个指令而非易失的.
#include <asm/system.h>
void rmb(void);
void read_barrier_depends(void);
void wmb(void);
void mb(void);
硬件内存屏障. 它们请求 CPU(和编译器)来检查所有的跨这个指令的内存读, 写, 或都有.
#include <asm/io.h>
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
unsigned inl(unsigned port);
void outl(unsigned doubleword, unsigned port);
用来读和写 I/O 端口的函数. 它们还可以被用户空间程序调用, 如果它们有正当的权限来存取端口.
unsigned inb_p(unsigned port);
如果在一次 I/O 操作后需要一个小延时, 你可以使用在前一项中介绍的这些函数的 6 个暂停对应部分; 这些暂停函数有以 _p 结尾的名子.
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
这些"字串函数"被优化为传送数据从一个输入端口到一个内存区, 或者其他的方式. 这些传送通过读或写到同一端口 count 次来完成.
#include <linux/ioport.h>
struct resource *request_region(unsigned long start, unsigned long len, char *name);
void release_region(unsigned long start, unsigned long len);
int check_region(unsigned long start, unsigned long len);
I/O 端口的资源分配器. 这个检查函数成功返回 0 并且在错误时小于 0.
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
void release_mem_region(unsigned long start, unsigned long len);
int check_mem_region(unsigned long start, unsigned long len);
为内存区处理资源分配的函数
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void *virt_addr);
ioremap 重映射一个物理地址范围到处理器的虚拟地址空间, 使它对内核可用. iounmap 释放映射当不再需要它时.
#include <asm/io.h>
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
用来使用 I/O 内存的存取者函数.
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
I/O 内存原语的"重复"版本.
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
memset_io(address, value, count);
memcpy_fromio(dest, source, nbytes);
memcpy_toio(dest, source, nbytes);
旧的, 类型不安全的存取 I/O 内存的函数.
void *ioport_map(unsigned long port, unsigned int count);
void ioport_unmap(void *addr);
一个想对待 I/O 端口如同它们是 I/O 内存的驱动作者, 可以传递它们的端口给 ioport_map. 这个映射应当在不需要的时候恢复( 使用
ioport_unmap )
原文链接:
http://blog.csdn.net/geng823/article/details/38084035相关文章推荐
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 与硬件通信
- Linux设备驱动程序第三版学习(12)- 与硬件通信
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 时间、延迟及延缓操作
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 中断处理
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 中断处理
- 《LINUX设备驱动程序》学习之与硬件通信(并行接口)实例
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 高级字符驱动程序操作
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 高级字符驱动程序操作
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 内核数据类型
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 调试技术
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 字符设备驱动
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 时间、延迟及延缓操作
- Linux设备驱动程序第三版学习(12)- 与硬件通信
- 《LINUX设备驱动程序》学习之与硬件通信(并行接口)实例
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 分配内存
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 调试技术
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 字符设备驱动
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 内核数据类型
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 并发和竟态
- 【原创】《Linux设备驱动程序》学习之循序渐进 --- 分配内存