美文网首页
[读书笔记]于硬件通信(第九章)

[读书笔记]于硬件通信(第九章)

作者: c枫_撸码的日子 | 来源:发表于2018-10-29 15:04 被阅读0次

1.I/O端口和I/O内存

每种外设都通过读写寄存器进行控制,不管是在内存地址空间,还是I/O地址空间
在硬件层,内存区域和IO区域没有概念的区别:它们都是通过地址总线和控制总线发送电平信号(比如读写信号)进行访问,在通过数据总线读写数据。

I/O寄存器和常规内存

1.在访问I/O寄存器的时候必须之一避免由于cpu或编译器不恰当的优化而改变预期的I/O动作。驱动程序必须确保不使用高速缓存,并且在访问寄存器时不发生读写指令的重新排序。
2.硬件自身缓存的解决办法:把底层硬件配置成在访问I/O区域(不管是内存还是端口)时禁止硬件缓存
3.解决编译器自作聪明的优化和硬件重新排序的方法:
对硬件必须以特定顺序执行的操作之间设置内存屏障,Linux中提供了4个宏来解决所有肯的排序问题:

#include <linux/kernel.h>
void barrier(void);
该函数通知编译器插入一个内存屏障,但对硬件没有影响。
编译后的代码会把当前cpu寄存器的中所有修改过的数值保存到内存中,
需要的时候在重新读取出来。
对barrier的调用可避免在屏障前后的编译器优化,但硬件能完成自己的重新排序。
#include <asm/system.h>
void rmb(void);
void read_barrier_depenfs(void);
void wmb(void);
void mb(void);
rmb(读内存屏障)保证了屏障之前的读操作一定会在后来的读操作执行之前完成。
wmb保证写不会乱序,mb保证读写都不会乱序。
read_barrier_depends较为特殊,仅仅阻止某些读取操作的重新排序,
除非开发者能够正确理解read_barrier_depends和rmb,否则就应该始终使用rmb。

smp系统的屏障

void smp_rmb(void);
void smp_read_barriers_depends(void);
void smp_wmb(void);
void smp_wb(void);
上述屏障宏版本也插入硬件屏障,但仅仅在内核针对SMP系统编译时有效。

设备驱动使用内存屏障的典型代码

write_(dev->registers.addr,io_destianation_address);
write_(dev->registers.size,io_size);
write_(dev->registers.operation,DEV_READ);
wmb();
write_(dev->registers.control,DEV_GO);

内核空间使用I/O端口

I/O端口分配
声明自己需要操作的端口

#include <linux/ioport.h>
struct resource *request_region(unsigned long first,unsigned long n,
                                const char *name);

使用起始于first的n个端口,name是设备的名称。
查看分配的端口
cat proc/ioports
释放端口

void release_region(unsigned long start,unsigned long n);

检查端口是否可用

void check_region(unsigned long first,unsigned long n);

如果端口不可用,返回值是负的错误码,但是不赞成使用该函数。因为其操作不是原子的操作,返回值并不能代表什么,推荐使用request_region();

操作I/O端口

unsigned inb(unsigned port);
void outb(unsigned chat byte,unsigned port);
字节(8位宽度)读写端口。port参数在一些平台上被定义为unsigned long,
而其它平台被定义为unsigned short 。不同平台的inb返回值类型也不相同。

ungiened inw(unsigned port);
void outw(unsigned short word,unsigned port);
访问16位端口

unsigned inl(unsigned port)
void outl(unsigned long word,unsigend port)
访问32位端口

在用户空间访问I/O端口

GNU的c库在 <sys/io.h>中定义了这些函数
如果要在代码中使用inb及其相关函数,必须满足一些条件。

串操作

之前的I/O操作都是一次传输一个数据,下面的指令一次传输一个数据序列(序列中的数据单位可以是字节、字、双字)。

void insb(unsigned port,void * addr,unsigned long count);
void outsb(unsigned port,void * addr,unsigned long count);

从内存地址addr开始连续读/写count数目的字节,只针对单一端口。
(8位)

void intsw(unsigned port,void *addr,unsigned long count);
void intsw(unsigned port,void *addr,unsigned long count);

对一个16位端口读/写32位数据

void insl(unsigned port,void *addr,unsigned long count);
void outsl(unsigned port,void *addr,unsigned long count);

对一个32位端口读/写32位数据

使用I/O内存

分配内存

#include <linux/ioprot.h>
struct resource *request_mem_region(unsigned long start.unsigned long len,
                                    char *name);

从start开始分配len字节长的内存区域。
可以通过cat proc/iomem查看
映射内存
分配IO内存之后,必须先建立映射才能访问。

#include <asm/io.h>
void *ioremap(unsigned long phts_addr,unsigned long size);
void *ioremap_nocache(unsigned long  phys_addr,unsigned long size);
void *iounmap(void * addr);

访问IO内存
虽然可以将ioremap返回值直接当作指针使用,但是不推荐。
访问IO内存的正确方法是通过以下专用函数。
1.读取

unsigned int ioread8(void *addr);
unsigned int ioread16(void * addr);
unsigned int ioread32(void *addr);

这里的addr应该是ioremap获得的地址
2.写入

void iowrite8(u8 value,void *addr);
void iowrite16(u16 value,void *addr);
void iowrite32(u32 value,void *addr);

3.读写

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);

从给定的buf向给定的addr读取或者写入count的值
上面的函数是在给定的addr操作,如果想操作一块IO内存,则:
在一块IO内存上操作

void memset_io(void *addr,u8 value,unsigned int count);
void memcpy_fromio(void *dest,void *source,unsigned int count);
void memcpy_toio(void *dest,void *source,unsigned int count);

释放内存

void release_mem_region(unsigned long start,unsigned long len);

检测I/O内存区域是否可用

int check_mem_region(unsigned long start,unsigned long len);

和check_region一样,该函数不安全,避免使用。


老的IO内存函数
这些函数不执行类型检测,安全性差,不推荐使用!

unsigned readb(address);
unsigned readw(address);
unsigned readl(address);

unsigned writeb(unsigned value,address);
unsigned writew(unsigned value,address);
unsigned writel(unsigned value,address);

像IO内存一样使用端口

void *ioport_map(unsigned long port,unsigned int count);

该函数重新映射count个IO端口,使其看起来像IO内存,这样,驱动程序可在该函数返回的的地址上使用ioread8机器同类函数,不必理会IO端口和IO内存之前的区别。

void ioport_unmap(void * addr);

这些函数是的IO端口看起来像是IO内存,但需要注意的是,在重新银蛇之前,必须通过request_region来分配这些IO端口。

相关文章

网友评论

      本文标题:[读书笔记]于硬件通信(第九章)

      本文链接:https://www.haomeiwen.com/subject/wiortqtx.html