2012年8月17日 星期五

ubuntu 12.04 install apache/phpmyadmin


1.  sudo apt-get install apache2
2.  sudo apt-get install php5 libapache2-mod-php5
3.  sudo apt-get install mysql-server
4.  sudo apt-get install libapache2-mod-auth-mysql php5-mysql phpmyadmin
5. cd /var/www
6. sudo ln -s /usr/share/phpmyadmin .
7. sudo /etc/init.d/apache2 restart
open web browser http://127.0.0.1/phpmyadmin

2012年8月16日 星期四

samba mount on linux

mount -t cifs -o username="username",password="password" //192.168.1.x/src /dist

start vmware vmx on linux

su -l root -c "/usr/bin/vmrun -T ws start /opt/vmware/xxx.vmx nogui"

安裝 qemu on ubuntu 12.04(包含執行 ARM 指令集的 QEMU emulator)

安裝:
1. 安裝gcc-4.6
2. apt-get install libsdl1.2debian libsdl1.2-dev zlib1g-dev
3. download qemu-1.1.1-1.tar.bz2
4. mv qemu-1.1.1-1.tar.bz2 /usr/local
5. cd /usr/local
6. tar jxvf qemu-1.1.1-1.tar.bz2
7. cd qemu-1.1.1
8. ./configure --cc=/usr/bin/gcc-4.6 --host-cc=/usr/bin/gcc-4.6 --target-list=arm-softmmu

測試:
 ./arm-softmmu/qemu-system-arm -kernel /home/walter/arm-test/zImage.integrator -initrd /home/walter/arm-test/arm_root.img -nographic -append "console=ttyAMA0"


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驅動程式設計由平田豐著的這本書。