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

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

作者: 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