I/O映射之I/O端口
对于外设,操作系统会采用端口映射和内存映射两种方式来对其进行控制,其中端口映射就是操作系统规定好一段地址给指定换上设,其与外设的寄存器按顺序一一对应上。
在Linux内核源码include/asm-generic/io.h头文件里有如下我们对I/O port进行操作的函数:
在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架构,该部分的电路原理图如下:
该头文件中还有其他的操作函数及宏定义,请自行深入了解。
我们可通过读取/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和控制寄存器,其对应属性说明如下:
1.PB0和PB1都为高电平;
2.8254里的Timer2输出指定频率的正弦波信号。
其中,PB0和PB1分别对应I/O端口0x61的D0和D1位(即低两位),故而等会让Beep出声时需要让0x61的值“或”上0x03,而不让其出声时则“与”上0xfc。
而8254定时计数器对应的端口地址是0x40~0x43,分别对应Timer0、1、2和控制寄存器,其对应属性说明如下:
- 端口描述40HCounter 041HCounter 142HCounter 243HControl Word Register
其中,Counter0-2这三个定时计数器都是16位的,而其数据线宽是8位的。
其使用的外部晶振频率是1.193182MHz,在内核源码include/linux/timex.h中有#define PIT_TICK_RATE 1193182ul宏定义。
对于控制寄存器,其格式如下:
其使用的外部晶振频率是1.193182MHz,在内核源码include/linux/timex.h中有#define PIT_TICK_RATE 1193182ul宏定义。
对于控制寄存器,其格式如下:
- D7D6D5D4D3D2D1D0SC1SC0RW1RW0M2M1M0BCD
分别说明如下:
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。
好了,了解了这么多理论后,接下来还是实例感受下吧:
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/基本时钟设备概念/
/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/基本时钟设备概念/
评论
发表评论