1. 程式人生 > >Linux裝置驅動第四天(自動建立裝置節點、LED驅動程式)

Linux裝置驅動第四天(自動建立裝置節點、LED驅動程式)

回顧:
與驅動有關的幾個重要結構體
1,struct cdev //從軟體上代表硬體裝置
{
dev_t dev;//裝置號 = 主裝置號+次裝置號
struct file_operations f_ops;
}

2,struct file_operations{
open
read
write
release
ioctrl
mmap
}

3,struct file//對應核心裡面的檔案表中的一項
{
f_pos;//讀寫的偏移位置
f_op;//檔案的操作函式集合
f_flags;//檔案打時傳遞的引數 讀寫模式
private_data// 私有資料
…..
}
這裡寫圖片描述

4,struct inode
{
dev_t i_rdev;//裝置號
file_opeartions *i_fop;//操作函式集合,核心會給它賦值
struct cdev *i_cdev;//字元裝置集合
….
}

以上兩個結構體多用於一套驅動程式碼驅動多個同類型的裝置。如下面的案例。

問題:找到串列埠驅動程式原始碼檔案
KCofnfig:make menuconfig 時生成選單選項
Makefile:和原始碼在同一級目錄,決定編譯動作
make menuconfig
-> Device Drivers
->Character devices
->Samsung S5PV210 Serail port support
巨集: CONFIG_SERIAL_S5PV210
路徑:devices/serail/Kconfig確定原始碼在drivers/serail/目錄下
開啟該路徑下的Makefile查詢 CONFIG_SERIAL_S5PV210,找到對應的.c檔案

Uart案例:
建立裝置節點
mknod /dev/serial0 c 204 64 ==> open(“/dev/serial0”); uart_open
mknod /dev/serial1 c 204 65 ==> open(“/dev/serial1”); uart_open
mknod /dev/serial2 c 204 66 ==> open(“/dev/serial2”); uart_open
mknod /dev/serial3 c 204 67 ==> open(“/dev/serial3”); uart_open

其中open(“/dev/serial0”)為應用程式中的程式碼;
在uart_open中如何確定初始化哪一個串列埠?
通過裝置號來確定初始化哪一個串列埠。
如何拿到裝置號?
在uart_open方法中可以通過傳遞進來的inode->i_rdev拿到裝置號,通過裝置號拿到次裝置號

在urat_write中如何確定通過哪串列埠向外傳送資料?

#include<linux/init.h>
#include<linux/module.h>

static int uart_open(struct inode *inode, struct file *file)
{
   MINOR(inode -> i_rdev);//次裝置號
   //private_data這塊空間留給驅動開發人員使用的,放什麼資料都可以
   file->private_data = MINOR(inode -> i_rdev);//記錄要操作的裝置
   //初始化相應暫存器
   return 0; 
}

static int uart_close(struct inode *inode, struct file *file)
{
   printlk("enter uart_close! \n");
   return 0; 
}

static ssize_t uart_write (struct file *file, const char __user *buf, size_t count, loff_t *offset)
{

   return 0; 
}

static ssize_t uart_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
   return 0; 
}

static int uart_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
    printlk("enter led_ioctl! \n");
}

//需要實現的函式
static struct file_operations led_fops = {
    .owner   = THIS_MOUDLE,
    .open    = uart_open,
    .release = uart_close,
    .write   = uart_write,
    .read    = uart_read,
    .ioctl   = uart_ioctl,
};

static int major = 0;
/*1,分配一個cdev */
static struct cdev led_cdev;

static int led_status;//記錄燈的狀態

static int chardevice_init(void){
     dev_t dev;
     if(major != 0){//靜態分配
         dev = MKDEV(major,0);//構建一個裝置號
         //向核心註冊裝置號
         register_chrdev_region(dev,1,"tarena");
     }else{
         //動態申請註冊裝置號 
         alloc_chrdev_region(&dev,1001,"test");
         major = MAJOR(dev);//獲取主裝置號   相當於dev>>20
         unsigned int minor = MINOR(dev);//獲取次裝置號  相當於把高20位清0
         printk("major = %d ! \n",major);
     }

     //2, 初始化cdev 
     cdev_init(&led_cdev,&led_fops);

     //3,把cdev新增到核心裡面去
     cdev_add(&led_cdev,dev,1);

     //申請GPIO管腳
     gpio_request(S5PV210_GPC1(3),"LED1");//從原圖中找管腳名稱GPC1_3
     //配置為輸出
     gpio_dirction_output(S5PV210_GPC1(3),0);
     led_status = 0;

     return 0;
}

static void chardevice_exit(void){
    dev_t dev;

    //4,按照對核心產生的影響,逆序銷燬cdev
    cdev_del(&led_cdev);

    dev = MKDEV(major,0);
    //釋放
    unregist_chrdev_region(dev,1);
}

module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");

自動建立裝置檔案
手工建立裝置檔案:mknode /dev/xxx c major minor
自動建立:在安裝模組時自動建立
程式設計的角度,需要呼叫以下兩個函式:
class_create(….);//生成一個樹枝
deivce_create(…)//生成一個果實

class_destroy(...);
device_destroy(...);

其他要完成的工作
1,用busybox新增,要選擇支援medv
2,rootfs/ect/rcS新增mount -a
3,rootfs/etc新增fstab檔案
4, 在fstab中新增sysfs和proc支援
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
5,在rootfs/ect/rcS中新增
echo /sbin/mdev > / proc/sys/kernel/hotplub

當執行insmod xx.ko時,如果xx_init函式中包含了class_create(THIS_MODULE,”tanena”),生成資料夾:/sys/class/tarena;當執行device_create(cls,NULL,dev,NULL,”leds”),會生成資料夾:/sys/class/tarena/leds;以上操作核心也理解為熱插拔事件,產生熱插拔事件以後,核心會呼叫/proc/sys/kernel/hotplug,實則就是呼叫/sbin/mdev程式,該程式掃描/sys/目錄下的變化,根據變化去自動在/dev目錄下建立leds檔案;

procfs基於記憶體的檔案系統,匯出核心的執行資訊;如:cat /proc/cpuinfo
sysfs基於記憶體的檔案系統,匯出系統中硬體驅動的組織結構;

案例:

#include<linux/init.h>
#include<linux/module.h>

#include<linux/cdev.h>
#include<linux/kernel.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<plat/gpio-cfg.h>
#include<asm/gpio.h>

static struce class *cls;//記錄樹枝 (其實屬於一個裝置類)

static int led_open(struct inode *inode, struct file *file)
{
   printlk("enter led_open! \n");
   return 0; 
}

static int led_close(struct inode *inode, struct file *file)
{
   printlk("enter led_close! \n");
   return 0; 
}

//假如在使用者空間寫入:write(fd,buf,1) buf中的值1亮  0 滅
static ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
   int cmd;
   copy_from_user(&cmd,buf,sizeof(cmd));//將使用者空間的資料拷貝到核心空間
   if(cmd == 1){
       gpio_set_value(S5PV210_GPC1(3),1);//點亮
       led_status = 1;
   }else{
       gpio_set_value(S5PV210_GPC1(3),-);
       led_status = 0;
   }
   return count; 
}

//read(fd,buf,cnt)
static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
   //把燈的狀態返回給使用者空間
   copy_to_user(buf,&led_status,sizeof(led_status));
   return count; 
}

static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
    printlk("enter led_ioctl! \n");
}

//需要實現的函式
static struct file_operations led_fops = {
    .owner   = THIS_MOUDLE,
    .open    = led_open,
    .release = led_close,
    .write   = led_write,
    .read    = led_read,
    .ioctl   = led_ioctl,
};

static int major = 0;
/*1,分配一個cdev */
static struct cdev led_cdev;

static int led_status;//記錄燈的狀態

static int chardevice_init(void){
     dev_t dev;
     if(major != 0){//靜態分配
         dev = MKDEV(major,0);//構建一個裝置號
         //向核心註冊裝置號
         register_chrdev_region(dev,1,"tarena");
     }else{
         //動態申請註冊裝置號 
         alloc_chrdev_region(&dev,1001,"test");
         major = MAJOR(dev);//獲取主裝置號   相當於dev>>20
         unsigned int minor = MINOR(dev);//獲取次裝置號  相當於把高20位清0
         printk("major = %d ! \n",major);
     }

     //2, 初始化cdev 
     cdev_init(&led_cdev,&led_fops);

     //3,把cdev新增到核心裡面去
     cdev_add(&led_cdev,dev,1);

     //自動建立裝置節點檔案
     cls =  class_create(THIS_MODULE,"tarena");//見到owner就傳THIS_MODULE
     device_create(cls,NULL,dev,NULL,"leds");//這個裝置要掛到哪個枝上(cls),父裝置為NULL, 

     //申請GPIO管腳
     gpio_request(S5PV210_GPC1(3),"LED1");//從原圖中找管腳名稱GPC1_3
     //配置為輸出
     gpio_dirction_output(S5PV210_GPC1(3),0);
     led_status = 0;

     return 0;
}

static void chardevice_exit(void){
    dev_t dev;
    gpio_free(S5pV210_GCP(3));

    dev = MKDEV(major,0);

     //自動刪除裝置節點,注意要逆序銷燬
     device_destory(cls,dev);//相當於從這個樹枝(cls)把水果摘掉(dev)
     class_destory(cls);//砍樹枝

    //4,按照對核心產生的影響,逆序銷燬cdev
    cdev_del(&led_cdev);

    //釋放
    unregist_chrdev_region(dev,1);
}

module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");

然後Makefile檔案,將ko檔案拷貝到開發板,然後在開發板中執行。。。

LED標準程式的寫法:
關於uart使用者空間程式設計
串列埠初始化 xxx_init 或 open
通過串列埠傳送資料 write函式
通過串列埠接收資料 read函式
修改傳輸的速率 有效位個數 校驗方式 ioctl

led_open(){
gpio_reqiest(….);
gpio_direction_output(…);
}

led_close(){
gpio_free(…);
}

led_ioctl(inode,filep,cmd,val){

if(cmd == 1){//亮
   if(val == 1){//led1_on 第一個燈亮
   }else if(val == 2 ){//led2_on 第二個燈亮
   }
}else if(cmd == 0){

}

}

led_read(){//來獲取燈的亮滅狀態
int led_status
}

init(){
1,動態申請裝置號
2,建立並初始化cdev
3,向核心新增cdev
4,自動建立裝置節點檔案
}

exit(){
逆序消除init函式中對核心的影響
}

案例:

#include<linux/init.h>
#include<linux/module.h>

#include<linux/cdev.h>
#include<linux/kernel.h>
#include<linux/device.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<plat/gpio-cfg.h>
#include<asm/gpio.h>

#define LED_ON     0x100001;
#define LED_OFF    0x100002;
#define LED_STATUS 0x100003;

static struce class *cls;//記錄樹枝 (其實屬於一個裝置類)

static int led_status;//記錄燈的狀態

static int led_open(struct inode *inode, struct file *file)
{
   //6,申請GPIO管腳
   gpio_request(S5PV210_GPC1(3),"LED1");
   gpio_request(S5PV210_GPC1(4),"LED2");

   //7,配置為輸出
   gpio_direction_output(S5PV210_GPC1(3),0);
   gpio_direction_output(S5PV210_GPC1(4),0);
   led_status = 0;
   printlk("enter led_open! \n");
   return 0; 
}

static int led_close(struct inode *inode, struct file *file)
{
   //8,釋放GPIO管腳
   gpio_free(S5PV210_GPC1(3));
   gpio_free(S5PV210_GPC1(4));

   printlk("enter led_close! \n");
   return 0; 
}

//read(fd,buf,cnt)
static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset)
{
   int ret;
   //把燈的狀態返回給使用者空間
   ret = copy_to_user(buf,&led_status,sizeof(led_status));
   return count; 
}

/**
*使用者空間呼叫ioctl(fd,cmd,&val);
*產生軟中斷,呼叫sys_ioctl
* sys_ioctl(...){
*   led_ioctl(inode,file,cmd,val);
* }
***/

static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val)
{
    int index = 0;
    int ret = 0;
    int status = 0;

    //因為不能直接操作使用者空間的地址,
    //所以把使用者空間的4個位元組的資料拷貝到index裡面,這就得到了具體操作了哪個燈
    ret = copy_from_user(&index,(int *)val,4);

    if(index > 1 || index <0 ){//只能為0 1,其他的非法
        return -EINVAL;//返回非法引數
    }
    switch(cmd){
        case: LED_ON:
             gpio_set_value(S5PV210_GPC(3)+index,1);
             led_status |= (0x01<<index);
             break;
        case: LED_OFF:
             gpio_set_value(S5PV210_GPC(3)+index,0);
             led_status &= ~(0x01<<index);
             break;
        case: LED_STATUS:
            // printk("status = %d \n",status);
             if(led_status & (0x01<<index)){
                status = 1;
             }else{
                status = 0;
             }
            // printk("status = %d \n",status);
             ret = copy_to_user((int*)val,&status,4);
             break;
        default:
             return -EINVAL;
    }
    return 0;
}

//需要實現的函式
static struct file_operations led_fops = {
    .owner   = THIS_MOUDLE,
    .open    = led_open,
    .release = led_close,
    //.write   = led_write,
    .read    = led_read,
    .ioctl   = led_ioctl,
};

static int major = 0;
/*2,分配一個cdev */
static struct cdev led_cdev;

static int chardevice_init(void){
     dev_t dev;
     //1,動態申請註冊裝置號 
     alloc_chrdev_region(&dev,1001,"test");
     major = MAJOR(dev);//獲取主裝置號   相當於dev>>20
     // unsigned int minor = MINOR(dev);//獲取次裝置號  相當於把高20位清0

     //3, 初始化cdev 
     cdev_init(&led_cdev,&led_fops);

     //4,把cdev新增到核心裡面去
     cdev_add(&led_cdev,dev,1);

     //5,自動建立裝置節點檔案
     cls =  class_create(THIS_MODULE,"tarena");//見到owner就傳THIS_MODULE
     //"leds" 就是/dev要生成的裝置檔案的名字
     device_create(cls,NULL,dev,NULL,"leds");//這個裝置要掛到哪個枝上(cls),父裝置為NULL

     return 0;
}

static void chardevice_exit(void){

    dev_t dev;
    dev = MKDEV(major,100);

     //自動刪除裝置節點,注意要逆序銷燬
    device_destory(cls,dev);//相當於從這個樹枝(cls)把水果摘掉(dev)
    class_destory(cls);//砍樹枝

    //10,從核心中刪除led_cdev  按照對核心產生的影響,逆序銷燬cdev
    cdev_del(&led_cdev);

    //登出裝置號
    unregist_chrdev_region(dev,1);
}

module_init(chardevice_init);
module_exit(chardevice_exit);
MODULE_LICENSE("GPL");

對應的測試程式:

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

#define LED_ON     0x100001;
#define LED_OFF    0x100002;
#define LED_STATUS 0x100003;

int main(void){
   int fd;
   int cmd = 0;
   int val = ;
   fd = open("/dev/leds",o_RDWR);
   if(fd<0){
      printlf("open /dev/leds failed");
      return -1;
   }

   //LED2 on
   cmd = LED_ON;
   val = 1;//代表第二個燈
   ioctl(fd,cmd,&val);//cmd對應驅動程式ioctl方法中的cmd,val對應驅動程式的val
   cmd = LED_STATUS;
   ioctl(fd,cmd,&val);//cmd對應驅動程式ioctl方法中的cmd,val對應驅動程式的val
   if(val = 1){
       printf("LED2 ON \n");
   }else if(val = 0){
       printf("LED2 OFF \n");
   }
   sleep(5);

   //LED1 OFF
   cmd = LED_OFF;
   val = 1;
   ioctl(fd,cmd,&val);
   cmd = LED_STATUS;
   ioctl(fd,cmd,&val);
   if(val = 0){
      printf("LED2 OFF\n");
   }else{
      printf("LED2 ON\n");
   }
   return 0;
}

作業:計算S5PV210_GPC1(3)的值是多少並驗證(列印)