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,100,1,"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,100,1,"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,100,1,"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)的值是多少並驗證(列印)