10.20 platform总线驱动

文章目录

  • platform总线驱动
    • 实现代码
      • pdrv.c
      • Makefile
      • test.c
      • 设备树节点
    • 运行结果

platform总线驱动

a.应用程序通过阻塞的io模型来读取number变量的值
b.number是内核驱动中的一个变量
c.number的值随着按键按下而改变(按键中断) 例如number=0 按下按键number=1 ,再次按下按键number=0
d.在按下按键的时候需要同时将led1的状态取反
e.驱动中需要编写字符设备驱动
f.驱动中需要自动创建设备节点
g.这个驱动需要的所有设备信息放在设备树的同一个节点中

实现代码

pdrv.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CNAME "myled"
#include "myled.h"

int minor = 0; //次设备号从0开始
#if 0
unsigned int major = 0;//动态申请
#else
unsigned int major = 500; //静态指定设备号
#endif
char kbuf[128] = {}; //定义数组用于存放和用户之间拷贝的数据
struct cdev *cdev;
struct class *cls;
struct device *dev;

const int count = 3; //指定设备节点的个数为3
//定义队列头
wait_queue_head_t wq_head;
unsigned int condition = 0; //判断是否有数据准备好的标识变量

int gpiono1;
int number = 0;
struct property *pr; //属性结构体指针
struct device_node *node;
unsigned int irqno; //接收软中断号
struct work_struct work;

struct resource *res;
struct gpio_desc *desc;

struct resource *res;
struct gpio_desc *desc;

//对应的是open()
int mycdev_open(struct inode *inode, struct file *file)
{

      printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
      return 0;
}

//底半部中断处理函数
void work_func(struct work_struct *w)
{
      if (number == 1)
      {
            //点亮led1
            gpio_set_value(gpiono1, 0);
            number = 0;
      }
      else if (number == 0)
      {
            //点亮led1
            gpio_set_value(gpiono1, 1);
            number = 1;
      }
      condition = 1;
      wake_up_interruptible(&wq_head);
}
//中断处理函数
irqreturn_t irq_handler(int irq, void *dev)
{
      //启用底半部中断处理
      schedule_work(&work);
      return IRQ_HANDLED;
}

// read()
ssize_t mycdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff)
{

      // size参数是用户期待读到的字节长度
      int ret;
      if (file->f_flags & O_NONBLOCK)
      {
            //非阻塞
            return -EINVAL;
      }
      else
      {
            //阻塞
            ret = wait_event_interruptible(wq_head, condition);
            if (ret)
            {
                  printk("接收阻塞休眠\n");
                  return ret;
            }
      }
      //把父进程拷贝到内核的数据再拷贝给子进程
      if (size > sizeof(number))
            size = sizeof(number);
      ret = copy_to_user(ubuf, &number, size);
      if (ret)
      {
            printk("数据从内核向用户拷贝失败\n");
            return -EIO;
      }
      // condition=0;
      condition = 0;

      return size;
}

// write()
ssize_t mycdev_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff)
{
      int ret;
      if (size > sizeof(kbuf))
            size = sizeof(kbuf);
      ret = copy_from_user(kbuf, ubuf, size);
      if (ret)
      {
            printk("数据从内核向用户拷贝失败\n");
            return -EIO;
      }
      //唤醒
      condition = 1;
      wake_up_interruptible(&wq_head);

      return size;
}

int mycdev_close(struct inode *inode, struct file *file)
{
      printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
      return 0;
}
//操作方法结构体的初始化
struct file_operations fops =
    {
        .open = mycdev_open,
        .read = mycdev_read,
        .release = mycdev_close,
        .write = mycdev_write,
};

int pdrv_probe(struct platform_device *pdr)
{
      //获取设备信息
      printk("%s:%d\n", __FILE__, __LINE__);
      res = platform_get_resource(pdr, IORESOURCE_MEM, 0);
      if (res == NULL)
      {
            printk("获取资源失败\n");
            return -ENODATA;
      }
      irqno = platform_get_irq(pdr, 0);
      if (irqno < 0)
      {
            printk("获取资源失败\n");
            return irqno;
      }
      printk("%#x    %d\n", res->start, irqno);

      return 0;
}
int pdrv_remove(struct platform_device *pdr)
{
      printk("%s:%d\n", __FILE__, __LINE__);
      return 0;
}
//定义compatible表
struct of_device_id oftable[] = {
    {.compatible = "hqyj,hello"},
    {},
};
MODULE_DEVICE_TABLE(of, oftable);
//定义并初始化对象
struct platform_driver pdrv = {
    .probe = pdrv_probe,
    .remove = pdrv_remove,
    .driver = {
        .name = "test",
        .of_match_table = oftable, //设备树匹配
    },
};
static int __init mycdev_init(void)
{
      int ret, i;

      //分配对象
      dev_t devno;

      //注册对象
      platform_driver_register(&pdrv);

      cdev = cdev_alloc();
      if (cdev == NULL)
      {
            printk("cdev alloc memory err\n");

            ret = -ENOMEM;
            goto ERR1;
      }
      printk("对象分配成功\n");
      //对象的初始化
      cdev_init(cdev, &fops);
      //设备号的申请

      if (major == 0) //动态申请
      {
            ret = alloc_chrdev_region(&devno, minor, count, "my_led");
            if (ret)
            {
                  printk("动态申请设备号失败\n");
                  goto ERR2;
            }
            major = MAJOR(devno);
            minor = MINOR(devno);
            printk("动态申请设备号成功\n");
      }
      else
      {
            ret = register_chrdev_region(MKDEV(major, minor), count, "my_led");
            if (ret)
            {
                  printk("静态申请设备号失败\n");
                  goto ERR2;
            }
            printk("静态申请设备号成功\n");
      }
      //注册字符设备驱动
      ret = cdev_add(cdev, MKDEV(major, minor), count);
      if (ret)
      {
            printk("字符设备驱动注册失败\n");
            goto ERR3;
      }
      printk("注册字符设备驱动成功\n");

      //自动创建设备节点
      cls = class_create(THIS_MODULE, "led");
      if (IS_ERR(cls))
      {
            printk("创建逻辑节点目录失败\n");
            ret = PTR_ERR(cls);
            goto ERR4;
      }
      printk("创建逻辑节点目录成功\n");
      //向上提交节点信息
      for (i = 0; i < 3; i++)
      {
            dev = device_create(cls, NULL, MKDEV(major, i), NULL, "my_led%d", i);
            if (IS_ERR(dev))
            {
                  printk("创建逻辑节点失败\n");
                  ret = PTR_ERR(dev);
                  goto ERR5;
            }
      }
      printk("创建逻辑节点成功\n");
      //初始化队列头
      init_waitqueue_head(&wq_head);

      //工作队列初始化
      INIT_WORK(&work, work_func);
      //解析设备树节点
      node = of_find_node_by_name(NULL, "myirq");
      if (node == NULL)
      {
            printk("解析设备树节点失败\n");
            return EAGAIN;
      }
      printk("解析设备树节点成功\n");
      //根据设备树节点获取软中断号
      irqno = irq_of_parse_and_map(node, 0);
      if (irqno == 0)
      {
            printk("获取软中断号失败\n");
            return EINVAL;
      }
      printk("获取软中断号成功\n");
      //注册中断
      ret = request_irq(irqno, irq_handler, IRQF_TRIGGER_FALLING, "key2_inte", NULL);
      if (ret)
      {
            printk("注册中断失败\n");
            return ret;
      }
      printk("注册中断成功\n");
      //获取gpio编号
      gpiono1 = of_get_named_gpio(node, "led1", 0);
      if (gpiono1 < 0)
      {
            printk("获取led1编号失败\n");
            return gpiono1;
      }
      printk("获取led1编号成功\n");
      //申请gpio编号使用权
      ret = gpio_request(gpiono1, NULL);
      if (ret)
      {
            printk("申请设备编号失败\n");
            return ret;
      }
      //设置输出方式
      gpio_direction_output(gpiono1, 0);

      return 0;
ERR5:
      for (--i; i >= 0; i--)
      {
            device_destroy(cls, MKDEV(major, i));
      }
      class_destroy(cls);
ERR4:
      cdev_del(cdev);
ERR3:
      unregister_chrdev_region(MKDEV(major, minor), count);
ERR2:
      kfree(cdev);
ERR1:
      return ret;
}
static void __exit mycdev_exit(void)
{
      // 1.销毁设备节点
      int i;
      for (i = 0; i < count; i++)
      {
            device_destroy(cls, MKDEV(major, i));
      }
      class_destroy(cls);
      // 2.注销字符设备驱动
      cdev_del(cdev);
      // 3.释放设备号
      unregister_chrdev_region(MKDEV(major, minor), count);
      // 4.释放动态申请的空间
      kfree(cdev);
      //注销中断
      free_irq(irqno, NULL);

      //释放设备号
      gpio_set_value(gpiono1, 0);
      gpio_free(gpiono1);

      //释放GPIO编号
      gpiod_direction_output(desc, 0);
      gpiod_put(desc);

      //注销
      platform_driver_unregister(&pdrv);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

Makefile

modname ?= demo
arch ?= arm

ifeq ($(arch),arm)#arm架构
KERNELDIR := /home/ubuntu/fsmp1a/linux-stm32mp-5.10.61-stm32mp-r2-r0/linux-5.10.61
else  
KERNELDIR := /lib/modules/$(shell uname -r)/build #当前x86架构格式路径
#KERNELDIR是一个变量,指向内核源码目录
endif
PWD:=$(shell pwd)
#PWD指向当前驱动目录的一个变量

all:
	make -C $(KERNELDIR) M=$(PWD) modules
#make -C $(KERNELDIR)
#进入内核顶层目录下,读取对应的Makefile文件,然后执行make
# M=$(PWD)  :指定编译模块的路径为当前驱动路径
#make modules:模块化编译
#进入内核顶层目录使用其中的Makefile对当前文件进行模块化编译

clean:
	make -C $(KERNELDIR) M=$(PWD) clean
#清除编译

obj-m:=$(modname).o
#指定当前编译生成的模块名字为demo   demo.c==>demo.ko

test.c

#include
#include
#include
#include
#include
#include
#include
 #include 
#include 
#include"myled.h"
 
int main(int argc, char const *argv[])
{
   pid_t pid;
   int number=0;
   int count = 10;
   int fd=open("/dev/my_led0",O_RDWR);
   if(fd<0)
   {
    printf("设备文件打开失败\n");
    exit(-1);
   }
    while(1)
    {
        //write(fd,&number,sizeof(number));
        read(fd,&number,sizeof(number));
        printf("number:%d\n",number);
    }
    exit(0);
   
    return 0;
}

设备树节点

myplatform{
          compatible="hqyj,hello2";
          //填写内存地址
          reg = <0x12345678 0x14>;
          //填写中断
          interrupt-parent=<&gpiof>;
          interrupts=<9 0>,<7 0>,<8 0>;   
          led1 = <&gpioe 10 0>;
          led2 = <&gpiof 10 0>;
          led3 = <&gpioe 8 0>;
      };

运行结果

终端运行make modname=pdrv arch=arm,生成pdrv.ko
终端运行arm-linux-gnueabihf-gcc test.c,生成a.out
将这两个文件复制到~/nfs/rootfs/下
在串口工具中下载驱动并运行a.out

10.20 platform总线驱动_第1张图片

你可能感兴趣的:(驱动,嵌入式硬件,arm,arm开发,stm32,驱动开发)