字符設備控制技術
筆記要做的自己看起來舒服和有頭緒,這不又折騰切換編輯器來從新排版,有強迫症啊!對於字符控制,很多時候編寫上層應用程序時,使用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);
}