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架构,该部分的电路原理图如下:
X86架构下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和控制寄存器,其对应属性说明如下:
端口
描述
40H
Counter 0
41H
Counter 1
42H
Counter 2
43H
Control Word Register
其中,Counter0-2这三个定时计数器都是16位的,而其数据线宽是8位的。
其使用的外部晶振频率是1.193182MHz,在内核源码include/linux/timex.h中有#define PIT_TICK_RATE 1193182ul宏定义。
对于控制寄存器,其格式如下:
D7
D6
D5
D4
D3
D2
D1
D0
SC1
SC0
RW1
RW0
M2
M1
M0
BCD
分别说明如下:
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=30
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/基本时钟设备概念/

评论

此博客中的热门博文

通过Netlink检测网线插拔

使用seq_file