字符设备驱动(LED驱动)编写
1.准备工作
1.编译好uboot.bin(支持tftp、nfs),并且烧入开发板
2.准备好linux内核源码、zImage系统镜像和设备树文件board.dtb
3.linux主机配置好tftp、nfs。
4.zImage和board.dtb放入tftp共享文件夹 用BusyBox编译好根文件系统并复制到nfs共享文件夹。
2.uboot配置
用串口终端连接开发板,开发板上电打印uboot信息后按下enter停留在uboot。设置环境变量。
设置 bootcmd环境变量:从tftp服务器下载系统镜像和设备树文件并启动
setenv bootcmd tftp 80800000 zImage;tftp 83800000 board.dtb;bootz 80800000 - 83800000
设置 bootargs环境变量:console=ttymxc0,115200配置内核控制台参数;root=/dev/nfs配置根文件挂载在nfs服务器上,nfsroot=192.168.123.6…配置根文件系统绝对路径。ip=192.168.123.33:192.168.123.6…开发板和服务器ip配置。
setenv bootargs console=ttymxc0,115200 ; root=/dev/nfs nfsroot=192.168.123.6:/home/mypc/linux/nfs/rootfs,proto=tcp rw; ip=192.168.123.33:192.168.123.6:192.168.123.1:255.255.255.0::eth0:off
3.驱动编写
chardevled.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_COUNT 1 //设备数量
#define DEVICE_NAME "led" //设备名称
#define LED_ON 0
#define LED_OFF 1
/*存器物理地址*/
#define CCM_CCGR1_BASE (0x020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0x020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0x020E02F4)
#define GPIO1_DR_BASE (0x0209C000)
#define GPIO1_GDIR_BASE (0x0209C004)
/*映射后的虚拟地址指针*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
//设备结构体
struct chardev_str
{
struct cdev cdev; //字符设备
dev_t devid; //设备号
struct class *class; //类
struct device *device; //设备
int major; //主设备号
int minor; //次设备号
};
struct chardev_str devled;
/*操作函数集*/
static int led_open(struct inode *inode, struct file *filp);
static int led_release(struct inode *inode, struct file *filp);
static int led_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos);
static int led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos);
static struct file_operations devled_fops = {
.owner = THIS_MODULE,
.write = led_write,
.read = led_read,
.open = led_open,
.release = led_release,
};
/* 挂载设备函数,被驱动入口函数调用。即系统挂载ko文件时,执行该函数。*/
static int __init devled_init(void)
{
int ret, resut = 0;
uint32_t val;
/*********************以下硬件初始化,和实际硬件平台有关**********************/
/* 嵌入linux由于芯片MMU的机制,不支持直接用硬件的物理地址操作寄存器,需通过ioremap函数映射
*映射后用readl和 writel 对寄存器进行读写
*/
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
val = readl(IMX6U_CCM_CCGR1); //读取寄存器
val &= ~(3 << 26); //清除bit27-26
val |= 3 << 26; //设置bit27-26
writel(val, IMX6U_CCM_CCGR1); //写入寄存器
writel(0x05, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
val = readl(GPIO1_GDIR);
val |= 1 << 3; //设置bit3 设置输出模式
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR);
val |= (1 << 3); //设置bit3 GPIO3 = 1 默认关灯
writel(val, GPIO1_DR);
/****************************以下是注册设备**************************************/
if (devled.major)
{
devled.devid = MKDEV(devled.major, 0); //获取设备号
ret = register_chrdev_region(devled.devid, DEVICE_COUNT, DEVICE_NAME); //让系统设定分配设备号
}
else
{
ret = alloc_chrdev_region(&devled.devid, 0, DEVICE_COUNT, DEVICE_NAME); //让系统自动分配设备号
devled.major = MAJOR(devled.devid);//获得主设备号
devled.minor = MINOR(devled.devid);//获得子设备号
}
if (ret < 0)
{
printk("devid region error!\r\n"); //打印错误信息
resut = -1; //设置返回结果
goto fail_region; //跳转到返回处理
}
else
{
printk("major= %d minor = %d\r\n", devled.major, devled.minor);
}
/*注册字符设备*/
devled.cdev.owner = THIS_MODULE;
cdev_init(&devled.cdev, &devled_fops);
ret = cdev_add(&devled.cdev, devled.devid, DEVICE_COUNT); //添加字符设备
if (ret < 0)
{
printk("devled cdev_add error!\r\n"); //打印错误信息
resut = -1; //设置返回结果
goto fail_cdev_add; //跳转到返回处理
}
//
devled.class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(devled.class))
{
printk("devled class_create error!\r\n"); //打印错误信息
resut = PTR_ERR(devled.class); //设置返回结果
goto fail_class_create; //跳转到返回处理
}
devled.device = device_create(devled.class, NULL, devled.devid, NULL, DEVICE_NAME);
if (IS_ERR(devled.device))
{
printk("devled device_create error!\r\n"); //打印错误信息
resut = PTR_ERR(devled.device); //设置返回结果
goto fail_device_create; //跳转到返回处理
}
return 0;
fail_device_create:
class_destroy(devled.class);
fail_class_create:
//删除字符设备
cdev_del(&devled.cdev);
fail_cdev_add:
//注销设备号
unregister_chrdev_region(devled.devid, DEVICE_COUNT);
fail_region:
return resut;
}
/* 卸载设备函数,被驱动出口函数调用。即系统执行rmmod时执行该函数。*/
static void __exit devled_exit(void)
{
//取消物理寄存器地址重映射
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
//删除字符设备
cdev_del(&devled.cdev);
//注销设备号
unregister_chrdev_region(devled.devid, DEVICE_COUNT);
//摧毁设备
device_destroy(devled.class, devled.devid);
//摧毁类
class_destroy(devled.class);
}
void led_switch(uint8_t sta)
{
uint32_t val;
if (sta == LED_ON)
{
val = readl(GPIO1_DR);
val &= ~(1 << 3); //设置bit3 GPIO3 = 0 开灯
writel(val, GPIO1_DR);
}
else if (sta == LED_OFF)
{
val = readl(GPIO1_DR);
val |= (1 << 3); //设置bit3 GPIO3 = 1 关灯
writel(val, GPIO1_DR);
}
}
/*于硬件初始化在入口函数实现,open函数不用做特殊处理*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &devled; //私有数据初始化
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
filp->private_data = NULL; //释放私有数据
return 0;
}
/*读取led灯状态*/
static int led_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos)
{
int ret;
int resut;
struct chardev_str *dev = (struct chardev_str *)filp->private_data;//这里其实用不到私有数据
uint8_t databuf[1];
uint32_t val;
val = readl(GPIO1_DR);
if (val & (1 << 3))
{
databuf[0] = LED_OFF;
}
else
{
databuf[0] = LED_ON;
}
ret = copy_to_user(buf, databuf, count);
if (ret < 0)
{
printk("kernel_read_error!\r\n");
resut = -EFAULT;
goto fail_copy_to_user;
}
return 0;
fail_copy_to_user:
dev = NULL;
return resut;
}
static int led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret;
int resut;
struct chardev_str *dev = (struct chardev_str *)filp->private_data; //其实用不到私有数据
uint8_t databuf[1];
if (count > 1) //自允许写一字节数据。0 开 非0关
{
printk("user data error!\r\n");
resut = -EFAULT;
goto fail_datacheck ;
}
ret = copy_from_user(databuf, buf, count); //复制buf里的用户数据
if (ret < 0)
{
printk("get data error!\r\n");
resut = -EFAULT;
goto fail_copy_from_user;
}
if (databuf[0] != LED_ON) //关灯
{
led_switch(LED_OFF);
}
else //开灯
{
led_switch(LED_ON);
}
return 0;
fail_datacheck:
fail_copy_from_user:
dev = NULL;
return resut;
}
module_init(devled_init);//模块入口
module_exit(devled_exit);//模块出口
MODULE_LICENSE("GPL"); //开源协议
4.Makefile编写
#linux内核源码路径,就是编译zImage的内核源码路径,用来调用库文件和Makefile。
KERNELDIR := /home/wmz/study/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
#驱动源码路径,当前目录。
CURRENT_PATH := $(shell pwd)
#目标 chardevled.o
obj-m := chardevled.o
#编译驱动,先跳转到内核目录,获取内核Makefile信息在跳回当前目录执行编译。
#编译类型modules,内核的Makefile已经配置好了对应的平台信息。
build : kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
4.挂在测试
make后生成chardevled.ko文件,复制到服务器的nfs根文件系统文件夹的/lib/modules下。开发板上电linux内核启动后输入 cd /lib/modules 再输入modprobe chardevled.ko。用lsmod查看挂载是否成功。