聚會時間公告: 因應COSCUP 2011, Kalug 8月份休會一次

八月 29, 2015

csw-dawn
csw-dawn
» dnw install on ubuntu 14.04 LTS


Download dnw:
$ wget https://dnw-linux.googlecode.com/files/dnw-linux-v1.01.tar.gz

Install kernel source:
$ sudo apt-get install linux-source-`uname -r`

Install kernel headers:
$ sudo apt-get install linux-headers-`uname -r`

Install kernel headers for development:
$ sudo apt-get install linux-libc-dev

$ sudo apt-get install kernel-package libncurses5-dev fakeroot

Makefile (in ~/dnw-linux/src/driver)
--------------------

KDIR="/usr/src/linux-headers-3.16.0-30-generic"
PWD=$(shell pwd)
VV=

obj-m := secbulk.o
secbulk-m := secbulk.o


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

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

--------------------

$ cd ~/
$ mkdir dnw-linux
$ mv dnw-linux-v1.01.tar.gz dnw-linux
$ cd dnw-linux/
$ tar zxvf dnw-linux-v1.01.tar.gz 
$ cd ~/dnw-linux/src/driver
$ lsusb
Bus 001 Device 009: ID 04e8:1234 Samsung Electronics Co., Ltd   /* <— here */
Bus 001 Device 003: ID 067b:2303 Prolific Technology, Inc. PL2303 Serial Port
Bus 001 Device 002: ID 80ee:0021 VirtualBox USB Tablet
Bus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
$ emacs secbulk.c
…...
static struct usb_device_id secbulk_table[]= {
        { USB_DEVICE(0x5345, 0x1234) }, /* FS2410 */
        { USB_DEVICE(0x04e8, 0x1234) }, /* EZ6410 */
        { USB_DEVICE(0x04e8, 0x1234) }, /* <— here */
};
…...
$ make
make -C "/opt/linux-source-3.13.0" M=/home/moto/dnw-linux/src/driver V= modules
make[1]: Entering directory `/opt/linux-source-3.13.0'

  WARNING: Symbol version dump /opt/linux-source-3.13.0/Module.symvers
           is missing; modules will have no dependencies and modversions.

  CC [M]  /home/moto/dnw-linux/src/driver/secbulk.o
In file included from /home/moto/dnw-linux/src/driver/secbulk.c:1:0:
/home/moto/dnw-linux/src/driver/secbulk.c: In function ‘secbulk_write’:
include/linux/kernel.h:672:17: warning: comparison of distinct pointer types lacks a cast [enabled by default]
  (void) (&_min1 == &_min2);  \
                 ^
/home/moto/dnw-linux/src/driver/secbulk.c:60:14: note: in expansion of macro ‘min’
   to_write = min(len, BULKOUT_BUFFER_SIZE);
              ^
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/moto/dnw-linux/src/driver/secbulk.mod.o
  LD [M]  /home/moto/dnw-linux/src/driver/secbulk.ko
make[1]: Leaving directory `/opt/linux-source-3.13.0'

$ sudo cp secbulk.ko /lib/modules/3.16.0-30-generic/kernel
$ sudo depmod -A

Load module 
$ sudo modprobe secbulk

Auto loading while booting
$ sudo emacs /etc/modules
Add secbulk

$ cd ~/dnw-linux/src/dnw
$ make
gcc -g -o dnw dnw.c

Client:
SMDK6410 # dnw
OTG cable Connected!
Now, Waiting for DNW to transmit data

HOST:
$ touch 123
$  sudo ./dnw 123
load address: 0x57E00000
Writing data...
100%    0x0000000A bytes (0 K)
speed: 0.001907M/S
搞定收功~ XD

十二月 28, 2014

csw-dawn
csw-dawn
» Install Node.js on ubuntu 14.04

1. sudo apt-get update
2. sudo apt-get install gcc
3. sudo apt-get install make
4. sudo apt-get install g++
5. sudo apt-get install libssl-dev
6. wget http://nodejs.org/dist/v0.10.35/node-v0.10.35.tar.gz
7. mv node-v0.10.35.tar.gz /usr/local/
8. cd /usr/local
9. tar zxvf node-v0.10.35.tar.gz
10. cd node-v0.10.35
11. ./configure
12. make
13. sudo make install
14. vim helloworld.js
      console.log('Hello World! Node.js');
15. node helloworld.js
      Hello World! Node.js
16. enjoy!

三月 9, 2014

csw-dawn
csw-dawn
» 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

» Ubuntu 安裝 Java JDK

1. 先到 http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
    下載 jdk
2. 解壓縮
    $ tar zxvf jdk-7u51-linux-i586.tar.gz
3. 建立jdk目錄
    $ sudo mkdir /usr/lib/jdk
4. Copy jdk 到 /usr/lib 目錄下
    $ sudo cp -r jdk1.7.0_51 /usr/lib/jdk
5. 設定環境變數
    $ sudo vim /etc/profile
    加上以下內容:
        export JAVA_HOME=/usr/lib/jdk/jdk1.7.0_51
        export JRE_HOME=/usr/lib/jdk/jdk1.7.0_51/jre
        export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin
        export CLASSPATH=.:$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
6. 置換系統默認 jdk
    $ sudo update-alternatives --install /usr/bin/java java /usr/lib/jdk/jdk1.7.0_51/bin/java 300
    $ sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jdk/jdk1.7.0_51/bin/javac 300
    $ sudo update-alternatives --config java
    $ sudo update-alternatives --config javac
7. 確認安裝
    $ java -version
     java version "1.7.0_51"
     Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
    Java HotSpot(TM) Client VM (build 24.51-b03, mixed mode)

8. 完成

» Ubuntu Install Tomcat

1. Install Tomcat
$ sudo apt-get install tomcat7
2. Check Status
$ sudo /etc/init.d/tomcat7 status
* Tomcat servlet engine is running with pid 13249
3. Install mysql-connector-java
$ sudo cp mysql-connector-java-5.1.22-bin.jar /usr/share/tomcat7/lib/
4. Restart Tomcat
$ sudo /etc/init.d/tomcat7 restart
* Stopping Tomcat servlet engine tomcat7 [ OK ]
* Starting Tomcat servlet engine tomcat7 [ OK ]
5. Test
$ cd /var/lib/tomcat7/webapps/ROOT
$ sudo vim test1.jsp
test1.jsp:
<%
out.println("Hello World!");
%>
6. Open browser
http://<your_server_ip_address>:8080/test1.jsp
will show
Hello World!
7. cd /usr/share/tomcat7/lib
8. $ sudo cp servlet-api.jar jsp-api.jar el-api.jar /usr/lib/jdk/jdk1.7.0_51/jre/lib/ext/

» Ubuntu Install Tomcat (src)



1. Install JDK
reference here:
http://csw-dawn.blogspot.tw/2014/02/ubuntu-java-jdk.html
2. Download tomcat7
https://tomcat.apache.org/download-70.cgi
3. $ tar zxvf apache-tomcat-7.0.52.tar.gz
4. $ sudo cp -r apache-tomcat-7.0.52 /usr/local/tomcat7
5. $ cd /usr/local/tomcat7/lib
6. $ sudo cp servlet-api.jar jsp-api.jar el-api.jar /usr/lib/jdk/jdk1.7.0_51/jre/lib/ext/
7. Install mysql-connector-java
$ sudo cp mysql-connector-java-5.1.22-bin.jar /usr/local/tomcat7/lib/
8. $ sudo vim /etc/rc.local
Add : sudo sh /usr/local/tomcat7/bin/startup.sh

二月 20, 2014

csw-dawn
csw-dawn
» Java 使用 Apache POI

1. Download page
    http://poi.apache.org/download.html
    to download poi-bin-3.10-FINAL-20140208.tar.gz
2. 解壓縮
3. 打開 Eclipse
    在 Project 上按右鍵
    Properties->Java Build Path->Libraries->Add JARs
    選擇 poi-3.10-FINAL-20140208.jar 檔加入
4. 開始使用

八月 16, 2012

csw-dawn
csw-dawn
» 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"


一月 6, 2012

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

一月 5, 2012

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

一月 3, 2012

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


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

一月 2, 2012

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


一月 1, 2012

csw-dawn
csw-dawn
» 基礎 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 驅動程式#5 (character device driver基礎)

交換資料
這邊要說明的是user process 與驅動程式交換資料的方法。
這邊是以character這類的驅動程式做說明。

Device file 裝置檔
device file 又可稱為 special file,
通常位於 /dev 目錄底下。

基本上,一個裝置就要有一個device file,
why device file also call special file?

# ls -l /dev/ttyS0
crw-rw---- 1 root dialout 4, 64 2011-12-30 13:20 /dev/ttyS0

# ls -l README
-rw-r--r-- 1 walter walter 1691 2011-12-30 11:00 README

看到前面多了一個c嗎?很明顯和一般檔案不一樣,所以又稱為special file,
而c指的是字元裝置。

當然還有其他裝置檔,如下:
b      : 區塊裝置 (block device)
c or u : 字元裝置 (character device/unbuffered device)
p      : 具名管線 或稱為FIFO(name pipe)

建立與刪除裝置檔
底下是使用範例
  mknod   裝置檔名  種類 主編號 副編號
# mknod /dev/sample c 254 0
# ls -l /dev/sample
crw-r--r-- 1 root root 254, 0 2011-12-30 14:35 /dev/sample

要刪除的話,用rm指令即可
# rm /dev/sample

目前Linux 的major number是12-bit, minor是 20-bit,
合起來一共為 32-bit。
而major/minor number 可以使用的bit數是定義在 include/linux/kdev_t.h

Major Number的豋記方法
如果要將裝置檔和驅動程式連結起來的話,
驅動程式就必須向kernel豋記 major number才行,
目前都採動態豋記為主,所以我們就以動態豋記來做說明。

動態豋記法
Linux 2.6開始建議採用動態豋記法,也就是用cdev結構豋記 major number的方法。
1. 以 alloc_chrdev_region()動態取得 major number。
2. 以 cdev_init() 豋記系統呼叫 handler。
3. 以 cdev_add() 向 kernel豋記驅動程式。

在卸載驅動程式的時候,必須以相反步驟解除豋記才行。
1. 以 cdev_del() 向 kernel釋放驅動程式。
2. 以 unregister_chrdev_region()釋放 major number。

我們就分別來看一下這些函式。
int alloc_chrdev_region(dev_t *dev,
                        unsigned int firstminor,
                        unsigned int count,
                        char *name);
函式如果成呼叫會回傳0, 失敗則會回傳負數。
dev引數就會放進新的 major number及minor number。
firstminor 則是minor number的第一個數字編號。
count      是 minor number的個數,也就是裝置的個數。
name       是驅動程式的名稱。

void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev_init負責初使化 cdev結構,並豋記系統呼叫 handler(fops)。
因為 cdev結構變數,在卸載驅動程式的時候還會用到,所以要將它定義為全域變數。

int cdev_add(struct cdev *p, dev_t dev, unsigned int count);
cdev_add()會向 kernel豋記 cdev_init()設定好的裝置資訊,
p 引數可傳入 cdev_init()初使化完成的 cdev結構變數。
dev 則是豋記好的 major number 及 minor number 的起點。
count 則是 minor的個數。
同樣的,該函式如果成呼叫會回傳0, 失敗則會回傳負數。

void cdev_del(struct cdev *p);
會從 kernel釋放驅動程式。

void unregister_chrdev_alloc(dev_t from, unsigned int count);
則會釋放之前拿到的 major number。
from 是 major number 及 minor number 的起點。
count則是當初配置的minor number 個數。

OK, 既然稍作說明過後,我們就來run一下程式碼,才不會如此沉悶吧。

test_alloc_chrdev.c 程式碼如下:

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

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 #include <linux/fs.h>
     4 #include <linux/cdev.h>
     
     5 #define DRIVER_NAME "test_chrdev"
     6 static unsigned int test_chrdev_alloc_major = 0;
     7 static unsigned int num_of_dev = 1;
     8 static struct cdev test_chrdev_alloc_cdev;
     
     9 static int test_chrdev_alloc_open(struct inode *inode, struct file *filp)
    10 {
    11 printk(KERN_ALERT "test_chrdev_alloc is open.\n");
    12 return 0;
    13 }
     
    14 static int test_chrdev_alloc_close(struct inode *inode, struct file *filp)
    15 {
    16 printk(KERN_ALERT "test_chrdev_alloc is close.\n");
    17 return 0;
    18 }
     
    19 struct file_operations fops = {
    20 .owner = THIS_MODULE,
    21 .open = test_chrdev_alloc_open,
    22 .release = test_chrdev_alloc_close,
    23 };
     
    24 static int test_alloc_chrdev_init(void)
    25 {
    26 dev_t dev = MKDEV(test_chrdev_alloc_major, 0);
    27 int alloc_ret = 0;
    28 int cdev_ret = 0;
     
    29 alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
    30 if (alloc_ret)
    31 goto error;
    32 test_chrdev_alloc_major = MAJOR(dev);
     
    33 cdev_init(&test_chrdev_alloc_cdev, &fops);
    34 cdev_ret = cdev_add(&test_chrdev_alloc_cdev, dev, num_of_dev);
    35 if (cdev_ret)
    36 goto error;
    37
    38 printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, test_chrdev_alloc_major);
    39 return 0;
     
    40 error:
    41 if (cdev_ret == 0)
    42 cdev_del(&test_chrdev_alloc_cdev);
    43 if (alloc_ret == 0)
    44 unregister_chrdev_region(dev, num_of_dev);
    45 return -1;
    46 }
     
    47 void test_alloc_chrdev_exit(void)
    48 {
    49 dev_t dev = MKDEV(test_chrdev_alloc_major, 0);
     
    50 cdev_del(&test_chrdev_alloc_cdev);
    51 unregister_chrdev_region(dev, num_of_dev);
    52 printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
    53 }
     
    54 module_init(test_alloc_chrdev_init);
    55 module_exit(test_alloc_chrdev_exit);
     
    56 MODULE_LICENSE("GPL") ;
    57 MODULE_AUTHOR("Wang Chen Shu");
    58 MODULE_DESCRIPTION("This is test_alloc_chrdev module.");
 
/*****************************************************************************/

Makefile 如下:

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

     1 KDIR="/opt/linux-source-2.6.38"
     2 PWD=$(shell pwd)
     
     3 obj-m := test_alloc_chrdev.o
     
     4 all:
     5 $(MAKE) -C ${KDIR} M=${PWD} modules
     6 clean:
     7 $(MAKE) -C ${KDIR} M=${PWD} clean

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

我們這裡又準備了一個名叫 test_chrdev.c 的測試程式。

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

     1 #include <stdio.h>
     2 #include <errno.h>
     3 #include <fcntl.h>
     
     4 #define DEV_FILE_NAME "/dev/test_chrdev"
     
     5 int main(void)
     6 {
     7 int fd;
     8 fd = open(DEV_FILE_NAME, O_RDWR);
     9 if (fd == -1)
    10 perror("open");
     
    11 sleep(3);
     
    12 if (close(fd) != 0)
    13 perror("close");
     
    14 return 0;
    15 }

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

有了程式碼,測試程式以及Makefile,當然直接給它build下去囉^^

# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_alloc_chrdev modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.mod.o
  LD [M]  /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
這時就產生了ko檔。

# insmod test_alloc_chrdev.ko
test_chrdev driver(major number 250) installed.
看到了嗎,我們拿到了 250 這個 major number。
但此時我們並沒有 device file裝置檔,還記得怎麼建來嗎? 哈,想一下喔^^
這樣就行了啊。
# mknod /dev/test_chrdev c 250 0

編譯一下 test_chrdev.c
# gcc test_chrdev.c -o test_chrdev
# ./test_chrdev
經過3秒.....

這時候的訊息,我們就從 dmesg去看吧,走~~
# dmesg | tail
..........  // 以上略過 //
test_chrdev_alloc is open.
test_chrdev_alloc is close.

看到kernel 印出的資訊了嗎?
再來就是刪除裝置檔及卸載模組吧^^
# rm /dev/test_chrdev
# rmmod test_alloc_chrdev
test_chrdev driver removed.

這樣就完成了整個流程,包含豋記及反豋記驅動。


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

十二月 30, 2011

csw-dawn
csw-dawn
» 基礎 Linux Device Driver 驅動程式#2 (hello world驅動)

今天,我們先以hello world 這個簡單的驅動程式作為我們的第一個範例程式。

kernel的module, 例如驅動程式,並沒有像user space的程式一樣,
能夠使用 stdio.h, string.h等這些標頭檔,
所以取而代之的是 linux/module.h 以及 linux/init.h 這兩個標頭檔,
最基本的驅動程式,都一定會有這兩個標頭檔。
廢話不多說,我們就先來示範一下hello world這支驅動程式。
以下是程式碼:

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

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     
     
     3 static int hello_init(void)
     4 {
     5 printk(KERN_ALERT "Hello world init.\n");
     6 return 0;
     7 }
     
     8 static void hello_exit(void)
     9 {
    10 printk(KERN_ALERT "Hello world exit.");
    11 }
     
    12 module_init(hello_init);
    13 module_exit(hello_exit);
     
    14 MODULE_LICENSE("GPL");
    15 MODULE_AUTHOR("Wang Chen Shu");
    16 MODULE_DESCRIPTION("This is hello world module test.");

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

// 底下這兩行是模組所需的標頭檔 //
1  #include <linux/init.h>
2  #include <linux/module.h>

/*
 下面的hello_init函式則是模組在載入時的進入點,
 加上static 是要將命名空間限制在檔案內。
 */
3  static int hello_init(void)
4  {
5          printk(KERN_ALERT "Hello world init.\n");
6          return 0;
7  }

// 下面的hello_exit函式則是模組在卸載時的進入點。 //
8  static void hello_exit(void)
9  {
10          printk(KERN_ALERT "Hello world exit.");
11 }

// 透過module_init 巨集指定hello_init //
12  module_init(hello_init);

// 透過module_exit 巨集指定hello_exit //
13  module_exit(hello_exit);

// 下面則是對模組的一些聲明,像授權方式, 作者以及模組的描述 //
14  MODULE_LICENSE("GPL");
15  MODULE_AUTHOR("Wang Chen Shu");
16  MODULE_DESCRIPTION("This is hello world module test.");

至於, printk的部份,可參考include/linux/printk.h, 如下:
#define KERN_EMERG      "<0>"   /* system is unusable                   */
#define KERN_ALERT      "<1>"   /* action must be taken immediately     */
#define KERN_CRIT       "<2>"   /* critical conditions                  */
#define KERN_ERR        "<3>"   /* error conditions                     */
#define KERN_WARNING    "<4>"   /* warning conditions                   */
#define KERN_NOTICE     "<5>"   /* normal but significant condition     */
#define KERN_INFO       "<6>"   /* informational                        */
#define KERN_DEBUG      "<7>"   /* debug-level messages                 */

有了,程式碼,當然您也要準備linux 的kernel 原始碼,
因為模組的編譯需要核心原始碼。
我們這裡是用linux 2.6.38的核心做為示範。
當然您得下載您需要的版本,

# uname -r
2.6.38-13-generic-pae
像我的核心就是 2.6.38,所以視您的版本而定。
當然,您也不想為了每次編譯都打一長串的指令,
所以當初核心的開發人員也為各位想到了這點,所以才有makefile這類的script。

所以,我們還要撰寫一個makefile,檔名為Makefile,內容如下:

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

     1 KDIR="/opt/linux-source-2.6.38"
     2 PWD=$(shell pwd)
     3 VV=
     
     4 obj-m := hello.o
     
     5 all:
     6 $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules
     7 clean:
     8 $(MAKE) -C ${KDIR} M=${PWD} clean

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

-C 後面接的是核心原始碼的路徑
M= 後面接的是目前的目錄位置
6          $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules

這裡看到一個變數VV,如果指定為1的話, 就可以在編譯時看到更詳細的建構過程。
我這裡將我下載的核心原始碼放在/opt做為測試用。
有了驅動原始碼和Makefile, 就可以開始進行下一步了。示範如下:

# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V= modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/hello/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/hello/hello.mod.o
  LD [M]  /opt/test_driver/my_driver/hello/hello.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'

接著我做了一個動作:
# vim Makefile

3  VV=
改成
3  VV=1

再做一次。

# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V=1 modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
test -e include/generated/autoconf.h -a -e include/config/auto.conf || ( \
echo; \
echo "  ERROR: Kernel configuration is invalid."; \
echo "         include/generated/autoconf.h or include/config/auto.conf are missing.";\
echo "         Run 'make oldconfig && make prepare' on kernel src to fix it."; \
echo; \
/bin/false)
mkdir -p /opt/test_driver/my_driver/hello/.tmp_versions ; rm -f /opt/test_driver/my_driver/hello/.tmp_versions/*
make -f scripts/Makefile.build obj=/opt/test_driver/my_driver/hello
  gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.o.d  -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include  -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude  -include include/generated/autoconf.h -Iubuntu/include  -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO  -DMODULE  -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello)"  -D"KBUILD_MODNAME=KBUILD_STR(hello)" -c -o /opt/test_driver/my_driver/hello/.tmp_hello.o /opt/test_driver/my_driver/hello/hello.c
  if [ "-pg" = "-pg" ]; then if [ /opt/test_driver/my_driver/hello/hello.o != "scripts/mod/empty.o" ]; then /opt/linux-source-2.6.38/scripts/recordmcount "/opt/test_driver/my_driver/hello/hello.o"; fi; fi;
(cat /dev/null;   echo kernel//opt/test_driver/my_driver/hello/hello.ko;) > /opt/test_driver/my_driver/hello/modules.order
make -f /opt/linux-source-2.6.38/scripts/Makefile.modpost
  scripts/mod/modpost -m -a -i /opt/linux-source-2.6.38/Module.symvers -I /opt/test_driver/my_driver/hello/Module.symvers  -o /opt/test_driver/my_driver/hello/Module.symvers -S -w  -s
  gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.mod.o.d  -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include  -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude  -include include/generated/autoconf.h -Iubuntu/include  -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO  -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello.mod)"  -D"KBUILD_MODNAME=KBUILD_STR(hello)" -DMODULE  -c -o /opt/test_driver/my_driver/hello/hello.mod.o /opt/test_driver/my_driver/hello/hello.mod.c
  ld -r -m elf_i386 -T /opt/linux-source-2.6.38/scripts/module-common.lds --build-id  -o /opt/test_driver/my_driver/hello/hello.ko /opt/test_driver/my_driver/hello/hello.o /opt/test_driver/my_driver/hello/hello.mod.o
make[1]: Leaving directory `/opt/linux-source-2.6.38'

看到了嗎,修改成V=1,就秀出更詳細的過程了。
此時,該資料夾下就會多一個hello.ko的模組,就可以進行載入了。

# ls
hello.c   hello.mod.c  hello.o   modules.order   README
hello.ko  hello.mod.o  Makefile  Module.symvers

# insmod hello.ko
Hello world init.
看到了,這就成功的載入了hello world的模組。

也可以從這邊看到。
# lsmod | grep hello
hello                  12448  0

卸載模組也很簡單,只需下模組的名稱即可。
# rmmod hello
Hello world exit.
看到了嗎,成功的卸載了。

如果要做清除動作的話,就執行clean即可。
# ls
hello.c   hello.mod.c  hello.o   modules.order   README
hello.ko  hello.mod.o  Makefile  Module.symvers

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

# ls
hello.c  Makefile  README
如此,就成功的清除了。



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

» 基礎 Linux Device Driver 驅動程式#3 (hello_multifile驅動)

hello_multifile這支驅動程式與hello驅動程式其實沒太多的差別,
只是差在此驅動程式會引用到其他模組的函式如此。

以下是main.c 的程式碼:
/*****************************************************************************/
   
     1 #include <linux/module.h>
     2 #include <linux/init.h>
     
     3 extern void sub();
     
     4 static int hello_multifile_init(void)
     5 {
     6 printk(KERN_ALERT "hello_multifile is loaded.\n");
     7 sub();
     8 return 0;
     9 }
     
     
    10 static int hello_multifile_exit(void)
    11 {
    12 printk(KERN_ALERT "hello_multifile is unloaded.\n");
    13 }
     
    14 module_init(hello_multifile_init);
    15 module_exit(hello_multifile_exit);
     
    16 MODULE_LICENSE("GPL");
    17 MODULE_AUTHOR("Wang Chen Shu");
    18 MODULE_DESCRIPTION("Sample test.");

/*****************************************************************************/
看到這一行了嗎?
3  extern void sub();
學過C語言的都知道,
這一行是告訴編譯器說 sub() 在其它位置被定義了,
編譯器就會試著去找sub。


以下是sub.c 的程式碼:
/*****************************************************************************/

     1 #include <linux/module.h>
     2 #include <linux/init.h>
     
     3 void sub(void)
     4 {
     5 printk(KERN_ALERT "%s: sub() called\n", __func__);
     6 }

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

Makefile如下:

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

     1 CFILES := main.c sub.c
     2 obj-m := hello.o
     3 hello-objs := $(CFILES:.c=.o)
     
     4 KDIR="/opt/linux-source-2.6.38/"
     5 PWD=$(shell pwd)
     
     6 all:
     7 $(MAKE) -C $(KDIR) M=$(PWD) modules
     8 clean:
     9 $(MAKE) -C $(KDIR) M=$(PWD) clean
    10

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

話不多說,就直接來給它編譯下去囉^^

# make
make -C "/opt/linux-source-2.6.38/" M=/opt/test_driver/my_driver/hello_multifile modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/hello_multifile/main.o
/opt/test_driver/my_driver/hello_multifile/main.c:4:1: warning: function declaration isn’t a prototype
/opt/test_driver/my_driver/hello_multifile/main.c: In function ‘hello_multifile_exit’:
/opt/test_driver/my_driver/hello_multifile/main.c:17:1: warning: no return statement in function returning non-void
/opt/test_driver/my_driver/hello_multifile/main.c: In function ‘__exittest’:
/opt/test_driver/my_driver/hello_multifile/main.c:20:1: warning: return from incompatible pointer type
  CC [M]  /opt/test_driver/my_driver/hello_multifile/sub.o
  LD [M]  /opt/test_driver/my_driver/hello_multifile/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/hello_multifile/hello.mod.o
  LD [M]  /opt/test_driver/my_driver/hello_multifile/hello.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'

此時目錄就會多出hello.ko, 如下:

# ls
hello.ko  hello.mod.c  hello.mod.o  hello.o  main.c  main.o  Makefile  modules.order  Module.symvers  README  sub.c  sub.o

有了模組,接下來當然是載入囉,還用得著多說嗎?
# insmod hello.ko
hello_multifile is loaded.
sub: sub() called
看到了嗎? 它引用了sub。
移除模組也是和hello一樣的做法。
# rmmod hello
hello_multifile is unloaded.
這樣就成功的移除了模組。


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


» 基礎 Linux Device Driver 驅動程式#4 (執行環境 context)

關於執行環境:
由於裝置驅動程式是屬於kernel程式碼的一部份,
且驅動程式在kernel space運作,
kernel的程式有context(執行環境)的觀念,並分為以下兩種:
Process context(一般行程執行環境)
Interrupt context(中斷執行環境)

但這兩種context有其差異性,如下所示:
------------------------------------------------------------------------
| Context           | 可否sleep | 可否被preempt(搶先執行) | 處理時間   |
| Process context   |     可    |            可           |   可拖長   |
| Interrupt context |   不可    |          不可           |  極力縮短  |
------------------------------------------------------------------------

重點就在於Interrupt context是全系統執行權限最高的,
如果sleep的話,可想而知,會有什麼事發生,
就是沒人能叫醒它,結果就造成系統呼叫死結,導致kernel panic並停止運作。
因此就要確定,在interrupt context 的kernel 函式都不會用到sleep。
基於同樣理由,故不能被preempt。

關於Endian:
Byte 陣列一次讀寫2個bytes 以上的資料時,byte會以何種次序排列,
就是所謂endian的問題。

那當然, 我們可用簡單的程式來測試一下。
test_endian.c 的程式碼如下:

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

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     
     4 int main(void)
     5 {
     6 unsigned char buf[4];
     7 int n = 0xdeabbeef;
     
     8 memcpy(buf, &n, sizeof(n));
     9 printf("%02x %02x %02x %02x\n",
    10                buf[0], buf[1], buf[2], buf[3]
    11              );
     
    12 return 0;
    13 }

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

# gcc test_endian.c -o test_endian
# ./test_endian
如果執行結果為
ef be ab de <--- Little endian
de ab be ef <---    Big endian

但通常開發驅動程式的時候,不需要應對endian的問題,因為kernel會把endian的部份吸收。
有endian差異的部份主要包含
CPU
Bus(PCI 或 USB)
網路封包
EEPROM等資料內容


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

support:

biggo.com.tw

biggo.sg

A Django site.