I/O映射之I/O端口

对于外设,操作系统会采用端口映射和内存映射两种方式来对其进行控制,其中端口映射就是操作系统规定好一段地址给指定换上设,其与外设的寄存器按顺序一一对应上。

在Linux 内核源码 include/asm-generic/io.h 头文件里有如下我们对 I/O port 进行操作的函数:

static inline u8 inb(unsigned long addr)
{
    return readb(addr + PCI_IOBASE);
}

static inline u16 inw(unsigned long addr)
{
    return readw(addr + PCI_IOBASE);
}

static inline u32 inl(unsigned long addr)
{
    return readl(addr + PCI_IOBASE);
}

static inline void outb(u8 b, unsigned long addr)
{
    writeb(b, addr + PCI_IOBASE);
}

static inline void outw(u16 b, unsigned long addr)
{
    writew(b, addr + PCI_IOBASE);
}

static inline void outl(u32 b, unsigned long addr)
{
    writel(b, addr + PCI_IOBASE);
}

#define inb_p(addr) inb(addr)
#define inw_p(addr) inw(addr)
#define inl_p(addr) inl(addr)
#define outb_p(x, addr) outb((x), (addr))
#define outw_p(x, addr) outw((x), (addr))
#define outl_p(x, addr) outl((x), (addr))

分别对应对端口进行读取或写入字节、字、双字大小数据的操作。该头文件中还有其他的操作函数及宏定义,请自行深入了解。我们可通过读取/proc/ioports 文件来了解 Linux 里 I/O 端口的映射情况。

本次的实验是要参考源码 drivers/input/misc/pcspkr.c 驱动对 PC 机主板上的蜂鸣器进行操作,让其发声,而 PC 主机上一般使用 8253 或 8254 定时器来产生 PWM 信号驱动蜂鸣器响,对于标准的 X86 架构,该部分的电路原理图如下:

8253 或 8254 定时器

从图中可以看到整个定时计数器芯片 8254 由 GATE2 控制其使能状况,该 PIN 由 PB0 控制,其输出 OUT2(8254 内含 3 个定时计数器,这里与蜂鸣器连接的是 Timer2),该输出与 PB1 通过与门后才将最终的信号输出给 Beep 使用,所以要让 Beep 发声有如下两条件:

1.PB0 和 PB1 都为高电平;
2.8254 里的 Timer2 输出指定频率的正弦波信号。

其中,

PB0 和 PB1 分别对应 I/O 端口 0x61 的 D0 和 D1 位(即低两位),故而等会让 Beep出声时需要让 0x61 的值“或”上 0x03,而不让其出声时则“与”上 0xfc。

而8254 定时计数器对应的端口地址是 0x40~0x43,分别对应 Timer0、1、2 和控制寄存器,其对应属性说明如下:

Timer0、1、2 和控制寄存器

其中,Counter0-2 这三个定时计数器都是 16 位的,而其数据线宽是 8 位的。其使用的外部晶振频率是 1.193182MHz,在内核源码 include/linux/timex.h 中有#define PIT_TICK_RATE 1193182ul 宏定义。对于控制寄存器,其格式如下:

控制寄存器

分别说明如下:

1.SC:Select Counter
    00-Counter0
    01-Counter1
    10-Counter2
    11-Read-Back Command,仅对 8254 有效,对 8253 无效

2.RW:Read/Write
    00-Counter Latch Command,锁存计数器的值
    01-Read/Write least significant byte only,只读写 LSB,即低 8 位
    10-Read/Write most significant byte only,只读写 MSB,即高 8 位
    11-Read/Write LSB first,then MSB,先读写 LSB 再读写 MSB,即先低 8 位后高 8 位

3.M:Mode
    000-Mode0,interrupt on terminal count
    001-Mode1,hardware retriggerable one-shot
    x10-Mode2,rate generator
    x11-Mode3,square warve mode
    100-Mode4,software triggered strobe
    101-Mode5,hardware triggered strobe(retriggerable)
    其中,x 为可 0 可 1,后期兼容,默认设置 0。

4.BCD
    0-Binary Counter 16-bits
    1-Binary Coded Decimal(BCD) Counter(4 Decades)

有了上面的资料后,要如何操作呢?首先是设置控制寄存器,然后写指定计数器的初值,该初值则与我们上面提到的 PIT_TICK_RATE 相关,当我们想要设置相应计数器每秒钟中断 M次,即中断频率为 M,那么需要设置相应的计数器值为 PIT_TICK_RATE/M。

  好了,了解了这么多理论后,接下来还是实例感受下吧:

#include
#include
#include
#include

static int value = 0;
module_param(value, int, 0644);

MODULE_PARM_DESC(value, "the beep frequences(20,32767)");

static __init int ioport_beep_init(void)
{
    unsigned int count = 0;
    if (value > 20 && value < 32767)
        count = PIT_TICK_RATE / value;

    if (count) {
        /* set command for counter 2, write 2 bytes */
        outb_p(0xB6, 0x43);
        /* select desired HZ */
        outb_p(count & 0xff, 0x42); //write low 8 bits first
        outb((count >> 8) & 0xff, 0x42); //write hight 8 bits
        /* enable counter 2 */
        outb_p(inb_p(0x61) | 3, 0x61);
    } else {
        /* disable counter 2 */
        outb(inb_p(0x61) & 0xFC, 0x61);
    }

    return 0;
}

static __exit void ioport_beep_exit(void)
{
    /*disable counter 2 */
    outb(inb_p(0x61) & 0xFC, 0x61);

    printk("Bye ioport_beep!\n");
}

module_init(ioport_beep_init);
module_exit(ioport_beep_exit);

MODULE_LICENSE("GPL");

相应的 Makfile 文件内容如下:

obj-m += ioport_beep.o

CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6

all:
    make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean

  对应的源码文件目录树如下:

/home/xinu/xinu/linux_kernel_driver_l1/ioport_beep_example/
├── ioport_beep.c
└── Makefile

  编译后通过如下命令就可以加载模块了:

sudo insmod ioport_beep.ko value=20
sudo insmod ioport_beep.ko value=30000
sudo insmod ioport_beep.ko

其中,第一条声音会偏高(大),第二条会偏低(小),第三条是关闭,而在 rmmod 的时候也是会关闭。而 value 的值在(20,32767)之间,其他值均会关闭蜂鸣器。

至此,对于I/O 端口映射的使用和 PC 机蜂鸣器的使用都过了一遍,还有后期有个驱动模块实例会更吸引你,记得留意后期实例。

参考网址:
http://blog.chinaunix.net/uid-21347954-id-443670.html
http://www.ibm.com/developerworks/cn/linux/l-cn-directio/
http://oss.org.cn/kernel-book/ldd3/ch09s03.html
http://oss.org.cn/kernel-book/ldd3/ch09s02.html
http://exbob.com/post/linux/2013-01-24-linux_beep
http://way4ever.com/?tag=8254
http://www.intel.com/content/www/us/en/chipsets/82801db-io-controller-hub-4-datasheet.html
http://www.stanford.edu/class/cs140/projects/pintos/specs/8254.pdf
http://soft.zdnet.com.cn/software_zone/2007/1026/582971.shtml
http://article.yeeyan.org/view/197439/241694
http://mnstory.net/2014/03/基本时钟设备概念/

你可能感兴趣的:(I/O映射之I/O端口)