Linux驱动开发-3、字符设备控制技术

字符设备控制技术

1.在用户空间,使用ioctl系统调用来控制设备,原型如下:

a) int ioctl(int fd,unsigned long cmd,...)
fd: 要控制的设备文件描述符
cmd: 发送给设备的控制命令
…: 第3个参数是可选的参数,存在与否是依赖于控制命令(2 个参数 )

2.LINUX2.6.36之后的

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

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

c) 参数说明

i. fiel:打开的设备描述符

ii. cmd:传递的命令

iii. arg:命令的个数

d) 注意:

i. 为了防止对错误设备使用正确的命令,命令号应在系统范围内是唯一的

ii. 既每个命令号都应该由多个位段组成

iii. 定义号码的方法使用四个位段

3.位段详解

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. 如果该命令有数据传输。则它定义数据传输方向

1. _IOC_NONE:没有传输方向

2. _IOC_READ:

3. _IOC_WRITE:

4. _IOC_READ|_IOC_WRITE:双向传输

e) size(尺寸)

i. 涉及数据的大小

ii. 如果想要驱动可移植,则只能认为最大尺寸可达255字节

iii. 如果驱动程序需要更大的尺度的数据传输,则可忽略这个字段

4.构造命令号码的宏

a) _IO(type,nr):定义一个没有数据传输的命令号编号,

i. type:幻数

ii. nr:命令编号

b) _IOR(type,nr,size):定义一个读数据的命令编号;从设备读取参数

i. type:幻数

ii. nr:命令编号

iii. size:数据大小

c) _IOW(type,nr,size):定义一个写数据的命令编号;向设备写入参数

i. type:幻数

ii. nr:命令编号

iii. size:数据大小


d) IOWR(type,nr,size):定义一个可读可写的命名编号

 

e) _IOC_DIR(nr):获得命令编号中的数据传输方向

f) _IOC_TYPE(nr):获取命令编号中的幻数

g) _IOC_NR(nr):获取命令编号中的号码

h) _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) 驱动程序在相应的文件中可以使用这些数据

i. 代码架构:

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;

}

}

d) 应用程序测试

i. 代码架构

#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);

}

你可能感兴趣的:(linux,模块化,嵌入式,TQ210,Linux驱动开发)