内存: 1GB 存储: 4GB
详细参数 https://m.tb.cn/h.3wMaSKm
开发板交流群 641395230原理图:
参考资料:
寄存器位置
一些概念:
字符设备驱动框架
必须有一个设备号,用在众多到设备驱动中进行区分,
用户必须知道设备驱动对应到设备节点(设备文件),linux把所有到设备都看成文件
对设备操作其实就是对文件操作,应用空间操作open,read,write的时候实际在驱动代码有对应到open, read,write
字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
Linux 2.6 标准字符设备核心结构
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; /* 设备文件操作方法 */
struct list_head list;
dev_t dev; /* 32 位设备号:包含主和次 */
unsigned int count; /*占用多少个连续的次设备号 */
};
Linux2.6版本以上内核,使用dev_t来表示一个设备号,dev_t实际上是一个u32类型。其中高12位是主设备号,低20位是次设备号。
主设备号:dev_t 高 12 位, 2^12=4K (10 是给杂项设备使用)范围 0 ~ 4095
次设备号: dev_t 低 20 位, 2^20=1M 范围 0 ~ 1M-1
合成设备号:
MKDEV(A,B):A: 主设备号; B: 次设备号
分解设备号:
MAJOR(dev) : 从设备号 dev 中分解出主设备号
MINOR(dev) : 从设备号 dev 中分解出次设备号
alloc_chrdev_region函数,来让内核自动给我们分配设备号
(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。 mknod /dev/xxx c 主设备号 次设备号
第二个参数:次设备号的基准,从第几个次设备号开始分配。
第三个参数:次设备号的个数。
第四个参数:驱动的名字。
返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。
int copy_from_user(void * to, const void __user * from, unsigned long n)
将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用 参数1:内核驱动中的一个buffer
参数2:应用空间到一个buffer
参数3:个数
自动创建设备结点
struct class *class_create(owner, name)//创建一个类
参数1: THIS_MODULE
参数2: 字符串名字,自定义
返回一个class指针
//创建一个设备文件
struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt,...)
参数1: class结构体,class_create调用之后到返回值
参数2:表示父亲,一般直接填NULL
参数3: 设备号类型 dev_t
dev_t devt
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
参数4:私有数据,一般直接填NULL
参数5和6:表示可变参数,字符串,表示设备节点名字
驱动代码
#include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/slab.h> #include <linux/cdev.h> #define MY_DEVICE_NAME "my_led_device" //物理地址 #define GPL_BASE 0x01F02C00 volatile unsigned long *gpl_base = NULL; volatile unsigned long *gpl_r0 = NULL; volatile unsigned long *gpl_r1 = NULL; volatile unsigned long *gpl_dat = NULL; volatile unsigned long *gpl_pul0 = NULL; //setp 3 :实现函数 static int my_open (struct inode *node, struct file *filp) { if (GPL_BASE) { printk("ioremap 0x%x\n", GPL_BASE); } else { return -EINVAL; } return 0; } static ssize_t my_write (struct file *filp, const char __user *buf, size_t size, loff_t *off) { unsigned char val; copy_from_user(&val, buf, 1); if (val) { *gpl_dat |= ( (1<<7) | (1<<10) | (1<<12) ); } else { *gpl_dat &= ( ~(1<<7) | ~(1<<10) | ~(1<<12) ); } return 1; } static const struct file_operations my_led_fops = { //step 1 :定义file_operations结构体 .open = my_open, .write = my_write, }; //step 1 : static struct class *led_class; static struct cdev *pcdev; //定义一个cdev指针 static dev_t n_dev; //第一个设备号(包含了主和次) static int __init led_device_init(void) {//step 2 :注册 int ret = -1; pcdev = cdev_alloc();//分配cdev结构空间 if(pcdev == NULL) { printk(KERN_EMERG" cdev_alloc error\n"); ret = -ENOMEM; /* 分配失败 */ return ret; } //2. 动态申请设备号 ret = alloc_chrdev_region(&n_dev, 0 , 2, MY_DEVICE_NAME); if(ret < 0 ) { //释放前面成功的资源 kfree(pcdev); /*释放cdev结构空间 */ printk(KERN_EMERG"alloc_chrdev_region error\n"); return ret; } cdev_init(pcdev, &my_led_fops); //初始化cdev结构 /* 建立cdev和file_operations之间的连接 */ /* 或这样初始化cdev结构 pcdev->owner = THIS_MODULE; pcdev->ops = &my_led_fops; */ ret = cdev_add(pcdev, n_dev, 2) ;//4. 向内核里面添加一个驱动,注册驱动 if(ret < 0 ) { //释放前面成功的资源 unregister_chrdev_region(n_dev, 2); /* 释放前面申请的调和号*/ kfree(pcdev); /* 释放cdev结构空间 */ printk(KERN_EMERG"alloc_chrdev_region error\n"); return ret; } /*自动创建设备节点/dev/SinlinxA64_LED*/ led_class = class_create(THIS_MODULE, "myled"); device_create(led_class, NULL, n_dev, NULL, "SinlinxA64_LED"); /*虚拟地址映射*/ gpl_base = (volatile unsigned long *)ioremap(GPL_BASE, 1); gpl_r0 = gpl_base; gpl_r1 = gpl_base + 0x04; gpl_dat = gpl_base + 0x10; gpl_pul0 = gpl_base + 0x1c; /* 初始化寄存器 */ *gpl_r0 &= ~(7<<28); *gpl_r1 &= ( ~(7<<16) | ~(7<< 8) ); *gpl_dat &= ( ~(1<<7) | ~(1<<10) | ~(1<<12) ); *gpl_pul0 &= ( ~(3<<14) | ~(3<<20) | ~(3<<24) ); /*上拉输出*/ *gpl_r0 |= (1<<28); *gpl_r1 |= ( (1<<16) | (1<<8) ); *gpl_pul0 |= ( (1<<14) | (1<<20) | (1<<24) ); printk(KERN_EMERG"cdev ok\n"); return 0; } static void __exit led_device_exit(void) { //step 2 :注销 //注销cdev结构 cdev_del(pcdev); //释放设备号 unregister_chrdev_region(n_dev, 2); /*起始设备号(主、次) 连续的次设备号数量*/ //释放cdev结构空间 kfree(pcdev); device_destroy(led_class, n_dev); class_destroy(led_class); iounmap(GPL_BASE); printk(KERN_EMERG"cdev_del ok\n"); } module_init(led_device_init); module_exit(led_device_exit); MODULE_LICENSE("GPL");