Linux驅動開發-字符設備控制技術筆記 3

字符設備控制技術

  筆記要做的自己看起來舒服和有頭緒,這不又折騰切換編輯器來從新排版,有強迫症啊!對於字符控制,很多時候編寫上層應用程序時,使用ioctl系統調用來控制設備,原型如下:

/*
    fd: 要控制的設備文件描述符
    cmd: 發送給設備的控制命令
    …: 第3個參數是可選的參數,存在與否是依賴於控制命令(第2 個參數)。
*/
int ioctl(int fd,unsigned long cmd,...);

但是在Linux2.6.36之後的

/*
c) 參數說明

     fiel:打開的設備描述符

     cmd:傳遞的命令

     arg:命令的個數
*/
long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);

long (*compat_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);

d) 注意:

  • 爲了防止對錯誤設備使用正確的命令,命令號應在系統範圍內是唯一的
  • 既每個命令號都應該由多個位段組成
  • 定義號碼的方法使用四個位段

位段詳解

a) 頭文件:<linux/ioctl.h>

b) type(類型)

  • i. 幻數(magic number):選擇系統中沒有使用的號碼,並在整個驅動程序中使用這個號碼。

    ii. 8位寬度(_IOC_TYPEBITS)

    iii. 源碼的/Document目錄下magic-num.txt 記錄了當前已經使用的幻數

c) number(號碼)

i. 序數:8位寬度(_IOC_NRBITS)

d) direct(方向)

i. 如果該命令有數據傳輸。則它定義數據傳輸方向

  • _IOC_NONE:沒有傳輸方向

    _IOC_READ:

    _IOC_WRITE:

    _IOC_READ|_IOC_WRITE:雙向傳輸

e) size(尺寸)

  • i. 涉及數據的大小

    ii. 如果想要驅動可移植,則只能認爲最大尺寸可達255字節

    iii. 如果驅動程序需要更大的尺度的數據傳輸,則可忽略這個字段

4.構造命令號碼的宏

/*
 type:幻數
 nr:命令編號
*/
_IO(type,nr); //定義一個沒有數據傳輸的命令號編號

/*
type:幻數
nr:命令編號
size:數據大小
*/
 _IOR(type,nr,size):定義一個讀數據的命令編號;從設備讀取參數

 _IOW(type,nr,size):定義一個寫數據的命令編號;向設備寫入參數
 _IOWR(type,nr,size):定義一個可讀可寫的命名編號
 _IOC_DIR(nr):獲得命令編號中的數據傳輸方向
 _IOC_TYPE(nr):獲取命令編號中的幻數
 _IOC_NR(nr):獲取命令編號中的號碼
 _IOC_SIZE(nr):獲取命令編號中的尺寸

5.例:

a) 新建控制文件:led_ioctl.h;編寫一下代碼

 #define LED_MAGIC ‘L’
 #define LED_ON _IOW(LED_MAGIC,1,int)
 #define LED_OFF _IOW(LED_MAGIC,0,int)

b) 引用頭文件“led_ioctl.h”

c) 驅動程序在相應的文件中可以使用這些數據

代碼架構:

static int xxx_ioctl(struct file* filp,unsigned int cmd,unsignend long arg)
{
    if(_IOC_TYPE(cmd) != xxx_IOC_MAGIC) /*驅動的幻數,我們在頭文件中定義的*/{
        return -ENOTTY;
    }

    if(_IOC_NR(cmd) >= xxx_MAXNR){
    return -ENOTTY;
    }

    switch(cmd)
    {
    case LED_ON:

    處理;

    break;

    case LED_OFF:

    處理;

    break;

    default:
        return -ENOTTY;
    break;
    }

}

應用程序測試

代碼架構:

#include<fcntl.h>

#include<sys/ioctl.h>

#include  “led_ioctl.h”

/*

參數說明:

arg:參數個數(注意,在終端輸入執行命令時已近佔據0號參數,我們自己的 參數從1開始)

argv:參數內容

*/

int main(int arg,unsigned char* argv)

{

    int fd=0;

    int ret=0;

    /*處理終端傳遞過來的參數*/

    atio();//將終端輸入的字符串轉換成int型。

    fd = open(“驅動設備文件”,打開模式);

    判斷是否成功;

    /*更具需要調用讀寫,控制函數,這裏調用ioctl()*/

    ret = ioctl(fd,cmd,...);

    close(fd);

}

LED 控制驅動程序設計

第一步:申明控制號,保存文件爲 led_ioctl.h

#define LED_MAGIC 'L'

#define TURNON_LED _IOW(LED_MAGIC,1,int)

#define TURNOFF_LED _IOW(LED_MAGIC,0,int)

第二步:編寫驅動

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

作者:hntea

時間:2016/3/9

功能:led 字符設備驅動+ioctl

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

#include <linux/init.h>

#include <linux/module.h>

#include <linux/fcntl.h>

#include <linux/kernel.h>

#include <linux/types.h>

#include <linux/cdev.h>

#include <linux/sched.h>

#include <linux/slab.h>

#include <linux/fs.h>

#include <linux/mm.h>

#include <linux/ioctl.h>

#include <asm/io.h>

#include <asm/uaccess.h>

#include "led_ioctl.h"

#define DEV_NAME "led"

#define MAJOR_NR 10

#define MINIR_NR 1

#define LED_ON 0x00000018

#define LED_OFF 0X00000000

#define CON_ADDR 0xE0200060

#define DAT_ADDR 0xE0200064

struct cdev led_dev; /*靜態分配設備號*/

dev_t dev_nm; /*設備號*/


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

函數名: led_hardinit 實現

函數參數:

函數功能:led硬件初始化

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

static void led_hardinit(void)

{

    unsigned int  tmp = 0;

    unsigned int *led_config;



    /*找出物理地址對應的虛擬地址*/

    led_config = ioremap(CON_ADDR,4); /*該寄存器是32位數值*/

    /*讀取當前寄存器狀態*/

    #if 0

    tmp = ioread32(led_config);

    tmp &= (~(0x11 << 3));

    tmp |= (0x11 << 3);

    #endif

    tmp = 0x11000;

    /*寄存器設置寫回*/

    iowrite32(tmp,led_config);

}

static void led_on(void)

{

    unsigned int *led_data;

    led_data = ioremap(DAT_ADDR,4);

    iowrite32(LED_ON,led_data);

    printk("revice cmd:ledon\n");

}

static void led_off(void)

{

    unsigned int *led_data;

    led_data = ioremap(DAT_ADDR,4);

    iowrite32(LED_OFF,led_data);

    printk("revice cmd:ledoff\n");

}



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

函數名: file_operations 實現

函數參數:

函數功能:

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

static int  led_open (struct inode *inode, struct file *fp)

{

int ret = 0;

/*led 打開時只需要初始化相關寄存器就夠*/

led_hardinit();

return ret;

}



static int led_release (struct inode *inode, struct file *fp)

{

return 0;

}



static long led_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)

{

    /*這裏可以使用命令解析宏解析幻數再判斷,使代碼邏輯更嚴謹*/

    /*命令解析*/

    switch(cmd)
    {

        case TURNON_LED:

        led_on();

        break;

        case TURNOFF_LED:

        led_off();

        break;

        default:

        return -ENOTTY;

        break;

    }

return 0;

}

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

函數名: led_write 實現

函數參數:

函數功能:向led設備文件寫如數據

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

static ssize_t led_write(struct file *fp, const char __user *buf, size_t count, loff_t *f_pos)
{

    unsigned char count_tmp = count ; /*獲取寫入字節數*/

    unsigned char led_sta[2]; /*用來存放兩個led的狀態*/

    int i = 0;

    memset(led_sta,0,2); /*數據清零*/

    if( count > 2){
        count_tmp = 2;
    }

    /*將數據從用戶空間複製到內核空間*/

    if(copy_from_user(led_sta,buf,count_tmp)){
    return -EFAULT;}else
    {
        /*控制兩個led的狀態*/
        for(i=0;i<2;i++)
        {
            /*讀取當前led寄存器狀態,嚴謹就要處理,防止數據破話而使系統奔潰*/
            if( led_sta[i] == '1' ){
                led_on();
            }else{
                led_off();
            }
        }

    }

    return 0;

}

struct file_operations ledfp={

    .owner = THIS_MODULE,

    .open = led_open,

    .write = led_write,

    .unlocked_ioctl = led_ioctl,

    .release = led_release,

};

struct cdev cdev={

    .owner = THIS_MODULE,

    .ops = &ledfp,

};



static int ledInit(void)
{

    int ret = 0;

    /*1.分配設備號,0是以該數字作爲主設備號分配的起始,2是次設備號(用來標識設備的個數)*/

    if((ret = alloc_chrdev_region(&dev_nm, 0, 1,DEV_NAME))<0)
    {
        printk("device num alloc err!\n");
    }

    /*2.分配設備結構;靜態分配一個全局變量*/

    printk("led major number is %d\n",MAJOR(dev_nm));

    printk("led minor number is %d\n",MINOR(dev_nm));

    /*3.初始化設備結構*/

     cdev_init(&cdev, &ledfp);

    /*4.註冊設備*/

     if((ret = cdev_add(&cdev,dev_nm, 1)) < 0) /*最後一個參數說明設備數*/

     {

      printk("cdev add err!\n");

     }

     /*5.創建設備文件,現在使用 手工創建mknod*/

    //device_create();

     printk("led.ko insmod success!\n");

    return 0;

}



static void ledexit(void)
{
    /*2.註銷設備號,註銷設備*/
    cdev_del(&cdev);   /*註銷設備*/
    unregister_chrdev_region(dev_nm, 1); /*釋放設備號*/
    printk("led device rmmod success!\n");
}





MODULE_AUTHOR("hntea");

MODULE_LICENSE("GPL");

module_init(ledInit);

module_exit(ledexit);

加載驅動:手動創建 /dev/led 節點文件

mknod /dev/led c 主設備號 次設備號

第三步:編譯測試應用

#include<sys/fcntl.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<sys/ioctl.h>

#include<stdio.h>

#include "led_ioctl.h"

#define DEVICE_FILE "/dev/led" 

int main(int arg,char **argv)

{

    int fd = 0 ;

    int cmd = 0;

    int ret = 0;

    /*解析傳遞進來的參數*/

    if(arg < 2){

    printf("Please input : <cmd>  <0/1>\n");

    return -1;

    }

    /*格式化*/

    cmd = atoi((argv[1]));

    /*設備操作*/

    if((fd  = open(DEVICE_FILE,O_RDWR))< 0){

    printf("File open err!\n");

    }

    switch(cmd)

    {

        case 0:

        ret = ioctl(fd,TURNOFF_LED);

        break;

        case 1:

        ret = ioctl(fd,TURNON_LED);

        break;

        default:

        printf("Command er!\n");

        return -1;

    }

    close(fd);

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章