2012年1月6日 星期五

基礎 Linux Device Driver 驅動程式#11 (procfs-read only)

procfs 指的是位於 /proc 的虛擬檔案系統(實體在 RAM內), 內含 kernel 與各驅動程式的設定選項。
/proc 內的大多數檔案都是唯讀的,像 /proc/cpuinfo , /proc/meminfo 等等...
但也有些是可讓使用者設定的。 像 /proc/sys/kernel/printk 就是了。

如果是要建立唯讀的 procfs,是呼叫 create_proc_read_entry。
定義在 include/linux/proc_fs.h

struct proc_dir_entry *create_proc_read_entry(const char *name,
        mode_t mode, struct proc_dir_entry *base,
        read_proc_t *read_proc, void * data)
       
name:      在 /proc 之內的檔案名稱
mode:      權限(可 NULL)
base:      上層目錄(可 NULL)
read_proc: 讀取處理函式
data:      私有資料(可 NULL)

read_proc 引數要傳入讀取時呼叫的處理函式指標。
這個函式的 prototype 已 typedef 為 read_proc_t, 如下所示:
typedef int (read_proc_t)(char *page, char **start, off_t off,
                          int count, int *eof, void *data);
                         
page:    Kernel 配置的記憶體空間
start:   驅動程式寫入資料的開始位址指標(由驅動程式回傳)
off:     驅動程式傳回資料的偏移量
count:   User space 一次讀回的量
eof:     通知已達資料終點(由驅動程式回傳)
data:    create_proc_read_entry() 設定的私有資料

page 引數是 kernel 分配的記憶體,所以驅動程式可以直接寫入,
但只分配了一個 page 的大小(PAGE_SIZE),所以寫入資料起過 PAGE_SIZE 就會破壞記憶體內容。

驅動程式在卸載的時候,要呼叫 remove_proc_entry() 清除 procfs 的內容。
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);

好吧, 就來試一下吧。

test_proc.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>

#define PROC_NAME "test_proc_info"
#define BUF_SIZE 10
static char buf[BUF_SIZE] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'};

static int test_proc_read(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int len = BUF_SIZE;

if (len > PAGE_SIZE)
return -ENOBUFS;
len = sprintf(page, "%s\n", buf);

return len;
}

static int test_proc_init(void)
{
struct proc_dir_entry *entry;
int ret = 0;
printk(KERN_ALERT "Procfs init...\n");

entry = create_proc_read_entry(PROC_NAME,
      S_IRUGO | S_IWUGO,
      NULL,
      test_proc_read,
      NULL);
if (entry == NULL) {
ret = -ENOMEM;
goto out;
}

out:
return ret;
}

static void test_proc_exit(void)
{
remove_proc_entry(PROC_NAME, NULL);
printk(KERN_ALERT "Procfs exit.\n");
}

module_init(test_proc_init);
module_exit(test_proc_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_proc_read module.");

/*****************************************************************************/

Makefile 如下:
/*****************************************************************************/

KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := test_proc.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

開始測試, go........

# ls
Makefile  test_proc.c
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_proc_read modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/test_proc_read/test_proc.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/test_proc_read/test_proc.mod.o
  LD [M]  /opt/test_driver/my_driver/test_proc_read/test_proc.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./test_proc.ko
Procfs init...
# ls -l /proc/test_proc_info
-rw-rw-rw- 1 root root 0 2012-01-06 16:35 /proc/test_proc_info
# cat /proc/test_proc_info
abcdefghij
# rmmod test_proc
Procfs exit.
# ls -l /proc/test_proc_info
ls: 無法存取 /proc/test_proc_info: 沒有此一檔案或目錄

你看看,你看看,是不是這麼一回事呢?
哈^^

procfs - read write 待續......

註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

2012年1月5日 星期四

基礎 Linux Device Driver 驅動程式#10 (select/poll)

相信各位都有在Linux上寫程式的經驗,
當您程式裡呼叫 open 時,Linux 預設會以 blocking mode的方式開啟,
block 是指當 process 為了等待某件事的發生,而進入 sleep 狀態的情形。

像 read 就是其中一種,當沒資料讀取時,process 就會被 block。
write 也是一樣,在寫入資料時,寫入對象還無法處理資料時,一樣會 block。

對某些程式來說,如果 read 系統呼叫被 block 的話,有時就會有設計上的問題,
所以為了避免這種問題發生,Linux 就準備了以下方式。

1. Non-blocking 模式

啟用 non-blocking 模式後,不管是 read 還是 write 就不會被 block住。但會傳回 errno 錯誤碼,
這時就必須自已再做讀寫的動作。
想使用 non-blocking 模式的話,可在 open() 開檔時指定 O_NONBLOCK。

2. 同時執行多個同步 I/O 工作
同時執行多個同步 I/O 工作 指的是使用 select 系統呼叫的做法。
select 系統呼叫本身會被 block, 但可指定 timeout。

使用 select() 的時候,有幾個常用的函式巨集,可參考 man 2 select。

int select(int nfds, fd_set *readfds, fd_set *writefds,
  fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

還有一個和 select()很類似的 poll()系統呼叫,以基本功能來說,select()與poll()都一樣,
但指定的 file handler的方式不一樣,且指定多個 file handler 的時候,poll()走訪所有
file handler的速度比較快。 可參考 man 2 poll。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

應用程式想同時執行多個同步 I/O 工作時,可使用的系統呼叫有好幾個,
但在驅動程式,只需要準備一個函式即可。
不管 user process 用了哪個系統呼叫,kernel 都只會呼叫驅動程式提供的這個函式。

Character 類型的裝置想支援同時執行多個同步 I/O 工作的話,只要在驅動程式準備 poll方法即可。
poll方法會收到 file_operations 結構。
unsigned int (*poll) (struct file *, struct poll_table_struct *);

poll方法會在kernel 處理 select 與 poll 之類的系統呼叫時用到。它必須的執行工作如下:
1. 在 wait queue 豋記。
2. 傳回目前可以操作的狀態。

在呼叫同時執行多個同步 I/O 工作的系統呼叫時,block 直到狀態變化的動作,指的是在 kernel裡面 sleep。
如果要 sleep的話,要先準備 wait queue(wait_queue_head_t),這個由驅動程式負責提供。
豋記 wait queue 的工作可透過 poll_wait()完成,它定義在 include/linux/poll.h

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);

在呼叫同時執行多個同步 I/O 工作的系統呼叫時,解除 block 的時機,是驅動程式透過傳給 poll_wait()
的 wait queue 被喚醒的時候。Kernel 在被 wait queue 喚醒之後,會再次呼叫驅動程式的 poll()
確認是否成為等待中的狀態(可寫入或可讀出),如果是的話,FD_ISSET 巨集就會成立。
想判斷是不是這種狀態的話,還是需要驅動程式提供資訊才行,這個資訊就是透過poll()方法的傳回值來表示。
傳回值要透過 include/linux/poll.h 定義的巨集 OR 起來表示,下面是常用到的組合。

POLLIN|POLLRDNORM                     可讀取
POLLOUT|POLLWRNORM                    可寫入
POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM  可讀寫
POLLERR                               發生錯誤
POLLHUP                               裝置離線(EOF)

說了那麼多,還倒不如寫個程式比較好了解。

剛試了一下,果然新舊版核心還是有差,幾個問題,稍為提出來討論一下:
1. 要用 kmalloc 及 kfree 的話,要 include <linux/slab.h>
   註:其實在上一個示範程式就已有這問題了。
   請參考: 基礎 Linux Device Driver 驅動程式#9 (IOCTL)
2. void init_MUTEX (struct semaphore *sem); /* 新版 kernel 已不適用 */
   改用
   void sema_init (struct semaphore *sem, int val);

test_select.c 原始碼如下:
/*****************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <asm/uaccess.h>

#define DRIVER_NAME "test_select"

static unsigned int test_select_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev test_select_cdev;
static unsigned int timeout_value = 10;

struct test_select_data {
struct timer_list timeout;
spinlock_t lock;
wait_queue_head_t read_wait;
int timeout_done;
struct semaphore sem;
};

unsigned int test_select_poll(struct file *filp, poll_table *wait)
{
struct test_select_data *data = filp->private_data;
unsigned int mask = POLLOUT|POLLWRNORM;
printk(KERN_ALERT "Call test_select_poll.\n");

if (data == NULL)
return -EBADFD;
down(&data->sem);
poll_wait(filp, &data->read_wait, wait);

if (data->timeout_done == 1) {    /* readable */
mask |= POLLIN|POLLRDNORM;
}
up(&data->sem);
printk(KERN_ALERT "%s returned (mask 0x%x)\n", __func__,  mask);
}

static void test_select_timeout(unsigned long arg)
{
struct test_select_data *data = (struct test_select_data*)arg;
unsigned long flags;
printk(KERN_ALERT "Call test_select_timeout.\n");

spin_lock_irqsave(&data->lock, flags);

data->timeout_done = 1;
wake_up_interruptible(&data->read_wait);

spin_unlock_irqrestore(&data->lock, flags);
}

ssize_t test_select_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
return -EFAULT;
}

ssize_t test_select_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct test_select_data *data = filp->private_data;
int i;
unsigned char val;
int retval;

if (down_interruptible(&data->sem))
return -ERESTARTSYS;
if (data->timeout_done == 0) {    /* no read */
up(&data->sem);
if (filp->f_flags & O_NONBLOCK)    /* non-blocking mode */
return -EAGAIN;
do {
retval = wait_event_interruptible_timeout(
data->read_wait,
data->timeout_done == 1,
1*HZ);
if (retval == -ERESTARTSYS)
return -ERESTARTSYS;
} while (retval == 0);    /* timeout elapsed */

if (down_interruptible(&data->sem))
return -ERESTARTSYS;
}
val = 0xff;
for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}
retval = count;

out:
data->timeout_done = 0;

/* restart timer */
mod_timer(&data->timeout, jiffies + timeout_value*HZ);
up(&data->sem);

return retval;
}

static int test_select_close(struct inode *inode, struct file *filp)
{
struct test_select_data *data = filp->private_data;
printk(KERN_ALERT "Call test_select_close.\n");

if (data) {
del_timer_sync(&data->timeout);
kfree(data);
}

return 0;
}

static int test_select_open(struct inode *inode, struct file *filp)
{
struct test_select_data *data;
printk(KERN_ALERT "Call test_select_open.\n");

data = kmalloc(sizeof(struct test_select_data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;

/* initialize members */
spin_lock_init(&data->lock);
init_waitqueue_head(&data->read_wait);
// init_MUTEX(&data->sem); /* 新版 kernel 已不適用 */
sema_init(&data->sem, 1);  /* 改用 sema_init */
init_timer(&data->timeout);

data->timeout.function = test_select_timeout;
data->timeout.data = (unsigned long)data;

filp->private_data = data;

/* start timer */
data->timeout_done = 0;
mod_timer(&data->timeout, jiffies + timeout_value*HZ);

return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_select_open,
.release = test_select_close,
.read = test_select_read,
.write = test_select_write,
.poll = test_select_poll,
};

static int test_select_init(void)
{
dev_t dev = MKDEV(test_select_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;

alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;

test_select_major = MAJOR(dev);
cdev_init(&test_select_cdev, &fops);
cdev_ret = cdev_add(&test_select_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;
printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_select_major);

return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_select_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);

return -1;
}

static void test_select_exit(void)
{
dev_t dev = MKDEV(test_select_major, 0);

cdev_del(&test_select_cdev);
unregister_chrdev_region(dev, num_of_dev);

printk(KERN_ALERT "%s driver removed\n", DRIVER_NAME);
}

module_init(test_select_init);
module_exit(test_select_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_select module.");

/*****************************************************************************/

Makefile 如下:
/*****************************************************************************/
KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := test_select.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

test.c 如下:
/*****************************************************************************/
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define DEVFILE "/dev/test_select0"

int main()
{
int fd;
        fd_set rfds;
        struct timeval tv;
        int retval;
        unsigned char buf;
        ssize_t sz;
        int i;

        fd = open(DEVFILE, O_RDWR);
        if (fd == -1) {
                perror("open");
                return -1;
        }

        do {
                FD_ZERO(&rfds);
                FD_SET(fd, &rfds);
                tv.tv_sec = 5;
                tv.tv_usec = 0;

                printf("select() ...\n");
                retval = select(fd + 1, &rfds, NULL, NULL, &tv);
                if (retval == -1) {
                        perror("select");
                        break;
                }

                if (retval) {
                        break;
                }
        } while (retval == 0);   /* timeout elapsed */

        if (FD_ISSET(fd, &rfds)) {
                printf("read() ...\n");
                sz = read(fd, &buf, 1);
                printf("read() %d\n", sz);
                printf("%02x ", buf);
                printf("\n");
        }

        close(fd);

        return 0;

}

/*****************************************************************************/

執行結果如下:

# ls
Makefile  test_code  test_select.c
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_select modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/test_select/test_select.o
/opt/test_driver/my_driver/test_select/test_select.c: In function ‘test_select_poll’:
/opt/test_driver/my_driver/test_select/test_select.c:44:1: warning: control reaches end of non-void function
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/test_select/test_select.mod.o
  LD [M]  /opt/test_driver/my_driver/test_select/test_select.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
Makefile       Module.symvers  test_select.c   test_select.mod.c  test_select.o
modules.order  test_code       test_select.ko  test_select.mod.o
# cd test_code/
# ls
test.c
# gcc test.c -o test
# ls
test  test.c
# cd ..
# insmod ./test_select.ko
test_select driver(major: 250) installed.
# mknod /dev/test_select0 c 250 0
# ./test_code/test
select() ...
read() ...
... 經過 10秒 ...
read() 1
ff
# dmesg | tail
... 以上略過 ...
Call test_select_open.
Call test_select_poll.
test_select_poll returned (mask 0x104)
Call test_select_timeout.
Call test_select_close.

# rm -rf test_code/test
# rm /dev/test_select0
# rmmod test_select
# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_select clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CLEAN   /opt/test_driver/my_driver/test_select/.tmp_versions
  CLEAN   /opt/test_driver/my_driver/test_select/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'

OK, 一切就是如此的順利,世界就是如此的美好 ^^


註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

2012年1月3日 星期二

基礎 Linux Device Driver 驅動程式#9 (IOCTL)

IOCTL 是一種系統呼叫介面,user process 呼叫 ioctl() 即可對驅動程式送出系統呼叫,
如此會呼叫驅動程式的 IOCTL 處理函式,也可跟驅動程式交換資料。
交換資料的格式,可由驅動程式開發者自由決定。

IOCTL 方法的 prototype如下:
int (*ioctl)(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);

inode:  是開啟裝置檔的相關資訊。
filp:   可取出驅動程式的私有資料。
cmd:    是 IOCTL 的指令,且不可省略,驅動程式可由它得知 user process 想做什麼。
arg:    是 ioctl()可變引數(...)的參數,內含 user process的指標,但驅動程式不得
        直接讀寫這個指標,必須透過copy_from_user() 及 copy_to_user() 讀寫資料。
     
但,各位可能要注意一個小地方囉,我目前用的 linux kernel 是 2.6.38,
ioctl 這個 handler 在 2.3.36的核心版本已不用這個名稱了,
改用以下兩個:
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl)   (struct file *, unsigned int, unsigned long);

所以要注意喔,如果各位也是和我用一樣新核心的話,就....你知道的。
我示範的 code 也會用 unlocked_ioctl 這個 handler。

IOCTL 指令的格式是以巨集定義的。驅動程式與user process 的程式共用一個標頭檔。
四個巨集分別如下:
_IO():    無引數的 IOCTL。
_IOR():   從驅動程式讀取資料。
_IOW():   把資料寫給驅動程式。
_IOWR():  與驅動程式讀寫資料。

定義在 include/asm-generic/ioctl.h

/* used to create numbers */
#define _IO(type,nr)            _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)      _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)      _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

驅動程式可使用 _IO_SIZE 巨集得知傳給 ioctl() 可變引數的結構大小。
也是定義在 include/asm-generic/ioctl.h
#define _IOC_SIZE(nr)           (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

在讀寫 user space 的指標時,可以呼叫 access_ok() 判斷指標可否讀讀寫。
定義在 include/asm-generic/uaccess.h
#define access_ok(type, addr, size) __access_ok((unsigned long)(addr),(size))

當然有些時候,會只限定root才能使用 ioctl,一般使用者不能使用,可用 int capable(int cap);

再來寫個 code 跑一下吧^^
但在這之前,我們先看一下我們要 include 的標頭檔,如下:

test_ioctl.h
/*****************************************************************************/

#ifndef _IOCTL_TEST_H
#define _IOCTL_TEST_H

#include <linux/ioctl.h>

struct ioctl_arg {
        unsigned int reg;
        unsigned int val;
};

/* 這裡要找一個沒用到的號碼,請參考 Documentation/ioctl/ioctl-number.txt */
#define IOC_MAGIC '\x66'

/* 您要的動作 */
#define IOCTL_VALSET      _IOW(IOC_MAGIC, 0, struct ioctl_arg)
#define IOCTL_VALGET      _IOR(IOC_MAGIC, 1, struct ioctl_arg)
#define IOCTL_VALGET_NUM  _IOR(IOC_MAGIC, 2, int)
#define IOCTL_VALSET_NUM  _IOW(IOC_MAGIC, 3, int)

#define IOCTL_VAL_MAXNR 3

#endif

/*****************************************************************************/

test_ioctl.c 原始碼如下:
/*****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "test_ioctl.h"

#define DRIVER_NAME "test_ioctl"
static unsigned int test_ioctl_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev test_ioctl_cdev;
static int ioctl_num = 0;

struct test_ioctl_data {
unsigned char val;
rwlock_t lock;
};

static long test_ioctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
int retval;
unsigned char val;
struct ioctl_arg data;

memset(&data, 0, sizeof(data));
switch (cmd) {
case IOCTL_VALSET:
/*
if (!capable(CAP_SYS_ADMIN)) {
retval = -EPERM;
goto done;
}
if (!access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd))) {
retval = -EFAULT;
goto done;
}
*/
if (copy_from_user(&data, (int __user *)arg, sizeof(data))) {
retval = -EFAULT;
goto done;
}
printk(KERN_ALERT "IOCTL set val:%x .\n", data.val);

write_lock(&ioctl_data->lock);
                        ioctl_data->val = data.val;
                        write_unlock(&ioctl_data->lock);

break;
case IOCTL_VALGET:
/*
if (!access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd))) {
                                retval = -EFAULT;
                                goto done;
                        }
*/
read_lock(&ioctl_data->lock);
                        val = ioctl_data->val;
                        read_unlock(&ioctl_data->lock);
                        data.val = val;

if (copy_to_user((int __user *)arg, &data, sizeof(data)) ) {
                                retval = -EFAULT;
                                goto done;
                        }

break;

case IOCTL_VALGET_NUM:
retval = __put_user(ioctl_num, (int __user *)arg);
break;
case IOCTL_VALSET_NUM:
/*
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
*/
ioctl_num = arg;

break;
default:
retval = -ENOTTY;
}

done:
return retval;
}

ssize_t test_ioctl_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct test_ioctl_data *ioctl_data = filp->private_data;
unsigned char val;
int retval;
int i = 0;

read_lock(&ioctl_data->lock);
val = ioctl_data->val;
read_unlock(&ioctl_data->lock);

for (;i < count ;i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}

retval = count;

out:
return retval;
}

static int test_ioctl_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "%s call.\n", __func__);
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}

return 0;
}

static int test_ioctl_open(struct inode *inode, struct file *filp)
{
struct test_ioctl_data *ioctl_data;
printk(KERN_ALERT "%s call.\n", __func__);

ioctl_data = kmalloc(sizeof(struct test_ioctl_data), GFP_KERNEL);
if (ioctl_data == NULL)
return -ENOMEM;

rwlock_init(&ioctl_data->lock);
ioctl_data->val = 0xFF;

filp->private_data = ioctl_data;

return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_ioctl_open,
.release = test_ioctl_close,
.read = test_ioctl_read,
.unlocked_ioctl = test_ioctl_ioctl,
};

static int test_ioctl_init(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;

alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;

test_ioctl_major = MAJOR(dev);

cdev_init(&test_ioctl_cdev, &fops);
cdev_ret = cdev_add(&test_ioctl_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;

printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, test_ioctl_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&test_ioctl_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);

return -1;
}

static void test_ioctl_exit(void)
{
dev_t dev = MKDEV(test_ioctl_major, 0);

cdev_del(&test_ioctl_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}

module_init(test_ioctl_init);
module_exit(test_ioctl_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is test_ioctl module.");

/*****************************************************************************/

那當然還少不了測試程式囉。

test.c 如下:
/*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "../test_ioctl.h"

#define DEVFILE "/dev/t1"

int main(void)
{
struct ioctl_arg cmd;
        int fd;
long ret;
int num = 0;

        fd = open(DEVFILE, O_RDWR);
        if (fd == -1)
                perror("open");

memset(&cmd, 0, sizeof(cmd));
        ret = ioctl(fd, IOCTL_VALGET, &cmd);

if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }
        printf("val %x\n", cmd.val);

        cmd.val = 0xCC;
        ret = ioctl(fd, IOCTL_VALSET, &cmd);
        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }

ret = ioctl(fd, IOCTL_VALGET, &cmd);

        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }
        printf("val %x\n", cmd.val);


num = 100;
ret = ioctl(fd, IOCTL_VALSET_NUM, num);
        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }

ret = ioctl(fd, IOCTL_VALGET_NUM, &num);

        if (ret == -1) {
                printf("errno %d\n", errno);
                perror("ioctl");
        }
        printf("num %d\n", num);

        if (close(fd) != 0)
                perror("close");

        return 0;
}

/*****************************************************************************/

Makefile 如下:
/*****************************************************************************/

KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := test_ioctl.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

開始測試囉^^
# ls
Makefile  test_code  test_ioctl.c  test_ioctl.h
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/test_ioctl/test_ioctl.o
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c: In function ‘test_ioctl_ioctl’:
/opt/test_driver/my_driver/test_ioctl/test_ioctl.c:23:6: warning: ‘retval’ may be used uninitialized in this function
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/test_ioctl/test_ioctl.mod.o
  LD [M]  /opt/test_driver/my_driver/test_ioctl/test_ioctl.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./test_ioctl.ko
test_ioctl driver(major: 248) installed.
# cd test_code/
# gcc test.c -o test
# cd ..
# mknod /dev/t1 c 248 0
# ./test_code/test
val ff
val cc
num 100
# dmesg | tail
... /* 這裡略過 */
test_ioctl_open call.
IOCTL set val:cc .
test_ioctl_close call.
# rm /dev/t1
# rmmod test_ioctl

# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_ioctl clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CLEAN   /opt/test_driver/my_driver/test_ioctl/.tmp_versions
  CLEAN   /opt/test_driver/my_driver/test_ioctl/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'


完成 ^^


註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

2012年1月2日 星期一

基礎 Linux Device Driver 驅動程式#8 (character device driver基礎_chrdev_sys)

前面介紹了那麼多的範例,但您有發現嗎?
所有範例程式的裝置檔,都必須要手動建立,難道不能自動建立嗎?
有,就是 udev,那您會說, udev 是什麼?
簡單,上wiki去查一下就有啦。
它是 Linux kernel 2.6系列的裝置管理器。
更詳細的說明,去wiki看囉,這裡就不多說了。

您也可以去看您的udevd是否有在運行。
# ps aux | grep udevd
root       315  0.0  0.0   2880  1132 ?        S<s  09:35   0:00 udevd --daemon
這就是啦。

當驅動程式載入時,udevd daemon會偵測到這個事件,而後去檢查/sys目錄,
如果驅動程式建立了dev檔案的話,檔案裡會含有major及minor number,如此 udevd就能以它
建立裝置檔囉。

想讓驅動程式支援 udev 的話,必須豋錄驅動程式的 class 並在 /sys/class目錄下建立驅動程式資訊。

豋記class時是用 class_creat()這個kernel 函式。
它的prototype如下:
struct class *class_create(struct module *owner, const char *name);
owner: 還記得嗎? 就是 THIS_MODULE 啊
name:   就放 DRIVER_NAME 即可。

刪除豋記的 class是用 class_destroy()
void class_destroy (struct class *cls);

接著要建立 /sys/class/class 名稱/裝置名稱 這個檔案,用的是 device_create()
struct device *device_create(struct class *cls, struct device *parent,
                                    dev_t devt, void *drvdata,
                                    const char *fmt, ...)

cls:     傳入由class_creat() 傳回的 class。
parent:  是指定上層 class的時候使用的,傳入 NULL也行。
devt:    是dev檔顯示的major/minor number,也可用 MKDEV巨集指定。
drvdate: 是添加到裝置的資料,傳入 NULL也行。
fmt:     裝置檔的名稱。
2.6 要舊的核心,是用class_device_create()

如要刪除豋記的裝置,可用 device_destroy()
void device_destroy (struct class *class, dev_t devt);
2.6 要舊的核心,是用class_device_destroy()

大致上了解了之後,再來就是快樂的coding 時間囉^^
不好意思啊,又要等我一下啦。

時間一分一秒的過去............
好了,

chrdev_sys.c 程式碼如下:

/*****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>

#define DRIVER_NAME "chrdev_sys"
static unsigned int chrdev_sys_major = 0;
static unsigned int num_of_dev = 1;
static struct cdev chrdev_sys_cdev;
static struct class *chrdev_sys_class = NULL;

static int chrdev_sys_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "Call chrdev_sys_close.\n");
return 0;
}

static int chrdev_sys_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "Call chrdev_sys_open.\n");
return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_sys_open,
.release = chrdev_sys_close,
};

static int chrdev_sys_init(void)
{
dev_t dev = MKDEV(chrdev_sys_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;

alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;

chrdev_sys_major = MAJOR(dev);
cdev_init(&chrdev_sys_cdev, &fops);
cdev_ret = cdev_add(&chrdev_sys_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;

chrdev_sys_class = class_create(THIS_MODULE, DRIVER_NAME);
if (IS_ERR(chrdev_sys_class))
goto error;

device_create(chrdev_sys_class,
                      NULL,
                      MKDEV(chrdev_sys_major, 0),
                      NULL,
                      "ch_sys0");

printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_sys_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_sys_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);

return -1;
}


static void chrdev_sys_exit(void)
{
dev_t dev = MKDEV(chrdev_sys_major, 0);

device_destroy(chrdev_sys_class, dev);
        class_destroy(chrdev_sys_class);

cdev_del(&chrdev_sys_cdev);
unregister_chrdev_region(dev, num_of_dev);
printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}

module_init(chrdev_sys_init);
module_exit(chrdev_sys_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_sys module.");

/*****************************************************************************/

Makefile 如下:
/*****************************************************************************/

KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := chrdev_sys.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

run 一下囉^^

# ls
chrdev_sys.c  Makefile
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_sys modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.mod.o
  LD [M]  /opt/test_driver/my_driver/chrdev_sys/chrdev_sys.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# insmod ./chrdev_sys.ko
chrdev_sys driver(major number 250) installed.

看一下 /dev/底下是否已有產生了呢?

# ls -l /dev/ch_sys0
crw------- 1 root root 250, 0 2012-01-02 17:16 /dev/ch_sys0
沒錯吧,哈哈^^
移除看看囉。
# rmmod chrdev_sys
chrdev_sys driver removed.
# ls -l /dev/ch_sys0
ls: 無法存取 /dev/ch_sys0: 沒有此一檔案或目錄
看到了嗎,不見了。
好玩吧......


註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

2012年1月1日 星期日

基礎 Linux Device Driver 驅動程式#7 (character device driver基礎_minor number)

有時候,如果有多個裝置,但想要做出不同的行為,那怎麼辦呢?
之前有介紹過 minor 吧。
minor number得由驅動程式自已去管理,如果想要不同的 minor提供不同的功能的話,
可以在當開啟裝置時,做minor的判斷。
但,如何做呢?不用擔心,接下來,我將會為各位示範實作的方法。
又是來寫code的時候啦,等我囉^^
時間又在一點一滴的流逝....
......................
不好意思,老婆大人在催了,
如有不足的話,我會再check。

chrdev_minor.c 原始碼如下:
/*****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#define DRIVER_NAME "chrdev_minor"
static unsigned int chrdev_minor_major = 0;
static unsigned int num_of_dev = 2;
static struct cdev chrdev_minor_cdev;

ssize_t one_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}

ssize_t one_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}

static int one_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}

static int one_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}

struct file_operations one_fops = {
.open = one_open,
.release = one_close,
.read = one_read,
.write = one_write,
};

ssize_t zero_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}

ssize_t zero_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_ALERT "%s called\n", __func__);
return 0;
}

static int zero_close(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}

static int zero_open(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "This is %s (minor=%d)\n", __func__, iminor(inode));
return 0;
}

struct file_operations zero_fops = {
.open = zero_open,
.release = zero_close,
.read = zero_read,
.write = zero_write,
};

static int chrdev_minor_open(struct inode *inode, struct file *filp)
{
switch (iminor(inode)) {
case 0:
filp->f_op = &zero_fops;
break;
case 1:
filp->f_op = &one_fops;
break;
default:
return -ENXIO;
}

if (filp->f_op && filp->f_op->open)
return filp->f_op->open(inode, filp);

return 0;
}


struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_minor_open,
};

static int chrdev_minor_init(void)
{
dev_t dev = MKDEV(chrdev_minor_major, 0);
int alloc_ret = 0;
        int cdev_ret = 0;

alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
                goto error;
        chrdev_minor_major = MAJOR(dev);

cdev_init(&chrdev_minor_cdev, &fops);
        cdev_ret = cdev_add(&chrdev_minor_cdev, dev, num_of_dev);
        if (cdev_ret)
                goto error;

printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_minor_major);
        return 0;
error:
        if (cdev_ret == 0)
                cdev_del(&chrdev_minor_cdev);
        if (alloc_ret == 0)
                unregister_chrdev_region(dev, num_of_dev);
        return -1;

}

static void chrdev_minor_exit(void)
{
dev_t dev = MKDEV(chrdev_minor_major, 0);

cdev_del(&chrdev_minor_cdev);
unregister_chrdev_region(dev, num_of_dev);

printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}

module_init(chrdev_minor_init);
module_exit(chrdev_minor_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_minor module.");

/*****************************************************************************/

Makefile 如下:
/*****************************************************************************/

KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := chrdev_minor.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/
# ls
chrdev_minor.c  Makefile
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_minor modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.o
/opt/test_driver/my_driver/chrdev_minor/chrdev_minor.c:44:2: warning: initialization from incompatible pointer type
/opt/test_driver/my_driver/chrdev_minor/chrdev_minor.c:75:2: warning: initialization from incompatible pointer type
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.mod.o
  LD [M]  /opt/test_driver/my_driver/chrdev_minor/chrdev_minor.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
chrdev_minor.c   chrdev_minor.mod.c  chrdev_minor.o  modules.order
chrdev_minor.ko  chrdev_minor.mod.o  Makefile        Module.symvers
# insmod ./chrdev_minor.ko
chrdev_minor driver(major number 250) installed.
# mknod /dev/ch_m1 c 250 0
# mknod /dev/ch_m2 c 250 1
# ls -l /dev/ch_m*
crw-r--r-- 1 root root 250, 0 2012-01-01 20:38 /dev/ch_m1
crw-r--r-- 1 root root 250, 1 2012-01-01 20:38 /dev/ch_m2
# cat /dev/ch_m1
# dmesg | tail
..........  /* 以上略過 */
This is zero_open (minor=0)
zero_read called
This is zero_close (minor=0)

# cat /dev/ch_m2
# dmesg | tail
..........  /* 以上略過 */
This is one_open (minor=1)
one_read called
This is one_close (minor=1)

各位,真不好意思囉,接下來的教學,未完待續.....

基礎 Linux Device Driver 驅動程式#6 (character device driver基礎_讀寫)

既然各位對最基本的驅動程式已有概念了,
那當然我們得再去一步的探討囉。

在上一個驅動程式,我們實作了open及release,
但,實際上只有這些handler了嗎?
當然不只,還有以下實作:
是定義在 include/include/linux/fs.h 裡。
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
};

但隨著之後的範例會愈來愈多,非常建議各位有獨立的機器或者跑虛擬機比較保險喔^^
回到主題,既然有這麼多的 handler的實作,但並不是完全都用得到,那其它沒用到的函式呢?
最基本的驅動程式,都會實作open與release(close),其他沒實作的handler就會被定義成NULL。

像open 這個handler,也就是user process的程式在操作裝置檔做的開啟動作,
結束當然就是release囉。
而其中的 inode 引數,是一個內含inode資訊的結構指標,有幾個成員我們會用到的,

bdev_t i_rdev    Major/Minor Number
void *i_private  驅動程式私有指標

既然如此,我們就可以透過 i_rdev這個成員去取得 major及minor number了。
怎麼做呢? Linux已提供了以下方法提供實作:
unsigned int iminor(const struct inode *inode);
unsigned int imajor(const struct inode *inode);

i_private 成員則是驅動程式可以自由使用的指標,不設定也沒關係。

file 也是很大的一個結構,以下是常用的幾個成員:
struct file_operations *fops    系統呼叫 handlers
unsigned int f_flags            open函式第二個引數傳入的旗標
void *private_data              驅動程式私有資料指標

通常 fops 不需修改,但有時假設需要對不同裝置提供不同的處理的話,就可自已更新fops的成員。
open handler傳回值 0為成功,非0為失敗。

在上篇教學,我們只示範了open與release這兩個 handler,再來,我們將實作read及write 這兩個handler。

ssize_t *read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
read handler 是指 當user process的程式想要向驅動程式讀取資料時用的。
file 這個結構指標是指向開啟裝置檔 kernel建立的file 結構,和open收到的指標是同一個。
因此,當open handler設給 filp->private成員的指標,在read handler也能拿來用。

buf引數是 user process呼叫read()時指定的緩衝區指標,但驅動程式不能直接取用buf指標,
必須透過 copy_to_user 這個kernel提供的函式將資料複製過去。

count引數是 user process呼叫read()時提供的緩衝區空間。
f_pos引數是offset。

read handler傳回值 0為什麼都沒做,正為寫入緩衝區的byte數,負為發生錯誤。

ssize_t *write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
write handler和read handler差不多,主要差在,write handler是當user process要傳資料給驅動時用到的,
所以 buf引數就必須用 copy_from_user 這個kernel提供的函式將資料從緩衝區讀入。

read handler傳回值 0為什麼都沒做,正為從緩衝區讀入的byte數,負為發生錯誤。

相信各位已對read 與write這兩個 handler有了初步的了解,就來試一下囉^^
給我點時間,寫一下code喔。
時間一點一滴的流失.....
....................
好囉,sorry, 久等了,那我們就來run一下囉^^

chrdev_rw.c 原始碼如下:
/*****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#define DRIVER_NAME "chrdev_rw"

static unsigned int num_of_dev = 1;
static unsigned int chrdev_rw_major = 0;
static struct cdev chrdev_rw_cdev;

struct chrdev_rw_data {
unsigned char val;
rwlock_t lock;
};

static int chrdev_rw_open(struct inode *inode, struct file *filp)
{
struct chrdev_rw_data *data_p;

data_p = (struct chrdev_rw_data *)kmalloc(sizeof(struct chrdev_rw_data), GFP_KERNEL);

if (data_p == NULL) {
printk(KERN_ALERT "malloc error!!!\n");
return -ENOMEM;
}

        data_p->val = 0xff;
rwlock_init(&data_p->lock);
filp->private_data = data_p;

return 0;
}

static int chrdev_rw_close(struct inode *inode, struct file *filp)
{
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}

        return 0;
}

ssize_t chrdev_rw_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;
int i;

printk(KERN_ALERT "%s\n", __func__);
read_lock(&data_p->lock);
val = data_p->val;
read_unlock(&data_p->lock);

for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
val_ret = -EFAULT;
goto out;
}
}

val_ret = count;
out:
return val_ret;
}

ssize_t chrdev_rw_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;

printk(KERN_ALERT "%s\n", __func__);
if (count >= 1) {
if (copy_from_user(&val, &buf[0], 1)) {
val_ret = -EFAULT;
goto out;
}
}

write_lock(&data_p->lock);
data_p->val = val;
write_unlock(&data_p->lock);

out:
return val_ret;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_rw_open,
.release = chrdev_rw_close,
.read = chrdev_rw_read,
.write = chrdev_rw_write,
};

static int chrdev_rw_init(void)
{
dev_t dev = MKDEV(chrdev_rw_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;

alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;
chrdev_rw_major = MAJOR(dev);

cdev_init(&chrdev_rw_cdev, &fops);
cdev_ret = cdev_add(&chrdev_rw_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;

printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_rw_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_rw_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}

static void chrdev_rw_exit(void)
{
dev_t dev = MKDEV(chrdev_rw_major, 0);

cdev_del(&chrdev_rw_cdev);
unregister_chrdev_region(dev, num_of_dev);

printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}

module_init(chrdev_rw_init);
module_exit(chrdev_rw_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_rw module.");

/*****************************************************************************/

Makefile 如下:
/*****************************************************************************/

KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := chrdev_rw.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

有驅動程式原始碼和Makefile,當然也少不了測試程式囉,如下:

test_rw.c
/*****************************************************************************/

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

#define DEV_FILE "/dev/ch_rw"

int main(void)
{
int fd;
unsigned char buf;
ssize_t ret;

fd = open(DEV_FILE, O_RDWR);
if (fd == -1)
perror("open");

read(fd, &buf, 1);
printf("Before write, char is %x\n", buf);

buf = 0xCC;
ret = write(fd, &buf, 1);
if (ret <= 0)
perror("write");

read(fd, &buf, 1);
printf("After write, char is %x\n", buf);

if (close(fd) != 0)
perror("close");

return 0;
}

/*****************************************************************************/
那我們就來run一下囉。

# ls
chrdev_rw.c  Makefile  test_code
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_rw modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.mod.o
  LD [M]  /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
chrdev_rw.c  chrdev_rw.ko  chrdev_rw.mod.c  chrdev_rw.mod.o  chrdev_rw.o  Makefile  modules.order  Module.symvers  test_code
# cd test_code/
# ls
test_rw.c
# gcc test_rw.c -o test_rw
# ls
test_rw  test_rw.c

OK, 模組和測試程式都編好了後,就來測試一下。
# cd ..
# insmod ./chrdev_rw.ko
chrdev_rw driver(major number 250) installed.
# mknod /dev/ch_rw c 250 0
# cd test_code/
# ./test_rw
Before write, char is ff
write: Success
After write, char is cc

成功~~~~~

我們目前是用 read_lock 和 write_lock做資料讀寫的鎖定,
但實際上還有很多種方式,到時,在後面的教學,我將會為各位說明更多種的實作方法。


註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。