Thursday, September 24, 2015

Char Device Driver - 1_Architecture


驅動程式框架:
User space 的 APP 透過 open, read, write, close, ioctl, llseek, fsync 等函數來對應 Kernel space 裡的 Device driver: open, read,  write, close, ioctl, llseek, fsync 。
以 LED 驅動程式為例,架構如下,
----------------------------------------APP:  open, write
----------------------------------------system call
----------------------------------------VFS
----------------------------------------Device Driver:  led_open,  led_write
----------------------------------------Hardware



1
2


驅動程式流程:
  1. 設計led_open, led_write 撰寫硬體操作驅動程式
  2. 構造 file_operation 中各成員函數
  3. 將 file_operation 結構成員註冊進 kernel, 使用 int register_chrdev(unsigned int major, const char *name, struct file_operations *fops), 其中參數 major 為主設備號, name 為設備名稱,
  4. 操作結束後需釋放裝置, 使用  unregister_chrdev(unsigned int major, const char *name)
1
2

重要結構(需再研究其他結構)
  1. file_operation 結構定義於 <linux/fs.h>, 含有字元驅動程式各作業方法的函式指標
  2. file 結構定義於 <linux/fs.h>, 代表已開啟檔案
  3. inode 結構代表檔案系統的節點(位於磁碟上的檔案)


實作1: 第一個驅動程式,只在 console 列印資訊
=========================================================
first_drv.c

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static int first_drv_open(struct inode *inode, struct file *file)

{
printk("first_drv_open\n");
return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)

{
printk("first_drv_write\n");
return 0;
}

static struct file_operations first_drv_fops = 

{
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動創建的__this_module變數 */
.open = first_drv_open,
.write = first_drv_write,
};

static int first_drv_init(void)

{
register_chrdev(111, "first_drv", &first_drv_fops); // 向kernel註冊
return 0;
}

static void first_drv_exit(void)

{
unregister_chrdev(111, "first_drv"); // 卸載
}

module_init(first_drv_init);

module_exit(first_drv_exit);

MODULE_LICENSE("GPL");


=========================================================
Makefile


KERN_DIR = /home/scyu/share/linux_source/kernel/linux-2.6.22.6


all:

make -C $(KERN_DIR) M=`pwd` modules 

clean:

make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order

obj-m += first_drv.o

=========================================================
firstdrvtest.c

#include <sys/types.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

/* firstdrvtest on

  * firstdrvtest off
  */
int main(int argc, char **argv)
{
    int fd;
    int val=1;
    fd = open("/dev/xxx", O_RDWR);
    if (fd < 0) printf("can't open!\n");
    write(fd, &val, 4);
    return 0;
}
=========================================================
實驗結果:

# ls
bin         lib         mnt         sbin        usr
dev         linuxrc     proc        sys
etc         lost+found  root        tmp
# cd mnt/nfs/
# ls
first_drv.ko  firstdrvtest
# cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
  7 loop
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
# insmod first_drv.ko
# cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
111 first_drv         <--- 看到載入的驅動
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
  7 loop
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
# ./firstdrvtest
can't open!                             <-- 找不到設備節點
# mknod /dev/xxx c 111 0     <-- 手動掛載設備節點, 指定 major number
# ./firstdrvtest
first_drv_open
first_drv_write
=========================================================


實作2: 第一個驅動程式,只在 console 列印資訊,加入自動創建設備節點
=========================================================
first_drv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;



static int first_drv_open(struct inode *inode, struct file *file)
{
printk("first_drv_open\n");
return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("first_drv_write\n");
return 0;
}

static struct file_operations first_drv_fops = 
{
.owner = THIS_MODULE, /* 這是一個巨集,推向編譯模組時自動創建的__this_module變數*/
.open = first_drv_open,
.write = first_drv_write,
};

int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 向kernel註冊, major number 隨系統創建,成功返回 major number,失敗返回0
firstdrv_class = class_create(THIS_MODULE, "firstdrv_class");

    firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");   // 系自動創建 /dev/xyz


return 0;
}

static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸載

    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}

module_init(first_drv_init);
module_exit(first_drv_exit);

MODULE_LICENSE("GPL");
=========================================================
firstdrvtest.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

/* firstdrvtest on
  * firstdrvtest off
  */
int main(int argc, char **argv)
{
    int fd;
    int val=1;
    fd = open("/dev/xyz", O_RDWR);
    if (fd < 0) printf("can't open!\n");
    write(fd, &val, 4);
    return 0;
}
=========================================================
實驗結果:

Please press Enter to activate this console.
starting pid 772, tty '/dev/s3c2410_serial0': '/bin/sh'
#
# ls
bin         lib         mnt         sbin        usr
dev         linuxrc     proc        sys
etc         lost+found  root        tmp
# cd mnt/nfs/
# ls
first_drv.ko  firstdrvtest
# insmod first_drv.ko     <-- 系統自動創建 /dev/xyz
# ls -l /dev/xyz
crw-rw----    1 0        0        252,   0 Jan  1 00:01 /dev/xyz
# cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 90 mtd
 99 ppdev
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 s3c2410_serial
252 first_drv
253 usb_endpoint
254 rtc

Block devices:
  1 ramdisk
  7 loop
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
# cd /sys/
# ls
block     class     firmware  kernel    power
bus       devices   fs        module
# cd class
# ls
firstdrv_class  misc            rtc             tty
graphics        mmc_host        scsi_device     usb_endpoint
hwmon           mtd             scsi_disk       usb_host
i2c-adapter     net             scsi_host       vc
input           ppdev           sound           vtconsole
mem             printer         spi_master
# cd firstdrv_class/
# ls
xyz
# cd xyz
# ls
dev        subsystem  uevent
# cat dev
252:0
# cd /mnt/nfs/
# ls
first_drv.ko  firstdrvtest
# rmmod first_drv.ko
# ls -l /dev/xyz
ls: /dev/xyz: No such file or directory   <-- 系統自動卸載
# insmod first_drv.ko
# ls -l /dev/xyz
crw-rw----    1 0        0        252,   0 Jan  1 00:03 /dev/xyz
# ls
first_drv.ko  firstdrvtest
# ./firstdrvtest
first_drv_open
first_drv_write
=========================================================
參考資料:
  1. 嵌入式Linux應用開發完全手冊
  2. Linux Device Driver (3rd)
  3. Linux Kernel Development (3rd)

No comments:

Post a Comment