背景信息
通过写入和读取其寄存器来控制外围设备。 通常,设备具有多个寄存器,可以在内存地址空间或I / O地址空间中的连续地址处访问多个寄存器。 连接到I / O总线的每个设备都有一组I / O地址,称为I / O端口。 可以将I / O端口映射到物理内存地址,以便处理器可以通过直接与内存配合的指令与设备通信。 为简单起见,我们将直接使用I / O端口(不映射到物理内存地址)与物理设备进行通信。
寄存器
每个设备都有如下的寄存器
- 控制寄存器(用来接收设备命令)
- 状态寄存器(包含设备的内部状态信息)
- 输入寄存器(用来存放来自设备的输入数据)
- 输出寄存器(用来存放即将输出到设备的数据)
主要的你问题在于发生在特殊的场景当中,砸死不确定的时间,使得CPU没办法重复获取设备的状态。解决这一问题的办法是使用中断:Interrupt ReQuest(IRQ)。所以设备驱动必定要实现中断处理器,即中断处理程序。设备驱动要共享中断并做好中断的同步。
当我们需要访问在中断例程A和进程上下文或者进程上下文的下半段B之前共享的资源时,我们需要使用特殊的同步技术。在A中,我们需要使用自旋锁,在B中,我们必定要禁止中断并且使用自旋锁原语。
例如,串口COM1拥有基址0x3F8并且有8个端口。
请求访问IO端口
在访问IO端口之前,我们首先发出请求.为了确保只有一个用户对其进行访问,使用request_region函数
#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name), 0)
/**
* __request_region - create a new busy resource region
* @parent: parent resource descriptor
* @start: resource start address
* @n: resource region size
* @name: reserving caller's ID string
* @flags: IO resource flags
*/
struct resource * __request_region(struct resource *parent,
resource_size_t start, resource_size_t n,
const char *name, int flags)
{
DECLARE_WAITQUEUE(wait, current);
struct resource *res = alloc_resource(GFP_KERNEL);
if (!res)
return NULL;
res->name = name;
res->start = start;
res->end = start + n - 1;
res->flags = resource_type(parent);
res->flags |= IORESOURCE_BUSY | flags;
write_lock(&resource_lock);
for (;;) {
struct resource *conflict;
conflict = __request_resource(parent, res);
if (!conflict)
break;
if (conflict != parent) {
if (!(conflict->flags & IORESOURCE_BUSY)) {
parent = conflict;
continue;
}
}
if (conflict->flags & flags & IORESOURCE_MUXED) {
add_wait_queue(&muxed_resource_wait, &wait);
write_unlock(&resource_lock);
set_current_state(TASK_UNINTERRUPTIBLE);
schedule();
remove_wait_queue(&muxed_resource_wait, &wait);
write_lock(&resource_lock);
continue;
}
/* Uhhuh, that didn't work out.. */
free_resource(res);
res = NULL;
break;
}
write_unlock(&resource_lock);
return res;
}
例如,串口com1有基址0x3F8,并且它有8个端口,下面的代码片段说明如何对这些端口的请求访问.
#include <linux/ioport.h>
#define MY_BASEPORT 0x3F8
#define MY_NR_PORTS 8
if (!request_region(MY_BASEPORT, MY_NR_PORTS, "com1")) {
/* handle error */
return -ENODEV;
}
为了释放这些端口,可以使用
release_region(MY_BASEPORT, MY_NR_PORTS);//释放的时候不需要name参数
#define release_region(start,n) __release_region(&ioport_resource, (start), (n))
/**
* __release_region - release a previously reserved resource region
* @parent: parent resource descriptor
* @start: resource start address
* @n: resource region size
*
* The described resource region must match a currently busy region.
*/
void __release_region(struct resource *parent, resource_size_t start,
resource_size_t n)
{
struct resource **p;
resource_size_t end;
p = &parent->child;
end = start + n - 1;
write_lock(&resource_lock);
for (;;) {
struct resource *res = *p;
if (!res)
break;
if (res->start <= start && res->end >= end) {
if (!(res->flags & IORESOURCE_BUSY)) {//如果忙,就找儿子
p = &res->child;
continue;
}
if (res->start != start || res->end != end)
break;
*p = res->sibling;//找兄弟
write_unlock(&resource_lock);//释放资源锁
if (res->flags & IORESOURCE_MUXED)
wake_up(&muxed_resource_wait);//唤醒资源队列
free_resource(res);
return;
}
p = &res->sibling;
}
write_unlock(&resource_lock);
printk(KERN_WARNING "Trying to free nonexistent resource "
"<%016llx-%016llx>\n", (unsigned long long)start,
(unsigned long long)end);
}
在大多数情况下,端口请求在设备初始化或者探测设备的时候完成。端口的释放在移除设备或者模块的时候完成。
所有的对端口的请求可以通过 /proc/ioports文件中看到
acat@acat-xx:~$ sudo cat /proc/ioports
[sudo] acat 的密码:
0000-0cf7 : PCI Bus 0000:00
0000-001f : dma1
0020-0021 : pic1
0040-0043 : timer0
0050-0053 : timer1
0060-0060 : keyboard
0062-0062 : PNP0C09:00
0062-0062 : EC data
0064-0064 : keyboard
0066-0066 : PNP0C09:00
0066-0066 : EC cmd
0070-0077 : rtc0
0080-008f : dma page reg
00a0-00a1 : pic2
00c0-00df : dma2
00f0-00ff : fpu
0680-069f : pnp 00:03
0cf8-0cff : PCI conf1
0d00-ffff : PCI Bus 0000:00
164e-164f : pnp 00:03
1800-18fe : pnp 00:01
1800-1803 : ACPI PM1a_EVT_BLK
1804-1805 : ACPI PM1a_CNT_BLK
1808-180b : ACPI PM_TMR
1810-1815 : ACPI CPU throttle
1850-1850 : ACPI PM2_CNT_BLK
1854-1857 : pnp 00:05
1860-187f : ACPI GPE0_BLK
2000-20fe : pnp 00:02
3000-3fff : PCI Bus 0000:01
3000-307f : 0000:01:00.0
4000-403f : 0000:00:02.0
4040-405f : 0000:00:1f.4
4060-407f : 0000:00:17.0
4060-407f : ahci
4080-4087 : 0000:00:17.0
4080-4087 : ahci
4088-408b : 0000:00:17.0
4088-408b : ahci
fd60-fd63 : pnp 00:03
访问IO端口
当驱动已经获取了目标端口的端口范围,那么就可以在这些端口上执行读写操作了。针对不同比特的端口,如8,16,后者32bit,有不同的端口访问方法。
- unsigned inb(int port), reads one byte (8 bits) from port
- void outb(unsigned char byte, int port), writes one byte (8 bits) to port
- unsigned inw(int port), reads two bytes (16-bit) ports
- void outw(unsigned short word, int port), writes two bytes (16-bits) to port
- unsigned inl (int port), reads four bytes (32-bits) from port
- void outl(unsigned long word, int port), writes four bytes (32-bits) to port
port当读或者写完成的时候,port参数确定端口的地址.类型依赖于具体的平台
一些设备可能当处理器传输数据很快的时候会出现问题,为了避免这个问题,我们需要在IO操作之后插入一个延迟。这些方法以_p结尾,如inb_p,outb_p,etc.
作为参考,下面的例子是写1字节到COM1串口中,并且实现对写入数据的读取
#include <asm/io.h>
#define MY_BASEPORT 0x3F8
unsigned char value = 0xFF;
outb(value, MY_BASEPORT);
value = inb(MY_BASEPORT);
从用户空间访问IO端口
尽管上面描述的都是在设备驱动中使用到的方法。我们仍然可以在用户空间当中通过引入<sys/io.h>头文件的方式来实现。当然,首先需调用ioperm和iopl方法来获取对端口的访问权。ioperm方法来获取单个端口的访问权,iopl来获取整个io地址空间的访问权。为了使用这些特征,用户必定要是root用户。
接下来的例子中我们来获取串口的前3个端口的访问权,然后释放它们。
#include <sys/io.h>
#define MY_BASEPORT 0x3F8
if (ioperm(MY_BASEPORT, 3, 1)) {
/* handle error */
}
if (ioperm(MY_BASEPORT, 3, 0)) {
/* handle error */
}
其中第三个参数代表的是请求或者释放端口的访问权,1表示获取访问权,0表示释放访问权
中断处理
请求一个中断
在linux当中,使用request_irq()和free_irq()方法来获取或者释放中断。
#include <linux/interrupt.h>
typedef irqreturn_t (*irq_handler_t)(int, void *);
int request_irq(unsigned int irq_no, irq_handler_t handler,
unsigned long flags, const char *dev_name, void *dev_id);
void free_irq(unsigned int irq_no, void *dev_id);
handler方法在中断上下文中执行,这意味着我们不能够通过调用阻塞api如mutex_loc或者msleep()。我们同时呀避免在中断上下文中做很多工作,而是根据需要使用延迟的工作。
在中断处理器中所做的工作包括读取设备寄存器获取设备的状态并且确认中断。
有些情况下,尽管设备使用中断,我们无法在非阻塞的模式下读取设备的寄存器。(例如和I2C或者SPI总线连接的传感器,该传感器的驱动并不能保证对总线的读写操作是非阻塞的)。在这种情况下,我们需要制定一个计划来访问设备的寄存器。由于这种场景普遍存在,所以内核提供了方法request_threaded_irq(),中断处理历程运行在两个阶段:进程阶段和中断上下文阶段。
#include <linux/interrupt.h>
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*/
if ((irqflags & IRQF_SHARED) && !dev_id)
return -EINVAL;
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc) ||
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
chip_bus_lock(desc);
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(desc);
if (retval)
kfree(action);
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
if (!retval && (irqflags & IRQF_SHARED)) {
/*
* It's a shared IRQ -- the driver ought to be prepared for it
* to happen immediately, so let's make sure....
* We disable the irq to make sure that a 'real' IRQ doesn't
* run in parallel with our fake.
*/
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval;
}
handler是一种运行在中断上下文中的方法,它会实现至关重要的操作,同时运行在进程上下文当中的thread_fn实现其余剩下来的操作。
当发生中断的时候,下面的flag可能会被用到。
- IRQF_SHARED 它用于告诉内核中断可以在不同的设备之间共享。如果没有设置这个flag,那么如果已经存在了一个和当前请求中断相关联的中断处理器,那么这次请求中断经不会被满足。共享的中断被内核以一种特殊的方式进行处理:所有相关联的中断处理器都将会被执行直到产生这个中断的设备被找到。但是设备驱动程序如何知道中断处理例程是否由其管理的设备生成的中断激活。事实上所有的提供中断支持的设备都有可以在处理例程中用于审讯的状态寄存器,从而我们可以判断该中断是不是这个设备产生的。
- IRQF_ONESHOT 在运行进程上下文例程之后,中断将会被重新激活。如果没有这个标志,在中断上下文中运行处理例程之后中断将会被重新激活。
接下来的例子是执行对串口COM1的中断请求
#include <linux/interrupt.h>
#define MY_BASEPORT 0x3F8
#define MY_IRQ 4
static my_init(void)
{
[...]
struct my_device_data *my_data;
int err;
err = request_irq(MY_IRQ, my_handler, IRQF_SHARED,
"com1", my_data);
if (err < 0) {
/* handle error*/
return err;
}
[...]
}
你看到了么,了对于串口COM1,其IRQ是4,被用于共享模式(IRQF_SHARED)
注意: 当我们请求共享的中断时,dev_id参数不可以为空。
为了释放和串口相关联的中断,接下来的操作会被执行:
free_irq (MY_IRQ, my_data);
在执行初始化方法init_module()或者打开设备的方法的时候,该设备的中断必定要激活。这个操作依赖于具体的设备,但是通常是在控制寄存器上设置一个比特位。
对于8250串口,接下来的操作被执行来允许中断。
#include <asm/io.h>
#define MY_BASEPORT 0x3F8
outb(0x08, MY_BASEPORT+4);
outb(0x01, MY_BASEPORT+1);
在上面的例子中,有两个操作被执行了。
- 通过设置bit为3,在MCR寄存器当中,
- RDAI被激活,通过设置设置IER的合适的bit位。中断允许寄存器。
实现中断处理器
irqreturn_t (*handler)(int irq_no, void *dev_id);
这个方法接收中断号即lirq_no和发送到request_irq()的指针作为参数。中断处理例程返回一个typedef irqreturn_t类型的值。在最近的内核版本中,有三个正当的值:IRQ_NONEA,IRQ_HANDLED,和IRQ_WAKE_THREAD.如果中断还没有发生并且设备正在工作,那么返回一个IRQ_NONE的值。如果中断可以直接从中断上下文中处理,那么返回IRQ_HANDLED。或者返回IRQ_WAKE_THREAD来对进程上下文的运行进行安排。
中断处理器的骨架如下
irqreturn_t my_handler(int irq_no, void *dev_id)
{
struct my_device_data *my_data = (struct my_device_data *) dev_id;
/* if interrupt is not for this device (shared interrupts) */
/* return IRQ_NONE;*/
/* clear interrupt-pending bit */
/* read from device or write to device*/
return IRQ_HANDLED;
}
通常上,在中断处理器中执行的第一件事情就是确定是否中断是由设备驱动所预定的设备产生的。这通常需要从设备寄存器中读取信息来判断是否设备产生了中断。因为大多数设备在复位之前不会再产生中断,所以第二件事情是复位物理设备上的中断挂起位。
锁
因为中断处理程序运行在中断上下文之中,所以中断处理程序的行为将受到限制。不能够访问用户空间的内存,不能够调用可能导致阻塞的函数。同事,使用自旋锁来保证同步也是比较严格的并且中断处理程序请求使用的自旋锁已经被被这个正在运行的中断处理程序中断的进程请求了,那么将会导致死锁。
但是,有很多的案例表明不得不使用中断来保证同步,例如当数据在中断处理程序和进程上下文或者handler的下半段之间共享时。在这些场景中,有必要关中断并且使用自旋锁来进行同步访问。
有两种方法来关中断。从处理器级别disasble所有的中断,或者在设备或者中断控制器级别disabling一个特殊的中断。在处理器级别进行disbale操作快速,因为我们倾向于这样做。
spin_lock_irqsave(),spin_unlock_irqrestore(),spin_lock_irq(),和spin_unlock_irq()
#include <linux/spinlock.h>
void spin_lock_irqsave (spinlock_t * lock, unsigned long flags);
void spin_unlock_irqrestore (spinlock_t * lock, unsigned long flags);
void spin_lock_irq (spinlock_t * lock);
void spin_unlock_irq (spinlock_t * lock);
spin_lock_irqsave()方法在获取到自旋锁之前从处理器级别关闭中断并且讲当前中断的状态保存到flags当中。
spin_lock_irq()方法在获取到自旋锁之前不会关闭中断。
类比,如下
- read_lock_irqsave()
- read_unlock_irqrestore()
- read_lock_irq()
- read_unlock_irq()
- write_lock_irqsave()
- write_unlock_irqrestore()
- write_lock_irq()
- write_unlock_irq()
如果我们想要在中断控制器级别关闭中断(不推荐因为速度将会非常慢,我们不能够关闭共享的中断),那么我们可以使用disable_irq(),disable_irq_nosync(),和enable_irq()。使用这些方法将会在处理器级别关闭中断。如果disable_irq被调用了两次,那么讲调用同样次数的enable_irq来ennable它。disable_irq和disable_irq_nosync之间的区别是disable_irq会等待当前的处理程序执行结束。当不确定的时候使用disable_irq()。
#define MY_IRQ 4
disable_irq (MY_IRQ);
enable_irq (MY_IRQ);
也可以在设备级别禁用中断。 这种方法也比在处理器级别禁用中断要慢,但它可用于共享中断。 完成此操作的方法是特定于设备的,通常意味着我们必须从一个控制寄存器中清除一点。
也可以不依赖于锁定而禁用当前处理器的所有中断。 为同步目的而禁用设备驱动程序的所有中断是不适当的,因为如果在另一个CPU上处理中断,则仍然可能发生竞争。 作为参考,禁用/启用本地处理器上的中断的函数是local_irq_disable()和local_irq_enable()。
为了使用在进程上下文和中断处理例程之间共享的资源,上述功能将按以下方式使用:
/* IRQ handling routine: interrupt context */
irqreturn_t kbd_interrupt_handle(int irq_no, void * dev_id)
{
...
spin_lock(&lock);
/* Critical region - access shared resource */
spin_unlock (&lock);
...
}
/* Process context: Disable interrupts when locking */
static void my_access(void)
{
unsigned long flags;
spin_lock_irqsave(&lock, flags);
/* Critical region - access shared resource */
spin_unlock_irqrestore(&lock, flags);
...
}
void my_init (void)
{
...
spin_lock_init (&lock);
...
}
上面的my_access方法运行在进程上下文当中,为了同步对共享数据的访问,我们禁止中断并且使用自旋锁。
在中断处理例程中,我们使用spin_lock和spin_unlock方法来对共享资源的访问。
注意:spin_lock_irqsave和spin_unlock_irqrestore方法中使用的flags参数是值,不是指针,但是要牢记spin_lock_irqsave()方法改变flag的值,因为这是一个宏。
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
MODULE_DESCRIPTION("KBD");
MODULE_AUTHOR("Kernel Hacker");
MODULE_LICENSE("GPL");
#define MODULE_NAME "kbd"
#define KBD_MAJOR 42
#define KBD_MINOR 0
#define KBD_NR_MINORS 1
#define I8042_KBD_IRQ 1
#define I8042_STATUS_REG 0x64
#define I8042_DATA_REG 0x60
#define BUFFER_SIZE 1024
#define SCANCODE_RELEASED_MASK 0x80
struct kbd {
struct cdev cdev;
/* TODO 4: add spinlock */
spinlock_t lock;
char buf[BUFFER_SIZE];
size_t put_idx, get_idx, count;
} devs[1];
/*
* Checks if scancode corresponds to key press or release.
*/
static int is_key_press(unsigned int scancode)
{
return !(scancode & SCANCODE_RELEASED_MASK);
}
/*
* Return the character of the given scancode.
* Only works for alphanumeric/space/enter; returns '?' for other
* characters.
*/
static int get_ascii(unsigned int scancode)
{
static char *row1 = "1234567890";
static char *row2 = "qwertyuiop";
static char *row3 = "asdfghjkl";
static char *row4 = "zxcvbnm";
scancode &= ~SCANCODE_RELEASED_MASK;
if (scancode >= 0x02 && scancode <= 0x0b)
return *(row1 + scancode - 0x02);
if (scancode >= 0x10 && scancode <= 0x19)
return *(row2 + scancode - 0x10);
if (scancode >= 0x1e && scancode <= 0x26)
return *(row3 + scancode - 0x1e);
if (scancode >= 0x2c && scancode <= 0x32)
return *(row4 + scancode - 0x2c);
if (scancode == 0x39)
return ' ';
if (scancode == 0x1c)
return '\n';
return '?';
}
static void put_char(struct kbd *data, char c)
{
if (data->count >= BUFFER_SIZE)
return;
data->buf[data->put_idx] = c;
data->put_idx = (data->put_idx + 1) % BUFFER_SIZE;
data->count++;
}
static bool get_char(char *c, struct kbd *data)
{
/* TODO 4: get char from buffer; update count and get_idx */
if(data->count>0) {
*c = data->buf[data->get_idx];
data->get_idx = (data->get_idx + 1) % BUFFER_SIZE;
data->count--;
return true;
}
return false;
}
static void reset_buffer(struct kbd *data)
{
/* TODO 5: reset count, put_idx, get_idx */
data->count = 0;
data->put_idx = 0;
data->get_idx = 0;
}
/*
* Return the value of the DATA register.
*/
static inline u8 i8042_read_data(void)
{
u8 val;
/* TODO 3: Read DATA register (8 bits). */
val = inb(I8042_DATA_REG);
return val;
}
/* TODO 2: implement interrupt handler */
irqreturn_t kbd_interrupt_handle(int irq_no,void *dev_id){
unsigned int scancode = 0;
int pressed,ch;
/* TODO 3: read scancode */
scancode = i8042_read_data();
pressed = is_key_press(scancode);
ch = get_ascii(scancode);
pr_info("IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n",irq_no,scancode,scancode,pressed,ch);
/* TODO 4: store ASCII key to buffer */
if(pressed){
struct kbd *data = (struct kbd *)dev_id;
spin_lock(&data->lock);
put_char(data,ch);
spin_unlock(&data->lock);
}
return IRQ_NONE;
}
static int kbd_open(struct inode *inode, struct file *file)
{
struct kbd *data = container_of(inode->i_cdev, struct kbd, cdev);
file->private_data = data;
pr_info("%s opened\n", MODULE_NAME);
return 0;
}
static int kbd_release(struct inode *inode, struct file *file)
{
pr_info("%s closed\n", MODULE_NAME);
return 0;
}
/* TODO 5: add write operation and reset the buffer */
static ssize_t kbd_write(struct file *file,const char __user *user_buffer,size_t size,loff_t *offset){
struct kbd *data = (struct kbd *)file->private_data;
unsigned long flags;
spin_lock_irqsave(&data->lock, flags);
reset_buffer(data);
spin_unlock_irqrestore(&data->lock, flags);
return size;
}
static ssize_t kbd_read(struct file *file, char __user *user_buffer,
size_t size, loff_t *offset)
{
struct kbd *data = (struct kbd *) file->private_data;
size_t read = 0;
/* TODO 4: read data from buffer */
unsigned long flags;
char ch;
bool more = true;
while(size--){
spin_lock_irqsave(&data->lock,flags);
more = get_char(&ch,data);
spin_unlock_irqrestore(&data->lock,flags);
if(!more)
break;
if(put_user(ch,user_buffer++))
return -EFAULT;
read++;
}
return read;
}
static const struct file_operations kbd_fops = {
.owner = THIS_MODULE,
.open = kbd_open,
.release = kbd_release,
.read = kbd_read,
/* TODO 5: add write operation */
.write = kbd_write,
};
static int kbd_init(void)
{
int err;
err = register_chrdev_region(MKDEV(KBD_MAJOR, KBD_MINOR),
KBD_NR_MINORS, MODULE_NAME);
if (err != 0) {
pr_err("register_region failed: %d\n", err);
goto out;
}
/* TODO 1: request the keyboard I/O ports */
if(request_region(I8042_DATA_REG+1,1,MODULE_NAME) == NULL){
err = -EBUSY;
goto out_unregister;
}
if(request_region(I8042_STATUS_REG+1,1,MODULE_NAME) == NULL){
err = -EBUSY;
goto out_unregister;
}
/* TODO 4: initialize spinlock */
spin_lock_init(&devs[0].lock);
/* TODO 2: Register IRQ handler for keyboard IRQ (IRQ 1). */
err = request_irq(I8042_KBD_IRQ,kbd_interrupt_handle,IRQF_SHARED,MODULE_NAME,&devs[0]);
if(err != 0){
pr_err("request_irq failed: %d\n",err);
goto out_release_regions;
}
cdev_init(&devs[0].cdev, &kbd_fops);
cdev_add(&devs[0].cdev, MKDEV(KBD_MAJOR, KBD_MINOR), 1);
pr_notice("Driver %s loaded\n", MODULE_NAME);
return 0;
/*TODO 2: release regions in case of error */
out_release_regions:
release_region(I8042_STATUS_REG+1,1);
release_region(I8042_DATA_REG+1,1);
out_unregister:
unregister_chrdev_region(MKDEV(KBD_MAJOR, KBD_MINOR),
KBD_NR_MINORS);
out:
return err;
}
static void kbd_exit(void)
{
cdev_del(&devs[0].cdev);
/* TODO 2: Free IRQ. */
free_irq(I8042_KBD_IRQ,&devs[0]);
/* TODO 1: release keyboard I/O ports */
release_region(I8042_STATUS_REG+1,1);
release_region(I8042_DATA_REG+1,1);
unregister_chrdev_region(MKDEV(KBD_MAJOR, KBD_MINOR),
KBD_NR_MINORS);
pr_notice("Driver %s unloaded\n", MODULE_NAME);
}
module_init(kbd_init);
module_exit(kbd_exit);
root@qemux86:~/skels/interrupts# insmod kbd.ko
Driver kbd loaded
root@qemux86:~/skels/interrupts# cat /proc/ioports | egrep “(0060|0064)”
0060-0060 : keyboard
0064-0064 : keyboard
root@qemux86:~/skels/interrupts# cat /proc/ioports | grep kbd
0061-0061 : kbd
0065-0065 : kbd
root@qemux86:~/skels/interrupts# rmmod kbd
Driver kbd unloaded
root@qemux86:~/skels/interrupts# cat /proc/ioports | grep kbd
root@qemux86:~/skels/interrupts#