天天看點

基于tiny4412開發闆led燈字元裝置ioctl驅動寫法

ioctl接口作用

write:向裝置寫入資料,單獨這個接口并不能滿足現實裝置控制的全部需求。例如一個

LCD

控制器:主要作用是驅動

lcd

屏,要顯示就是通過

write

接口把顯示資料發給

lcd

控制器指定的顯存。而參數設定類參數通過

write

接口設定就可能回和普通的顯示資料弄混了。為了解決這個問題,核心提供一個接口

ioctl

對裝置進行控制(參數設定,參數查詢等功能)。

ioctl

主要實作不太好實作的功能。

ioctl系統調用接口

#include <sys/ioctl.h>
       int ioctl(int d, int request, ...);
           

功能:

給系統通過指令形式,控制硬體裝置,相當于

linux

系統給我們提供了系統擴充功能的一個接口,

read`` write

等固定用法,而

ioctl

可由使用者自定義指令來執行不同代碼。

參數:

d:檔案描述符

request:指令(可以是系統指令,也可以是自定義的)

···:表示變參,相當于

printf

參數一樣,可以有,可以沒有。是否需要和

request

指令有關

示例說明可變參的用法:

1)

0x10

表示開全部燈,

2)

0x20

表示第

N

個燈,

3)

0x30

表示關第

N

個燈,

4)

0x40

表示關全部燈。

關閉、開啟第幾個燈N可以由可變參傳入來決定

傳回值:

>=0:成功,>0具體什麼含義由驅動程式決定

-1:執行失敗

ioctl接口驅動模闆

檔案操作結構體中的定義如下;

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

是以函數原型應該是:

long xxx_unlocked_ioctl (struct file *pfile, unsigned int cmd, unsigned long args);

功能:

對應于系統調用API的ioctl函數

int ioctl(int d, int request, ...);

參數對應關系:

pfile:檔案結構指針,間接對應與使用者空間ioctl函數的fd參數

cmd:直接對應于request

args:對應于可選參數···

傳回值:

>=0:成功,>0具體什麼含義由驅動程式決定

<1:執行失敗 傳回失敗錯誤碼,不一定是

-1

,但是隻要是錯誤碼,在上層一定是

-1

,這裡的錯誤碼提供給核心,比如記憶體不足,傳回

-EFAULT

,所傳回的錯誤碼會被儲存在系統全局變量

errno

中。

EFAULT:args 非法

EINVAL:參數無效

接口測試實驗

寫一個驅動代碼實作unlocked_ioctl接口

設計思路:由于控制程式

ioctl

函數中

request

參數對應驅動程式

cmd

參數,而應用程式通過傳遞不同的值來告訴驅動程式做不同的事情,是以,驅動中

unlocked_ioctl

接口代碼函數内部一定是要判斷

cmd

值執行不同的代碼段,是以就是一個

if

語句或者是一個

switch

語句。完成對

cmd

的判斷。

标準ioctl接口指令的合成

ioctl指令規則

1.系統自定義指令:執行優先級高于使用者自定義指令

2.使用者自定義指令

ioctl指令執行:不直接就執行驅動中的

unlocked_ioctl

接口,而是先根據

cmd

情況是否屬于預定義指令,如果是則去執行,完成後傳回,傳回後可能執行使用者自定義的指令,也可能直接傳回,不執行使用者指令。

避免指令沖突:核心為解決這個問題,定義了一個規則,指令是特定格式組成的,

核心說明文檔:

ioctl-decoding.txt \linux-3.5\Documentation\ioctl

ioctl-number.txt \linux-3.5\Documentation\ioctl

編碼格式:

位段 含義
31-30 指令傳輸方向
00 no parameters: uses _IO macro 沒有參數:使用宏_IO
10 read: _IOR 從核心讀取讀取:使用宏_IOR
01 write: _IOW 向核心寫入:使用宏_IOW
11 read/write: _IOWR 讀/寫:使用宏_IOWR
29-16 size of arguments 使用者和驅動之間有資料傳輸時有效表示資料大小
15-8 ascii character supposedly unique to each driver 給每一個驅動配置設定一個字元區分
7-0 function #同一個驅動中所有cmd指令編号0~255,一般值都是連續的

核心合成宏:

路徑: ioctl.h \linux-3.5\include\asm-generic

合成宏 含義
_IO(type,nr) 定義沒有資料傳遞的指令
_IOR(type,nr,size) 定義從驅動中讀取的指令
_IOW(type,nr,size) 定義向驅動中寫入的指令
_IOWR(type,nr,size) 定義雙向資料傳輸的指令

參數:

type:表示指令組成的魔數,也就是

8~15

nr:表示指令的編号,也就是

0~7

size:

b

表示指令組成參數傳遞的大小,但是這裡傳遞的不是數字,而是資料類型

分解宏 含義
_IOC_DIR(nr) 分解指令的方向
_IOC_TYPE(nr) 分解指令的魔數
_IOC_NR(nr) 分解指令的編号
_IOC_SIZE(nr) 分解指令的大小

參數:

nr:指令

利用ioctl向驅動寫資料

//例如使用者空間有
unsigned char buf[];
ioctl(fd,cmd,(unsigned long)buf);
           
long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//這裡args就是把使用者空間傳下來的buf的位址轉換成數字
//這裡再把數字還原成指針
copy_from_user(kbuf,(void*)args,);
//固定寫4個位元組,這樣寫不太規範實際應該從指令中擷取資料數目
}
           

利用ioctl從驅動讀取資料

long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//這裡args就是把使用者空間傳下來的buf的位址轉換成數字
//這裡再把數字還原成指針
copy_to_user(kbuf,(void*)args,);
}
           

互動資料 先讀取使用者空間 再傳給使用者空間

long unlocked_ioctl(struct file *pfil, unsigned int cmd, unsigned long args)
{
//這裡args就是把使用者空間傳下來的buf的位址轉換成數字
//這裡再把數字還原成指針
copy_from_user(kbuf,(void*)args,);
//固定寫4個位元組,這樣寫不太規範實際應該從指令中擷取資料數目
······
copy_to_user(kbuf,(void*)args,);
//固定寫4個位元組,這樣寫不太規範實際應該從指令中擷取資料數目
}
           

驅動接口函數接口函數示例

long leddriver_ioctl (struct file *pfile, unsigned int cmd, unsigned long args)
{
   unsigned char LEN_NUM=;
   int ret=;
   int nr=;
   switch(cmd)
    {
    case LED_ALL_ON:
        GPM4DAT &= ~(<<);
        break;
    case LED_ALL_OFF:
        GPM4DAT |= (<<);
        break;
    case LED_ON_N:
    case LED_OFF_N:
        ret=copy_from_user(&nr,(void *)args,_IOC_SIZE(cmd));
    //資料拷貝失敗 傳回錯誤碼
    if(ret)
        {
      return -EFAULT;
    }
    //如果燈的标号大于等于4 傳回錯誤碼
    if(nr>= LED_NUM)
        {
       return -EINVAL;
    }
    if(cmd == LED_ON_N)
        {
       GPM4DAT &= ~(<<nr);
    }else{
       GPM4DAT |= (<<nr);
    }
    break;
    default:
        return -EINVAL;
        break;
  }
}
           

完整代碼

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<asm/io.h>
#include<asm/uaccess.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/kdev_t.h>
#include<linux/slab.h>
#include<linux/device.h> //增加自動建立裝置頭檔案
#include<linux/uaccess.h>
#include "iocmd.h"
//定義字元裝置結構體
static struct cdev *leddriver_cdev;
//定義裝置号(包含主次)
static dev_t leddriver_num=;
//定義裝置類
static struct class *leddriver_class;
//定義裝置結構體
static struct device *leddriver_device;
//定義錯誤傳回類型
static int  err;
//定義裝置名稱
#define LEDDRIVER_NAME "myled"

#define GPM4CON_ADDR 0x110002E0 
#define GPM4DAT_ADDR 0X110002E4

static volatile unsigned long *gpm4con=NULL;  
static volatile unsigned long *gpm4dat=NULL;

#define GPM4CON *gpm4con
#define GPM4DAT *gpm4dat

ssize_t leddriver_read(struct file *file, char __user *usr, size_t size, loff_t *loft)
{
    loff_t cur_pos=*loft;//取出目前讀寫位置值
  unsigned char led_statue[],i,LED_NUM=;
  //讀取資料長度為0什麼也不做 傳回0 退出程式的執行
  if(size<=)
    {
    return ;
  }
  //讀取位置在末尾 無論size是多少都不能讀出資料 資料有效區域越界
  if(cur_pos>=LED_NUM)
    {
     return ;
  }
  //判斷size+目前位置是大于檔案大小,隻讀取有效位的内容
  if(cur_pos+size>LED_NUM)
    {
    size=LED_NUM-cur_pos;

  }
  for(i=;i<LED_NUM;i++)
    {
     if(GPM4DAT &(<<i))
     led_statue[i]=;
     else
        led_statue[i]=;
  }
  if(copy_to_user(usr,&led_statue[cur_pos],size)){
     printk("copy to user err\r\n");
     return -EFAULT;
    };
  //指針重新定位目前位置
  *loft+=size;
 return size;
}

ssize_t leddriver_write (struct file *file, const char __user *usr, size_t size, loff_t *loft)
{
  loff_t cur_pos=*loft;
  unsigned char led_statue[],i,LED_NUM=;
  //寫入資料大小為0 什麼也不操作傳回0退出
  if(size<=)
    {
    return ;
  }
  //目前位置大于等于檔案最大有效資料,即使寫入資料也是無效,所依不進行操作 傳回0退出
  if(cur_pos>=LED_NUM)
    {
   return ;
  }
  //如果目前位置加上所要讀取資料的長度大于剩餘有效位   隻讀取有效資料位的數值
  if(size+cur_pos>LED_NUM)
    {
   size=LED_NUM-cur_pos;
  }
  if(copy_from_user(&led_statue[cur_pos],usr,size))
    {
     printk("copy from user err\r\n");
     return -EFAULT;
  }
  for(i=;i<size;i++)
    {
    if(led_statue[i+cur_pos]==)
    GPM4DAT &= ~(<<(i+cur_pos));
    else
    GPM4DAT |= (<<(i+cur_pos));    
  }
  *loft+=size;
  return size;
}

int leddriver_open (struct inode *node, struct file *pfile)
{
  printk("files open is success\r\n");
  return ;
}

loff_t leddriver_llseek(struct file *pfile, loff_t loft, int whence){
    loff_t tmp;
unsigned char LED_NUM=;
switch(whence)
{
case SEEK_SET:
    tmp=loft;
    break;
case SEEK_CUR:
    tmp=pfile->f_pos+loft; //目前位置加上調整值
    break;
case SEEK_END:
    tmp=LED_NUM+loft;
    break;

default:
return -EINVAL;//告訴程式具體錯誤原因  參數無效
break;

}
//檢測最後的結果是否合法
if(tmp< || tmp>LED_NUM)
{
return -EINVAL;
}
//更新檔案調整後的結果到檔案結構體中
pfile->f_pos=tmp;
//傳回調整後的結果
return tmp;

}


int leddriver_release (struct inode *node, struct file *file)
{
  printk("leddriver close is success\r\n");
  return ;
}

long leddriver_ioctl (struct file *pfile, unsigned int cmd, unsigned long args)
{
   unsigned char LEN_NUM=;
   int ret=;
   int nr=;
   switch(cmd)
    {
    case LED_ALL_ON:
        GPM4DAT &= ~(<<);
        break;
    case LED_ALL_OFF:
        GPM4DAT |= (<<);
        break;
    case LED_ON_N:
     case LED_OFF_N:
        ret=copy_from_user(&nr,(void *)args,_IOC_SIZE(cmd));
    //資料拷貝失敗 傳回錯誤碼
    if(ret)
        {
      return -EFAULT;
    }
    //如果燈的标号大于等于4 傳回錯誤碼
    if(nr>= LED_NUM)
        {
       return -EINVAL;
    }
    if(cmd == LED_ON_N)
        {
       GPM4DAT &= ~(<<nr);
    }else{
       GPM4DAT |= (<<nr);
    }
    break;
    default:
        return -EINVAL;
        break;
  }
  return ;
}

//檔案操作函數結構體
static struct file_operations leddriver_fops={
    .owner=THIS_MODULE,
    .open=leddriver_open,
    .release=leddriver_release,
    .read=leddriver_read,
    .write=leddriver_write,
    .llseek=leddriver_llseek,
    unlocked_ioctl=leddriver_ioctl,
};

static __init int ldedriver_init(void)
{
//配置設定字元裝置結構體,前面隻是定義沒有配置設定空間
leddriver_cdev=cdev_alloc();
//判斷配置設定成功與否
if(leddriver_cdev==NULL)
{
  err=-ENOMEM;
  printk("leddriver alloc is err\r\n");
  goto err_leddriver_alloc;
}

//動态配置設定裝置号
err=alloc_chrdev_region(&leddriver_num, , , LEDDRIVER_NAME);
//錯誤判斷
if(err<)
{
  printk("alloc leddriver num is err\r\n");
  goto err_alloc_chrdev_region;
}

//初始化結構體
cdev_init(leddriver_cdev,&leddriver_fops);

//驅動注冊
err=cdev_add(leddriver_cdev,leddriver_num,);
if(err<)
{
  printk("cdev add is err\r\n");
  goto err_cdev_add;
}

//建立裝置類
leddriver_class=class_create(THIS_MODULE,"led_class");
  err=PTR_ERR(leddriver_class);
  if(IS_ERR(leddriver_class))
    {
printk("leddriver creat class is err\r\n");
goto err_class_create;
  }


//建立裝置
  leddriver_device=device_create(leddriver_class,NULL, leddriver_num,NULL, "leddevice");
 err=PTR_ERR(leddriver_device);
    if(IS_ERR(leddriver_device))
        {
printk("leddriver device creat is err \r\n");
goto err_device_create;
    }
//led燈寄存器配置
    gpm4con=ioremap(GPM4CON_ADDR, );
    gpm4dat=ioremap(GPM4DAT_ADDR, );

    GPM4CON &= ~(<<);
    GPM4CON |= (<<);
    GPM4DAT |= (<<);

printk("leddriver init is success\r\n");
return ;

err_device_create:
class_destroy(leddriver_class);
err_class_create:
 cdev_del(leddriver_cdev);
err_cdev_add:
unregister_chrdev_region(leddriver_num, );

err_alloc_chrdev_region:
kfree(leddriver_cdev);

err_leddriver_alloc:
return err;

}

static __exit void leddriver_exit(void)
{
    //取消映射
    iounmap(gpm4con);
    iounmap(gpm4dat);

  device_destroy(leddriver_class,leddriver_num);
  class_destroy(leddriver_class);
  cdev_del(leddriver_cdev);
  unregister_chrdev_region(leddriver_num, );
  printk("leddriver is exit\r\n");
}


module_init(ldedriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
           

app函數

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "iocmd.h"
int main(int argc,char *argv[])
{
  int led_fd,i;
  led_fd = open(argv[],O_RDWR);
  while()
    {
   ioctl(led_fd,LED_ALL_ON);
   sleep();
    for(i=;i<;i++)
    {
      ioctl(led_fd,LED_OFF_N,i);
      sleep();
    }
    for(i=;i<;i++)
    {
      ioctl(led_fd,LED_ON_N,i);
      sleep();
    }
    ioctl(led_fd,LED_ALL_OFF);
    sleep();
  }
    sleep();
  close(led_fp);
}
           

公共頭檔案

#ifndef _IOCMD_H_
#define _IOCMD_H_

#define LED_ALL_ON _IO('L',0)
#define LED_ALL_OFF _IO('L',1)
#define LED_ON_N _IOW('L',2,int)
#define LED_OFF_N _IOW('L',3,int)

#endif //_IOCMD_H_
           

Makefile

KERN_DIR = /zhangchao/linux3./linux-
all:
    make -C $(KERN_DIR) M=`pwd` modules
cp:
    cp ./* /zhangchao/rootfs/zhangchao
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order
obj-m += led.o
           

繼續閱讀