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 架构,该部分的电路原理图如下:

从图中可以看到整个定时计数器芯片 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 和控制寄存器,其对应属性说明如下:

其中,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 <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/timex.h>
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/基本时钟设备概念/