作者/陳俊宏
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

 

arrow
arrow
    全站熱搜

    pcman 發表在 痞客邦 留言(0) 人氣()