接續前文的實作,繼續完成 physical device driver 部份;由於 physical device driver 與 I/O 存取密切相關,因此我們會先說明 Linux 的 I/O 存取函數。
作者/陳俊宏
www.jollen.org
I/O 存取的觀念
I/O device必須透過I/O port來存取與控制,每個I/O port都會被指定一個memory address,稱為I/O port address(或port address),此即所謂的memory mapped I/O。
memory mapped I/O的意義為,我們可以透過I/O port被指定的memory address來存取I/O device,如此可將複雜的I/O device存取變成簡單的memory存取,也不需要使用 assembly 來存取 I/O device。
Memory-mapped I/O的觀念是將I/O port或I/O memory “mapping” 到 memory address上,此位址稱為I/O port address。採用memory-mapped I/O觀念的主要好處是可以將I/O device的存取變成記憶體存取。因此,對使用者而言,存取I/O裝置就會變成跟CPU的記憶體存取一樣。
RISC 架構的處理器,在 system design 方面,也都採取 memory-mapped I/O (I/O memory) 的觀念。
Linux I/O Port 存取介面
在 x86 平臺上,I/O port與I/O memory可以看成是一樣的東西。但在學習Linux驅動程式實作時,則是要把二者清楚的分開來。若是要存取I/O port,Linux提供以下的I/O port存取介面:
˙ unsigned inb(unsigned port);
˙ unsigned inw(unsigned port);
˙ unsigned inl(unsigned port);
˙ void outb(unsigned char byte, unsigned port);
˙ void outw(unsigned short word, unsigned port);
˙ void outl(unsigned long word, unsigned port);
若是要存取I/O “memory”,則改用以下函數:
˙ unsigned readb(unsigned port);
˙ unsigned readw(unsigned port);
˙ unsigned readl(unsigned port);
˙ void writeb(unsigned char byte, unsigned port);
˙ void writew(unsigned short word, unsigned port);
˙ void writel(unsigned long word, unsigned port);
inb()表示要由I/O port address讀取1 byte的資料,outw()表示要輸出1 short word(2 bytes)的資料到指定的I/O port address;同理,readl()表示要由I/O memory address讀取1 long word(4 bytes)的資料,其它函數則依此類推。
範例透過I/O port 80H與debug card溝通,因此只要執行:
outb(num, 0x80);
即可將數字”num”顯示在debug card上。有些debug card的規格也支援其它的I/O port位址,若要輸出到其它I/O port位址做測試,請自行修改範例。
在未學習ioremap()函數前,我們的範例都會以直接存取I/O port的方式來設計。但Linux device driver是「不能直接」存取I/O port或I/O memory的,必須將I/O port或I/O memory “remapping” 到kernel virtual address後才能存取裝置。 此觀念在學習 PCI 驅動程式設計時便能看到。 |
完成我們的範例
了解 Linux 驅動程式如存取 I/O device 後,我們就可以完成 ops->write 實作了!以下是我們的實作程式碼:
unsigned long IOPort = 0x80; void write_card(unsigned int num) { MSG("write 0x%02X (%d) to debug card", (unsigned char)num, num); outb((unsigned char)num, IOPort); } ssize_t card_write(struct file *filp, const char *buff, size_t count, loff_t *offp) { char *str; unsigned int num; int i;if (count == 0) return 0;
filp->private_data = (char *)kmalloc(64, GFP_KERNEL);
str = filp->private_data;if (copy_from_user(str, buff, count))
return -EFAULT;/* atoi() */
num = str[0]-'0'; for (i = 1; i < count; i++) {
num = num*10 + (str[i]-'0');
}write_card(num);
return 1;
};
完整範例列表
/*
* Debug Card 0.1.1 - Port 80 Debug Card Driver
*
* Copyright (C) 2004 www.jollen.org
*
* This file may be redistributed under the terms of the GNU Public
* License.
*/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>#include <linux/config.h>
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "card.h"unsigned long IOPort = 0x80;
int card_release(struct inode *, struct file *);
int card_open(struct inode *, struct file *);
int card_ioctl(struct inode *, struct file *,
unsigned int, unsigned long);
ssize_t card_write(struct file *, const char *,
size_t, loff_t *);void write_card(unsigned int);
void write_card(unsigned int num)
{
MSG("write 0x%02X (%d) to debug card", (unsigned char)num, num);
outb((unsigned char)num, IOPort);
}int card_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case IOCTL_RESET:
write_card(0x00);
break;
default:
return -1;
}
return 0;
}ssize_t card_write(struct file *filp, const char *buff,
size_t count, loff_t *offp)
{
char *str;
unsigned int num;
int i;if (count == 0) return 0;
filp->private_data = (char *)kmalloc(64, GFP_KERNEL);
str = filp->private_data;if (copy_from_user(str, buff, count))
return -EFAULT;/* atoi() */
num = str[0]-'0'; for (i = 1; i < count; i++) {
num = num*10 + (str[i]-'0');
}write_card(num);
return 1;
};/**************************************************/
struct file_operations card_fops = {
open: card_open,
write: card_write,
release: card_release,
ioctl: card_ioctl,
};int card_release(struct inode *inode, struct file *filp)
{
MOD_DEC_USE_COUNT;
kfree(filp->private_data);return 0;
};int card_open(struct inode *inode, struct file *filp)
{
MOD_INC_USE_COUNT;
return 0;
};int init_module(void)
{
MSG("DEBUG CARD v0.1.1");
MSG(" Copyright (C) 2004 www.jollen.org");if (register_chrdev(DEV_MAJOR, DEV_NAME, &card_fops) < 0) {
MSG("Couldn't register a device.");
return -1;
}
return 0;
}void cleanup_module(void)
{
if (unregister_chrdev(DEV_MAJOR, DEV_NAME))
MSG("failed to unregister driver");
else
MSG("driver un-installed\n");
}MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.jollen.org");
// card.h
#ifndef _CARD_H_#define MSG(format, arg...) printk(KERN_INFO "DEBUG CARD: " format "\n", ## arg)
#include <linux/ioctl.h>
#define DEV_MAJOR 121
#define DEV_NAME "debug"
#define DEV_IOCTLID 0xD0#define IOCTL_WRITE _IOW(DEV_IOCTLID, 10, int)
#define IOCTL_RESET _IOW(DEV_IOCTLID, 0, int)#endif
寫 User Program 來測試
/*
* Debug Card 0.1.1 - Port 80 Debug Card 'User-Space' Driver
*
* Copyright (C) 2004 www.jollen.org
*
* This file may be redistributed under the terms of the GNU Public
* License.
*/#include <stdio.h>
#include <unistd.h>#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "card.h"int main(int argc, char *argv[])
{
int devfd;
unsigned int num = 0;if (argc == 1) argv[1] = "0";
devfd = open("/dev/debug", O_RDWR);
if (devfd == -1) {
printf("Can't open /dev/debug\n");
return -1;
}printf("Resetting debug card...\n");
ioctl(devfd, IOCTL_RESET, NULL);
printf("Done. Wait 1 second...\n");
sleep(1);printf("Writing %s...\n", argv[1]);
write(devfd, argv[1], strlen(argv[1]));
printf("Done.\n");close(devfd);
return 0;
}
觀念大考驗
到這裡為止,我們已經完成階段性任務了--了解 Linux 驅動程式的架構觀念。
下一篇文章,我們會具體描繪出此範例的執行流程路徑;透過這張圖,大家便能考驗自己是否已經了解主要的驅動程式架構觀念了!
Jollen's Blog 使用 Github issues 與讀者交流討論。請點擊上方的文章專屬 issue,或 open a new issue
您可透過電子郵件 jollen@jollen.org,或是 Linkedin 與我連絡。更歡迎使用微信,請搜尋 WeChat ID:jollentw