最近开始,重新回顾原来的驱动入门资料了。
还是那些特别的结构体。想做个简单的总结一起
目录
一、字符设备驱动涉及的
1.设备号类型dev_t
2.字符设备的结构cdev
3.字符设备相关函数中涉及的参数
3.1
3.2
3.3struct file_operations 里函数参数
二、其他基础的使用
1.驱动符号的导出
2.linux驱动模块的参数
一、字符设备驱动涉及的
1.设备号类型dev_t
在linux内核里用类型”dev_t”来表示一个设备号. 其实就是一个unsigned int.
dev_t类型有32位数, 其中高12位用于存放主设备号,低20位用于存放次设备号. 在”include/linux/kdev_t.h”里有提供设备号的操作宏:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
#include <linux/fs.h>
//静态:申请指定的设备号, from指设备号(需已指定主设备和次设备号), count指使用该驱动有多少个设备(次设备号), name设备名(用于查看用, 长度不能超过64字节 )
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//动态申请设备号, 由内核分配没有使用的主设备号, 分配好的设备号存在dev(不需初始化), baseminor指次设备号从多少开始, count指设备个数, name设备名
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
//释放设备号, from指设备号, count指设备数
void unregister_chrdev_region(dev_t from, unsigned count)
2.字符设备的结构cdev
? ”struct cdev”类型的一个对象来描述一个字符设备驱动
#include <linux/cdev.h>
struct cdev {
struct kobject kobj; //内核用于管理字符设备驱动, kobject就是内核里最底层的类. 内核里会自动管理此成员.
struct module *owner; //通常设为THIS_MODULE, 用于防止驱动在使用中时卸载驱动模块
const struct file_operations *ops; //怎样操作(vfs), 也就是实现当用户进程进行open/read/write等操作时,驱动里对应的操作.
struct list_head list; //内核链表节点,内核里自动管理此成员.
dev_t dev; //设备号
unsigned int count; //设备数
};
3.字符设备相关函数中涉及的参数
3.1 struct?inode
在linux内核,用一个inode节点对象描述一个要操作的文件/设备文件, 包括权限,设备号等信息. 就是描述一个要操作的文件的属性. 一个文件可以打开很多次, 但都是共用一个inode对象来描述属性的. 文件描述符属于一个进程的资源,不同进程里有可能相同的文件描述符.
struct inode {
...
dev_t i_rdev; //设备文件对应的设备号, 驱动里即可通过区分次设备号来区别不同的具体硬件
struct cdev *i_cdev; //指向对应的字符设备驱动cdev对象的地址.
...
};
3.2?struct file
在用户进程里用一个int类型来表示文件描述符.但文件描述符里有还存有对文件位置的偏移,打开标志等信息, 用一个int数无法记录下来的,所在每个文件描述符的信息都是由内核里用file对象描述文件描述符, 在文件打开时创建, 关闭时销毁
struct file {
...
struct path f_path;
const struct file_operations *f_op; //对应的文件操作对象的地址
unsigned int f_flags; //文件打开的标志
fmode_t f_mode; //权限
loff_t f_pos; //文件描述符的偏移
struct fown_struct f_owner; //属于哪个进程
unsigned int f_uid, f_gid;
void *private_data; //给驱动程序员使用
...
};
如果打开设备文件,那么得到的file对象:
file对象里的成员f_path.dentry->d_inode->i_rdev可以获取到设备文件的设备号
file对象里的成员f_path.dentry->d_inode可以获取到设备文件的inode对象的地址
3.3struct file_operations 里函数参数
//inode表示应用程序打开的文件的节点对象, file表示打开文件获取到的文件描述符
int (*open) (struct inode *, struct file *);
//返回值0表示成功打开,负数表示打开失败。内核根据open函数的返回值来确定是否给调用的用户进程分配文件描述符。
//在驱动可以不实现此函数, 如不实现。则表示每次打开都是成功的.
//buf指向用户进程里的缓冲区, len表示buf的大小(由用户调用read时传进来的)
//off表示fl文件描述符的操作偏移, 返回值为实际给用户的数据字节数.
ssize_t (*read) (struct file *fl, char __user *buf, size_t len, loff_t *off);
//注意,必须通过off指针来改变文件描述符的偏移(*off += 操作字节数). 不可以直接通过"fl->f_pos"来设置
//用户进程把数据给驱动, 也就是让驱动存放用户进程传进来的数据.
// 参考read函数
ssize_t (*write) (struct file *, const char __user *buf, size_t len, loff_t *off);
// cmd表示用户进程调用ioctl时的第二个参数, arg表示第三个参数(可选)
long (*unlocked_ioctl) (struct file *fl, unsigned int cmd, unsigned long arg);
// 返回值为0表示ioctl成功, 返回负数表示失败.
// app: lseek(fd, 54, SEEK_SET)
loff_t (*llseek) (struct file *fl, loff_t offset, int whence);
二、其他基础的使用
1.驱动符号的导出
在一个驱动模块中定义了下面全局的函数,可以在其他的驱动模块中使用,类似C中的全局函数extren 关键字的使用
//在驱动模块1中进行声明和定义
void myfunc(char *str)
{
printk("in myfunc: %s\n", str);
}
EXPORT_SYMBOL(myfunc);
//可以在驱动模块2或者其他的驱动中,使用
extern void myfunc(char *);
static int __init test_init(void)
{
myfunc("test init");
return 0;
}
2.linux驱动模块的参数
模块参数是用于在加载驱动模块时,指定模块里面的特定变量的具体值.
1). #include <linux/moduleparam.h>
2). 在驱动源码里定义变量
static int num = 0; //当加载模块不指定num的值时则为0
3). 声明指定的变量为驱动模块参数
module_param(变量名, 类型, 权限);
//类型可有: byte, int, uint, short, ushort, long, ulong, bool, charp
//权限: 其它用户所占的权限里不能有写的权限
4). 加载驱动模块时指定模块参数的具体值:
insmod test.ko 变量名1=值1 变量名2=值2
eg:
#include <linux/init.h>
#include <linux/module.h>
#include <mach/gpio.h> // 芯片io口的宏定义
#include <linux/gpio.h> // io口的调用函数
#include <linux/moduleparam.h>
static int on = 1;
module_param(on, int, 0644); //声明模块参数
#define LED_GPIO GPIOA(15) //PA15
static int __init test_init(void)
{
int ret;
ret = gpio_request(LED_GPIO, "myled"); //如请求失败,则表示此io口已被其它驱动使用
if (ret < 0)
return ret;
gpio_direction_output(LED_GPIO, on); //根据外面的参数值来决定led灯的亮灭
return 0;
}
static void __exit test_exit(void)
{
gpio_set_value(LED_GPIO, 0);
gpio_free(LED_GPIO);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
加载驱动模块时,通过下面的指令完成,传值
insmod test.ko on=1 或者 insmod test.ko on=0
|