Linux 驅動程式觀念解析, #6: 依流程來實作 -- Physical Device Driver

jollen 發表於 May 4, 2006 10:18 AM

接續前文的實作,繼續完成 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