Communicating with Hardware [LDD3 08]

本章开始讲一些跟hardware有关的操作,比如I/O port,register等。

device driver的角色要清楚,它只是中间层,向上有kernel,user application,向下就是actual hardware。作为driver,核心功能必然是驱动硬件。

I/O Ports and I/O Memory

每一个外设都要通过register来控制,大多数设备都有不止一个register,并且这些register访问地址是连续,要么是IO地址空间,要么是memory 的地址空间。在实际haredware的角度,这二者没有本质区别,都是向控制总线发送控制信号,并通过数据总线来读写。不过不同的架构支持不同的方式,比如有些是统一的地址空间,有些是分开的,比如X86上,就有单独的总线来访问I/O port,并且有特殊的CPU指令访问这些ports。

因为所有的外设都要适配一定的总线,为了做IO,CPU必然也要支持这些IO,所以kernel自然也要支持CPU的这些IO操作。

尽管外设总线支持IO port,但是不是所有的设备都会把register map到IO port上,ISA外设通常会这么用,但是大部分PCI设备会把register map到memory的地址空间。使用memory地址空间更推荐,因为不需要特殊的CPU指令,访问memory更高效,并且编译器也更友好。

I/O Registers and Conventional Memory

虽然IO register和memory看上去很像,但是IO reigster 的访问还是有一些特殊的地方。比如IO的reigster读写会有副作用,因为CPU或者编译器会对指令进行优化,这样有可能导致读写register的顺序发生变化,最终出现问题。因此,kernel提供了一些函数来保证读写寄存器的时序:

#include <linux/kernel.h>
//barrier()是告诉编译器,此处插入一个memory的barrier,但是不影响hardware。code会把所有写的value全部放到CPU的cache,后续需要的时候再读。
void barrier(void)

#include <asm/system.h>
//插入一个hardware的read barrier,保证再次之前的read全部完成。
void rmb(void);
//插入一个hardware的write barrier,保证再次之前的write全部完成。
void wmb(void);
//插入一个hardware的barrier,保证再次之前的read和write全部完成。
void mb(void);
//这个读比较特殊,也是read barrier的一种,但是只会把依赖于别人read数据的read做barrier。
void read_barrier_depends(void);

//下面的几个只有SMP上才有效,否则就是一个barrier()。
void smp_rmb(void);
void smp_read_barrier_depends(void);
void smp_wmb(void);
void smp_mb(void);

一个典型的用法可能是这样的:

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

在所有的必要条件都ready之后,使用一次wmb,保证所有的register都会写进去,然后kick off hardware。

要注意,barrier是会影响performance的,所以使用的时候要确保一定需要才加,并且要准确的使用barrier。

Using I/O Ports

有些device,通过I/O Ports来控制。首先,在使用port之前,要保证你是排他的访问,port不可以共享:

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

调用这个函数,就是告诉kernel你要使用n个ports,第一个从first开始,name就是device的name,返回值表明获取IO port成功。所有被打开的port都可以在/proc/ioports这里看到。如果不需要port了,一般是在driver unload的时候,那就释放:

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

其实在获取port资源之前,可以向kernel query,查看port是否可用:

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

但是这个函数已经废弃不用了。因为查询到可用,不代表等会儿你去分的时候仍然可用。

Manipulating I/O ports

在获取到port资源以后,就会读或者写这些port,因为port的不同,读写可能是8/16/32 bit的,每次数据大小是固定的,而且需要调用不同的函数才可以。因为有些架构不支持IO port,只支持memory access,kernel针对这些架构,会将IO port操作,转换为memory的access,并向上提供了统一的接口。

//byte的形式读写
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);

//word的形式读写
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);

//longword的形式读写,一般是32bit
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);

最多就是32bit,即便是在64bit的机器上,IO的读写最多只有32bit。

I/O Port Access from User Space

在user mode来访问IO port,这里不讲了。因为IO port用的没那么普遍,所以重点还是放在IO memory。

Using I/O Memory

直接拿LDD3的定义:

I/O memory is simply a region of RAM-like locations that the device makes available to the processor over the bus.

根据当前计算机platform或者使用的bus的不同,IO memory可以通过page 访问。前提条件是针对这个IO memory有physical memory对应,并且通过ioremap做过map,这样kernel的page table就有entry,从而可以像访问普通的memory一样访问IO memory。如果没有page table,只能使用warpper的函数操作。

I/O Memory Allocation and Mapping

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

这个函数从start开始,分配了len byte的IO memory,name就是device name。所有的IO memory在/proc/iomem下都可以看到。如果需要释放:

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

然后,很多时候通过request_mem_region拿到的memory并不能直接被kernel访问,在访问之前,还需要做remap。

#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 * addr);

在map之后,就可以访问这些IO memory了。

Accessing I/O Memory

ioremap会返回指针地址,但是kernel不推荐直接使用,应该使用wrapper:

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

//如果要读写比较多的数据,可以使用repeate版本,参数里是buf。
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);

//一次读写一个block
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);

//废弃不用的一些function
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);

这里的addr就是remap获取到的地址。

ISA Memory Below 1 MB

考虑到ISA总线已经几乎不用,这里就不讨论了。

发布了32 篇原创文章 · 获赞 6 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/scutth/article/details/105385324