1.字符设备驱动框架
struct cdev {//代表硬件上的一个字符设备
dev_t dev;//设备号
struct file_operations f_ops;//操作函数集合
....
}
关于设备号的分配方式
静态分配
register_chrdev_region
动态分配
alloc_chrdev_region
关于cdev的操作
cdev_init
cdev_add
cdev_del
struct file_operations{
open
read
write
release
}
struct inode{
dev_t i_rdev;//记录设备号
struct cdev *i_cdev;//关于cdev结构
}
struct file{
void *private_data;
}
fd0=open("/dev/cdd0") struct file
fd1=open("/dev/cdd1") struct file
read(fd0,..) -> cdd_read(struct file* filp,..)
read(fd1,..) -> cdd_read(struct file* filp,..)
------------------------------------------------------------------
用户空间和内核空间数据交换的问题
copy_to_user
copy_from_user
day04代码多次读返回同样的内容
添加 llseek 函数
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#inlcude<linux/device.h>
#include<asm/uaccess.h>//用户空间拷贝头文件
//动态申请的头文件
#include<linux/slab.h>
#include<liunx/string.h>
MODULE_LICENSE("GPL");
#define CDD_MAJOR 200
#define CDD_MINOR 0
#define CDD_COUNT 10
dev_t dev=0;
u32 cdd_major=0;
u32 cdd_minor=0;
/*定义cdev类型的变量*/
struct cdev cdd_cdev;
struct class *dev_class=NULL;
//struct device *dev_device=NULL;
#define BUF_SIZE 100
struct cdd_cdev{
struct cdev cdev;
struct device *dev_device;
u8 led;
char kbuf[BUF_SIZE];
u32 data_len;//记录缓冲区已写入数据的长度
}
struct cdd_cdev *cdd_cdevp=NULL;
int cdd_open(struct inode *inode,struct file *filp){
/*2*/
printk("enter cdd_open\n");
struct cdd_cdev* pcdevp=NULL;
printk("enter cdd_open\n");
pcdevp = container_of(inode->i_cdev,struct cdd_cdev,cdev);
printk("led=%d\n",pcdevp->led);
filp->private_data = pcdevp;
return 0;
}
int cdd_read(struct file* filp,char __user *buf,size_t count,
loff_t *offset){
int ret=0;
u32 pos=*offset;
u32 cnt=count;
struct cdd_cdev* pcdevp = filp->private_data;
printk("LED%d\n",pcdevp->led);
//copy_to_user(to,from,n);
//先判断下有没有数据可以进行读取
if(cnt>(pcdevp->data_len/*先有数据*/ - pos/**/)){
cnt=pcdevp->data_len-pos;
}
ret=copy_to_user(buf,pcdevp->kbuf+pos,cnt);
*offset += cnt;
return ret;
}
int cdd_write(struct file *filp,const char __user *buf,
size_t count,loff_t *offset){
printk("enter cdd_write!\n");
int ret=0;
u32 pos=*offset;
u32 cnt =count;
if(cnt > (BUF_SIZE - pos)){
cnt = BUF_SIZE - pos;
}
copy_from_user(pcdevp->kbuf+pos,buf,cnt);
*offset += cnt;
if(*offset > pcdevp->data_len){
pcdevp->data_len = *offset
}
return ret;
}
int cdd_ioctl(struct inode *inode,struct file *filp,
unsigned int cmd ,unsigned data){
printk("enter cdd_ioctl!\n");
return 0;
}
int cdd_release(struct inode *inode,struct file *filp){
printk("enter cdd_release!\n");
return 0;
}
loff_t cdd_llseek(struct file *filp,loff_t offset,int whence){
struct cdd_cdev *pcdevp=filp->private_data;
loff_t newpos=0l
switch(whence){
case SEEK_SET:
newpos=offset;
break;
case SEEK_CUR:
newpos=filp->fops+offset;
break;
case SEEK_END:
newpos=pcdevp->data_len+offset;
break;
default:
return -EINVAL;
}
if(newpos<0 || newpos>=BUF_SIZE){
return -EINVAL;
}
filp->f_ops=newpos;
return newpos;
}
struct file_operations cdd_fops={
.owner = THIS_MODULE,
.open=cdd_open,
.read=cdd_read,
.write=cdd_write,
.ioctl=cdd_ioctl,
.release=cdd_release,
.llseek=cdd_llseek,
};
int __init cdd_init(void){
int ret =0;
int i;
if(cdd_major){
//静态方式分配设备号
dev=MKDEV(CDD_MAJOR,CDD_MINOR);
ret =register_chrdev_region(dev,CDD_COUNT,cdd_demo);
}else{
/*动态分配设备号*/
ret =all_chrdev_region(&dev,cdd_minor,CDD_COUNT,cdd_demo);
}
if(ret<0){
prink("register_chrdev_region failed!\n");
goto failure_register_chrdev;
}
/*获取主设备号*/
cdd_major=MAJOR(dev);
/*1*/
cdd_cdevp=kzalloc(sizeof(struct cdd_cdev)*CDD_COUNT,GFP_KERNEL);
if(IS_ERR(cdd_cdevp)){
printk("kzalloc fail");
goto failure_kzalloc;
}
/*创建设备类*/
dev_class = class_create(THIS_MODULE,"cdd_class");
if(IS_ERR(dev_class)){
printk("class_create failed!\n");
ret=PTR_ERR(dev_class);
goto failure_dev_class;
}
for(i=0;i<CDD_COUNT;i++){
/*初始化cdev*/
cdev_init(&(cdd_cdevp[i].cdev),&cdd_fops);
/*添加cdev到内核*/
cdev_add(&(cdd_cdevp[i].cdev),dev+i,1);
/*"/dev/xxx"*/
device_create(dev_class,NULL,dev+i,NULL,"cdd%d",i);
cdd_cdevp[i].led=i;
}
return 0;
failure_dev_class:
kfree(cdd_cdevp);
failure_kzalloc:
unregister_chrdev_region(dev,CDD_COUNT);
failure_register_chrdev:
return ret;
}
void __exit cdd_exit(void){
int i=0;
for(i=0;i<CDD_COUNT;i++){
device_destroy(dev_class,dev+i);
cdev_del(*(cdd_cdevp[i].cdev));
}
/*从内核中删除设备类*/
class_destroy(dev_class);
kfree(cdd_cdevp);
/*注销设备号*/
unregister_chrdev_region(dev,CDD_COUNT);
}
module_init(cdd_init);
moudle_exit(cdd_exit);
控制LED
看原理图,如果想让LED1亮就是在GPC1_3上输出高电平反之输出低电平
编程使CPU的GPC1_3管脚为输出管脚
控制该管教输出高电平或者低电平
cpu芯片手册:寄存器
GPC1CON
GPC1DAT
GPC1PUD
在linux下控制相应的GPIO管脚,有两种方式
1)直接操作相应的寄存器
2)通过内核提供的GPIO操作库函数
在用户空间采用两种方式控制LED的亮/灭
1) /dev/led0 /dev/led1
cdev0 cdev1
fd0=open("dev/led0",O_RDWR);
ioctl(fd,cmd,data)
例:ioctl(fd1,1,0)//亮 ioctl(fd1,0,0)//灭
2)/dev/led
cdev
fd0=open("dev/led0",O_RDWR);
ioctl(fd,cmd,data)
例:ioctl(fd1,1,1)//第一个亮 ioctl(fd1,0,2)//第二个灯亮
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#inlcude<linux/device.h>
#include<asm/uaccess.h>//用户空间拷贝头文件
//动态申请的头文件
#include<linux/slab.h>
#include<liunx/string.h>
//GPIO
#include<asm/gpio.h>
#include<plat/gpio-cfg,h>
MODULE_LICENSE("GPL");
#define CDD_MAJOR 200
#define CDD_MINOR 0
#define CDD_COUNT 1
dev_t dev=0;
u32 cdd_major=0;
u32 cdd_minor=0;
/*定义cdev类型的变量*/
struct cdev cdd_cdev;
struct class *dev_class=NULL;
//struct device *dev_device=NULL;
#define BUF_SIZE 100
struct cdd_cdev{
struct cdev cdev;
struct device *dev_device;
u8 led;
char kbuf[BUF_SIZE];
u32 data_len;//记录缓冲区已写入数据的长度
}
struct cdd_cdev *cdd_cdevp=NULL;
unsigned long led_gpio_table[2]={
S5PV210_GPC1(3),//宏->数字
S5PV210_GPC1(4)
};
int cdd_open(struct inode *inode,struct file *filp){
/*2*/
printk("enter cdd_open\n");
struct cdd_cdev* pcdevp=NULL;
printk("enter cdd_open\n");
pcdevp = container_of(inode->i_cdev,struct cdd_cdev,cdev);
printk("led=%d\n",pcdevp->led);
filp->private_data = pcdevp;
return 0;
}
int cdd_read(struct file* filp,char __user *buf,size_t count,
loff_t *offset){
int ret=0;
u32 pos=*offset;
u32 cnt=count;
struct cdd_cdev* pcdevp = filp->private_data;
printk("LED%d\n",pcdevp->led);
//copy_to_user(to,from,n);
//先判断下有没有数据可以进行读取
if(cnt>(pcdevp->data_len/*先有数据*/ - pos/**/)){
cnt=pcdevp->data_len-pos;
}
ret=copy_to_user(buf,pcdevp->kbuf+pos,cnt);
*offset += cnt;
return ret;
}
int cdd_write(struct file *filp,const char __user *buf,
size_t count,loff_t *offset){
printk("enter cdd_write!\n");
int ret=0;
u32 pos=*offset;
u32 cnt =count;
if(cnt > (BUF_SIZE - pos)){
cnt = BUF_SIZE - pos;
}
copy_from_user(pcdevp->kbuf+pos,buf,cnt);
*offset += cnt;
if(*offset > pcdevp->data_len){
pcdevp->data_len = *offset
}
return ret;
}
int cdd_ioctl(struct inode *inode,struct file *filp,
unsigned int cmd ,unsigned data){
printk("enter cdd_ioctl!\n");
switch(cmd){
case 1:/*点亮灯*/
/*
*gpio_direction_output(int gpio,int v);
*设置管脚为输出功能
*gpio要设置的管教编号
*v 默认的高低电平 1高 0低
*/
gpio_direction_output(led_gpio_table[data],0);
/*禁止内部上拉*/
s3c_gpio_setpull(led_gpio_table[data],S3C_GPIO_PULL_UP);
/*设置输出值*/
gpio_set_value(led_gpio_table[data],1);
break;
case 0:/*熄灭灯*/
/*设置管脚为输出功能*/
gpio_direction_output(led_gpio_table[data],0);
/*禁止内部上拉*/
s3c_gpio_setpull(led_gpio_table[data],S3C_GPIO_PULL_UP);
/*设置输出值*/
gpio_set_value(led_gpio_table[data],0);
break;
default:
return -EINVAL;
}
return 0;
}
int cdd_release(struct inode *inode,struct file *filp){
printk("enter cdd_release!\n");
return 0;
}
loff_t cdd_llseek(struct file *filp,loff_t offset,int whence){
struct cdd_cdev *pcdevp=filp->private_data;
loff_t newpos=0l
switch(whence){
case SEEK_SET:
newpos=offset;
break;
case SEEK_CUR:
newpos=filp->fops+offset;
break;
case SEEK_END:
newpos=pcdevp->data_len+offset;
break;
default:
return -EINVAL;
}
if(newpos<0 || newpos>=BUF_SIZE){
return -EINVAL;
}
filp->f_ops=newpos;
return newpos;
}
struct file_operations cdd_fops={
.owner = THIS_MODULE,
.open=cdd_open,
.read=cdd_read,
.write=cdd_write,
.ioctl=cdd_ioctl,
.release=cdd_release,
.llseek=cdd_llseek,
};
int __init cdd_init(void){
int ret =0;
int i;
if(cdd_major){
//静态方式分配设备号
dev=MKDEV(CDD_MAJOR,CDD_MINOR);
ret =register_chrdev_region(dev,CDD_COUNT,cdd_demo);
}else{
/*动态分配设备号*/
ret =all_chrdev_region(&dev,cdd_minor,CDD_COUNT,cdd_demo);
}
if(ret<0){
prink("register_chrdev_region failed!\n");
goto failure_register_chrdev;
}
/*获取主设备号*/
cdd_major=MAJOR(dev);
/*1*/
cdd_cdevp=kzalloc(sizeof(struct cdd_cdev)*CDD_COUNT,GFP_KERNEL);
if(IS_ERR(cdd_cdevp)){
printk("kzalloc fail");
goto failure_kzalloc;
}
/*创建设备类*/
dev_class = class_create(THIS_MODULE,"cdd_class");
if(IS_ERR(dev_class)){
printk("class_create failed!\n");
ret=PTR_ERR(dev_class);
goto failure_dev_class;
}
for(i=0;i<CDD_COUNT;i++){
/*初始化cdev*/
cdev_init(&(cdd_cdevp[i].cdev),&cdd_fops);
/*添加cdev到内核*/
cdev_add(&(cdd_cdevp[i].cdev),dev+i,1);
/*"/dev/xxx"*/
device_create(dev_class,NULL,dev+i,NULL,"cdd%d",i);
cdd_cdevp[i].led=i;
}
return 0;
failure_dev_class:
kfree(cdd_cdevp);
failure_kzalloc:
unregister_chrdev_region(dev,CDD_COUNT);
failure_register_chrdev:
return ret;
}
void __exit cdd_exit(void){
int i=0;
for(i=0;i<CDD_COUNT;i++){
device_destroy(dev_class,dev+i);
cdev_del(*(cdd_cdevp[i].cdev));
}
/*从内核中删除设备类*/
class_destroy(dev_class);
kfree(cdd_cdevp);
/*注销设备号*/
unregister_chrdev_region(dev,CDD_COUNT);
}
module_init(cdd_init);
moudle_exit(cdd_exit);
应用程序中调用ioctl就可以实现LED的亮灭
/*
!arm :历史命令就是 在shell中调用最近一次的arm开头指令
*/
上述程序会出现一大堆信息,但是还是能够实现亮和灭
多个设备使用LED的时候会发生穿线的现象
led_open中添加
gpio_request(led_gpiotable[0],"GPC1_3");
led_release中添加
gpio_free(led_gpiotable[0])
驱动程序的调试
printk
Oops:
kgdb
内核奔溃前会打印关键信息
pc:[<bf0000b8>] -> 地址
##cat /proc/kallsyms >test.txt
代码崩溃在cdd.c文件的哪个函数中
3G-4G内核地址:模块编译是没有在3-4G中,编译进zImage才是3-4G
arm-linux-objdump -D cdd.ko >test2.txt
arm-linux-objdump -D vmlinux >test3.txt
编译地址