嵌入式linux驱动学习笔记01

字符设备驱动(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查看挂载是否成功。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值