Linux 内核驱动开发--字符设备驱动

1.字符设备编程框架

实现一个硬件字符设备的驱动程序
实则就是实例化一个struct cdev类型的对象
include/linux/cdev.h
	struct cdev {
  		struct kobject kobj;
   		struct module *owner;
  		const struct file_operations *ops;//操作函数集合
  		struct list_head list;                                                                                                              
  		dev_t dev; //设备号
  		unsigned int count;
	} __randomize_layout;
	这里面只需要初始化 dev 和 *ops 这俩对象即可,其他的由内核进行维护
	
	1.1 设备号
		dev_t, 本质上就是一个unsinged int 类型数据
		设备号 = 主设备号 (高12bit) + 次设备号 (低20bit)
		主设备号用于区分不同类型的设备
		次设备号用于区分同一类设备中的不同个体
		ls /dev/ttySAC* -l 
		SAC0, SAC1, SAC2 的主设备号相同都是204,它们都是uart控制器,属于同一类设备
		ls /dev/fb0 -l  
		fb0是lcd屏,它的主设备号是29
		1.1.1 设备号的申请与注册有两种方式
			1) 静态注册
				主设备号的范围 0~255
				观察系统中主设备号哪些被占用了 然后从未被使用的里面选一个
				那么,如何看哪些主设备号没有被占用呢?
				cat /proc/devices --> 看里面有没有被注册的设备号,都可以拿来注册
				如何向内核注册一个设备好呢?
				内核中提供了一个接口:
		 /**
		 * register_chrdev_region() - register a range of device numbers
		 * @from: the first in the desired range of device numbers; must include
		 *        the major number.
		 * @count: the number of consecutive device numbers required
		 * @name: the name of the device or driver.
		 *
		 * Return value is zero on success, a negative error code on failure.
		 */
		int register_chrdev_region(dev_t from, unsigned count, const char *name)
		{
		  struct char_device_struct *cd;                                                                                                                  
		  dev_t to = from + count;
		  dev_t n, next;

		  for (n = from; n < to; n = next) {
		    next = MKDEV(MAJOR(n)+1, 0); 
		    if (next > to) 
		      next = to; 
		    cd = __register_chrdev_region(MAJOR(n), MINOR(n),
        		     next - n, name);
		    if (IS_ERR(cd))
		      goto fail;
		  }
		  return 0;
		fail:
		  to = n;
		  for (n = from; n < to; n = next) {
		    next = MKDEV(MAJOR(n)+1, 0); 
		    kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
	  	  }
		  return PTR_ERR(cd);
		}
			from,要注册的起始设备号
			count, 连续注册的设备号个数
			name, 名称
			void unregister_chrdev_region(dev_t from, unsigned count)
				from, 要注销的起始设备号
				count,要连续注销的设备号个数
			led_drv.c
			#include <linux/init.c>
			#include <linux/module.c>
			#include <linux/fs.h>
			#include <linux/cdev.h>

			MODULE_LICENSE("GPL");
			struct cdev led_drv;
			//设备号
			dev_t dev;
			unsigned int major = 200;
			unsigned int minor = 10; 

			int __init led_drv_init(void)
			{
			  //dev = major << 20 | minor;//自己实现
			  dev = MKDEV(major, minor);//内核中实现
			  register_chrdev_region (dev, 1, "leds");
			  return 0;
			}
			void __exit led_drv_exit(void)
			{
			  unregister_chrdev_region (dev, 1);
			}

			module_init(led_drv_init);
			module_exit(led_drv_exit);
			实验步骤:
				insmod led_drv.ko
				cat /proc/devices
				rmmod led_drv
				cat /proc/devices
				
			2) 动态注册	
				让内核选一个未被使用的主设备号
				注册后归你使用
				内核下也提供了一个接口:
				int alloc_chrdev_region(dev_t *dev, unsigned baseminor, 
										unsigned count, const char *name)
				dev, 传出参数 用于返回注册成功的第一个设备号
				baseminor, 起始次设备号
				count, 连续注册的设备号个数
				name, 名称
				void unregister_chrdev_region(dev_t from, unsigned count)
		#include <linux/init.c>
		#include <linux/module.c>
		#include <linux/fs.h>
		#include <linux/cdev.h>
		
		MODULE_LICENSE("GPL");
		struct cdev led_drv;
		//设备号
		dev_t dev;
		unsigned int major = 0;
		unsigned int minor = 10; 

		int __init led_drv_init(void)
		{
		  //dev = major << 20 | minor;//自己实现
		  if (major) //静态注册
		  {
		    dev = MKDEV(major, minor);//内核中实现
		    register_chrdev_region (dev, 1, "leds");
		  } 
		  else 
		  {
		    alloc_chrdev_region (&dev, 100, 1, "leds");
		    major = MAJOR(dev);
		    minor = MINOR(dev);
		    printk ("major = %d minor = %d\n",major,minor);
		  }
		  return 0;
		}
		void __exit led_drv_exit(void)
		{
		  unregister_chrdev_region (dev, 1);
		}
		module_init(led_drv_init);
		module_exit(led_drv_exit);
		实验步骤:
			insmod led_drv.ko
			cat /proc/devices
			rmmod led_drv
			cat /proc/devices		
			
	1.2 设备对应的操作函数集合
		实例化struct cdev 对象过程中的主要编码工作
		集中在 struct file_operations
		具体到某个字符设备文件 只需要实现以上操作函数集合的一个子集
		
	1.3 内核中提供操作cdev的API
		//初始化cdev
		void cdev_init(struct cdev *cdev, const struct file_operations *fops);
		{
			cdev->ops = fops;
		}
		//注册cdev
		int cdev_add(struct cdev *p, dev_t dev, unsigned count)
			p,要注册的cdev变量的地址
			dev,该cdev 对应的设备号
			count,连续注册的个数
		//注销cdev
		void cdev_del(struct cdev *p)
		led_drv.c		
		#include <linux/init.c>
		#include <linux/module.c>
		#include <linux/fs.h>
		#include <linux/cdev.h>
		MODULE_LICENSE("GPL");

		//定义一个cdev变量
		struct cdev led_cdev;

		//设备号
		dev_t dev;
		unsigned int major = 0;
		unsigned int minor = 10; 

		int led_open (struct inode *inode, struct file *filp)
		{
		  printk ("enter %s\n",__func__);
		  return 0;
		}

		int led_close (struct inode *inode, struct file *filp)
		{
		  printk ("enter %s\n",__func__);
		  return 0;
		}

		struct file_operations led_fops = 
		{
		  .owner = THIS_MODULE;
		  .open = led_open;
		  .release = led_close;
		}

		int __init led_drv_init(void)
		{		
		  //dev = major << 20 | minor;//自己实现
		  if (major) //静态注册
		  {
		    dev = MKDEV(major, minor);//内核中实现
		    register_chrdev_region (dev, 1, "leds");
		  } 
		  else 
		  {
		    alloc_chrdev_region (&dev, 100, 1, "leds");
		    major = MAJOR(dev);
		    minor = MINOR(dev);
		    printk ("major = %d minor = %d\n",major,minor);
		  }
		  //初始化cdev
		  cdev_init (&led_cdev, &led_fops);

		  //注册cdev
		  cdev_add (&led_cdev, dev, 1);

		  return 0;
		}
		void __exit led_drv_exit(void)
		{
		  //注销cdev 
		  cdev_del (&led_cdev);

		  //注销设备号
		  unregister_chrdev_region (dev, 1);
		}

		module_init(led_drv_init);
		module_exit(led_drv_exit);
		led_test.c
		#include <stdio.h>
		#include <unistd.h>
		#include <fcntl.h>

		int main (void)
		{
		  int fd = open("/dev/myleds",O_RDWR);

		  if(fd < 0)
		  {
		    perror("open failed");
		    return -1;
		  }
		  printf("open successed!\n");
		  sleep (10);
		  printf("close file...\n");
		  close (fd);
		  return 0;
		}
	Makefile
	obj-m += led_drv.o
	KERNEL_PATH=/home/liuyang/driver/kernel/
	all:
			make -C $(KERNEL_PATH) M=$(PWD) modules
			cp *.ko ../../rootfs
	clean:
			make -C $(KERNEL_PATH) M=$(PWD) clean
		实验步骤:
			1) arm-cortex_a9-linux-gnueabi-gcc led_test.c -o led_test
			   cp led_test ../../rootfs
			2) insmod led_drv.ko
			3) mknod /dev/myleds c 244 100 
			//注意,这里面的主设备号和次设备号必须和安装模块时,
			内核分的设备号保持一致
			4) ./led_test
	  	为什么我们没有增加系统调用号的情况下,依旧可以调用到内核态的sys_open?
	  	原因: 

在这里插入图片描述
2. GPIO库函数的使用

2.1 电路原理图
	控制led1就是控制GPIOC12管脚
	
2.2 cpu datasheet
	GPIOALTFN0
	GPIOCOUTENB
	GPIOCOUT
	
2.3 控制GPIO管脚
	1)直接操作特殊功能寄存器,使用虚拟地址
	2) 使用内核中提供的GPIO库函数
	
2.4 GPIO库函数的使用方式
	1) 申请gpio管脚
		int gpio_request(unsigned gpio, const char *label)
		gpio,要申请的管脚编号
		label,理解成name
	2) 使用gpio管脚
		int gpio_direction_input(unsigned gpio)
		将编号为gpio的管脚设置为输入模式
		int gpio_direction_output(unsigned gpio, int value)
		将编号为gpio的管脚设置为输出模式 默认输出value值
		value非0,管脚输出高电平
		void gpio_set_value(unsigned gpio, int value)
		让编号为gpio的管脚输出 高(value 非0) /低电平
		int gpio_get_value(unsigned gpio)
		获取编号为gpio的管脚电平状态
		高电平返回1
		释放gpio管脚
		void gpio_free(unsigned gpio)
		
		vi led_drv.c
		#include <linux/init.c>
		#include <linux/module.c>
		#include <linux/fs.h>
		#include <linux/cdev.h>
		#include <linux/gpio.h>
		#include <mach/platform.h>
		MODULE_LICENSE("GPL");
		//定义一个cdev变量
		struct cdev led_cdev;
		//设备号
		dev_t dev;
		unsigned int major = 0;
		unsigned int minor = 10; 
		int led_open (struct inode *inode, struct file *filp)
		{
		  printk ("enter %s\n",__func__);
		  gpio_set_value (PAD_GPIO_C + 12, 0);
		  return 0;
		}
		int led_close (struct inode *inode, struct file *filp)
		{
		  printk ("enter %s\n",__func__);
		  gpio_set_value (PAD_GPIO_C + 12, 1);
		  return 0;
		}
		struct file_operations led_fops = 
		{
		  .owner = THIS_MODULE;
		  .open = led_open;
		  .release = led_close;
		}
		int __init led_drv_init(void)
		{
		  //dev = major << 20 | minor;//自己实现
		  if (major) //静态注册
		  {
		    dev = MKDEV(major, minor);//内核中实现
		    register_chrdev_region (dev, 1, "leds");
		  } 
		  else 
		  {
		    alloc_chrdev_region (&dev, 100, 1, "leds");
		    major = MAJOR(dev);
		    minor = MINOR(dev);
		    printk ("major = %d minor = %d\n",major,minor);
		  }
		  //初始化cdev
		  cdev_init (&led_cdev, &led_fops);
		  //注册cdev
		  cdev_add (&led_cdev, dev, 1);
		  //申请GPIO管脚
		  gpio_request (PAD_GPIO_C + 12, "LED1");//PAD_GPIO_C 
		  //设置为输出模式
		  gpio_direction_output(PAD_GPIO_C + 12, 1);
		  return 0;
		}
		void __exit led_drv_exit(void)
		{
		  //释放GPIO管脚
		  gpio_free(PAD_GPIO_C + 12);
		  //注销cdev 
		  cdev_del (&led_cdev);
		  //注销设备号
		  unregister_chrdev_region (dev, 1);
		}
		module_init(led_drv_init);
		module_exit(led_drv_exit);
		建议将linux内核中自带的led驱动程序裁剪掉
		cd kernel
		make menuconfig
		Device Drivers --->
			--*-- LED Support --->
				< > LED Support for GPIO connect LEDS
		make uImage
		使用新内核
		cp uImage /tftpboot
	  	tftp 48000000 uImage
  		mmc write 48000000 800 3000
  		setenv bootcmd mmc read 48000000 800 3000 \; bootm 48000000
  		saveenv
  		insmod led_drv.ko
  		mknod /dev/my_leds c 244 100
  		./test
  1. 用户态和内核态的数据交互

    用户态不能访问内核空间中的数据
    内核空间代码也不直接访问用户空间数据
    

在这里插入图片描述

	3.1 内核中提供数据交互的第一套API
		copy_to_user(void __user *to, const void *from, unsigned long n)
		copy_from_user(void __user *to, const void *from, unsigned long n)
		//to, 目标地址
		//from,元数据地址
		//n, 连续操作的字节数
		返回值, 操作失败的字节数
		以上两个函数与memcpy相比,多了对要访问用户空间的权限检查
		
	3.2 内核中提供数据交互的第二套API (了解)
		int put_usr(data, ptr);
		int get_usr(data, ptr);
		连续操作的字节个数, 取决于data变量的数据类型

在这里插入图片描述

		练习 :
			./led_test on/off
			write 0  亮
					 1  灭
			read 获取灯的亮灭状态
			arm-cortex_a9_linux-gnueabi-gccc led_test.c -o led_test
			cp led_test ../../rootfs/
			rmmod led_drv.ko
			insmod led_drv.ko
			./led_test on
			./led_test off
			
		vi led_test.c			
		#include <stdio.h>
		#include <unistd.h>
		#include <fcntl.h>
		//./led_test on/off
		int main (int argc, char** argv)
		{

		  int cmd = 0;
		  int status = 0;

		  if (argc != 2)
		  {
		    printf ("usage:%s <on/off>\n",argv[0]);
		  }
		  int fd = open("/dev/myleds",O_RDWR);
		  if(fd < 0)
		  {
		    perror("open failed");
		    return -1;
		  }
		  pritnf ("open successed!\n");
		  if (strcmp (argv[1], "on") == 0)
		  {
		    cmd = 0;
		  } else {
		    cmd = 1;
		  }
		  write(fd, &cmd, sizeof(int));
		  read (fd, &status, sizeof(int));
		  if (status == 0)
		  {
		    printf ("led is on!\n");
		  } else {
		    printf ("led is off!\n");
		  }
		  close (fd);
		  return 0;
		}
	vi led_drv.c
		#include <linux/init.c>
		#include <linux/module.c>
		#include <linux/fs.h>
		#include <linux/cdev.h>
		#include <linux/gpio.h>
		#include <mach/platform.h>
		#include <linux/uaccess.h>
		MODULE_LICENSE("GPL");
		//定义一个cdev变量
		struct cdev led_cdev;
		//设备号
		dev_t dev;
		unsigned int major = 0;
		unsigned int minor = 10; 
		int k_status = 1;//灭
		int led_open (struct inode *inode, struct file *filp)
		{
		#if 0
		  printk ("enter %s\n",__func__);
		  gpio_set_value (PAD_GPIO_C + 12, 0);
		#endif
		  return 0;
		}

		int led_close (struct inode *inode, struct file *filp)
		{
		#if 0
		  printk ("enter %s\n",__func__);
		  gpio_set_value (PAD_GPIO_C + 12, 1);
		#endif
		  return 0;
		}
		ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
		{
		  int k_cmd = 0;
		  int ret = 0;
		  ret = copy_from_user(&k_cmd, buf, len);
		  gpio_set_value(PAD_GPIO_C + 12, k_cmd);
		  k_status = k_cmd;
		  return len;
		}
		ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
		{
		  int ret = 0;
		  ret = copy_to_user (buf, &k_status, len);
		  return len;
		}
		struct file_operations led_fops = 
		{
		  .owner = THIS_MODULE;
		  .open = led_open;
		  .release = led_close;
		  .write = led_write;
		  .read = led_read;
		}
		int __init led_drv_init(void)
		{
		  //dev = major << 20 | minor;//自己实现
		  if (major) //静态注册
		  {
		    dev = MKDEV(major, minor);//内核中实现
		    register_chrdev_region (dev, 1, "leds");
		  } 
		  else 
		  {
		    alloc_chrdev_region (&dev, 100, 1, "leds");
		    major = MAJOR(dev);
		    minor = MINOR(dev);
		    printk ("major = %d minor = %d\n",major,minor);
		  }
		  //初始化cdev
		  cdev_init (&led_cdev, &led_fops);
		  //注册cdev
		  cdev_add (&led_cdev, dev, 1);
		  //申请GPIO管脚
		  gpio_request (PAD_GPIO_C + 12, "LED1");//PAD_GPIO_C 
		  //设置为输出模式
		  gpio_direction_output(PAD_GPIO_C + 12, 1);
		  return 0;
		}
		void __exit led_drv_exit(void)
		{
		  //释放GPIO管脚
		  gpio_free(PAD_GPIO_C + 12);
		  //注销cdev 
		  cdev_del (&led_cdev);
		  //注销设备号
		  unregister_chrdev_region (dev, 1);
		}
		module_init(led_drv_init);
		module_exit(led_drv_exit);

4.ioctl

	类似于 open read write close lseek 也是一系统调用
	ioctl 设置/获取硬件设备工作参数
	uartputs ("liuyang"); //wirte
	要把115200这个数据给uart控制器
	
	int ioctl (fd, cmd) //控制LED1的亮灭 ioctl (fd, LED_ON)
								ioctl (fd, LED_OFF);
	int ioctl (fd, cmd, arg) //控制4栈LED
									ioctl (fd, LED_ON,1/2/3/4)

在这里插入图片描述

	vi led_test.c		
	#include <stdio.h>
	#include <unistd.h>
	#include <fcntl.h>
	#include <sys/ioctl.h>
	#include <stdlib.h>

	#define CMD_LDE_ON    0x10001
	#define CMD_LDE_OFF   0x10002

	//./led_test on/off <1/2/3/4>
	int main (int argc, char** argv)
	{
	  int cmd = 0;
	  int status = 0;
	  int index = 0; //要控制哪栈灯

	  if (argc != 3)
	  {
	    printf ("usage:%s <on/off> <1/2/3/4>\n",argv[0]);
	    return -1;
	  }

	  int fd = open("/dev/myleds",O_RDWR);
	  if(fd < 0)
	  {
    	perror("open failed");
	    return -1;
	  }
	  pritnf ("open successed!\n");
	  if (strcmp (argv[1], "on") == 0)
	  {
    	cmd = CMD_LDE_ON;
	  } else {
    	cmd = CMD_LDE_OFF;
	  }

	  index = strtoul (argv[2], 0, 10);
	  ioctl (fd, cmd, &index);
	  close (fd);
	  return 0;
	}
	vi led_drv.c		
	#include <linux/init.c>
	#include <linux/module.c>
	#include <linux/fs.h>
	#include <linux/cdev.h>
	#include <linux/gpio.h>
	#include <mach/platform.h>
	#include <linux/uaccess.h>
	
	MODULE_LICENSE("GPL");
	//定义一个cdev变量
	struct cdev led_cdev;
	//设备号
	dev_t dev;
	unsigned int major = 0;
	unsigned int minor = 10; 
	int k_status = 1;//灭
	#define CMD_LED_ON  0x10001
	#define CMD_LED_OFF 0x10002
	typedef struct led_desc
	{
	  int gpio; //管脚编号
	  char *name; //名称
	}led_desc_t;

	static led_desc_t leds[] = {
	  {PAD_GPIO_C + 12, "LED1"},
	  {PAD_GPIO_C + 7,  "LED2"},
	  {PAD_GPIO_C + 11, "LED3"},
	  {PAD_GPIO_C + 21, "LED4"},
	};

	int led_open (struct inode *inode, struct file *filp)
	{
	  return 0;
	}
	int led_close (struct inode *inode, struct file *filp)
	{
	  return 0;
	}
	ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t 	*offset)
	{
	  return len;
	}
	ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
	{
	  return len;
	}
	long led_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
	{
	  int ret = 0;
	  int k_index = 0;

	  ret = copy_from_user (&k_index, (const void *)arg, 4);
	  if (k_index >= 5)
	  {
	    return -EINVAL;
	  }
	  switch (cmd) {
	    case CMD_LED_ON:
	      gpio_set_value (leds[k_index-1].gpio, 0);
	      break;
    	case CMD_LED_OFF:
	      gpio_set_value (leds[k_index-1].gpio, 1);
    	  break;
	    default:
    	  return -EINVAL;
	  }
	  return 0;
	}

	struct file_operations led_fops = 
	{
	  .owner = THIS_MODULE;
	  .open = led_open;
	  .release = led_close;
	  .write = led_write;
	  .read = led_read;
	  .unlocked_ioctl = led_ioctl;
	}

	int __init led_drv_init(void)
	{
	  //dev = major << 20 | minor;//自己实现
	  if (major) //静态注册
	  {
    	dev = MKDEV(major, minor);//内核中实现
	    register_chrdev_region (dev, 1, "leds");
	  } 
	  else 
	  {
    	alloc_chrdev_region (&dev, 100, 1, "leds");
	    major = MAJOR(dev);
	    minor = MINOR(dev);
	    printk ("major = %d minor = %d\n",major,minor);
	  }
	  //初始化cdev
	  cdev_init (&led_cdev, &led_fops);
	  //注册cdev
	  cdev_add (&led_cdev, dev, 1);
	  for (; ARRAY_SIZE(leds);i++) {
    	//申请GPIO管脚
	    gpio_request (leds[i].gpio, leds[i].name);//PAD_GPIO_C 

    	//设置为输出模式
	    gpio_direction_output(leds[i].gpio, 1);
	  }
	  return 0;
	}
	void __exit led_drv_exit(void)
	{
	  for (; ARRAY_SIZE(leds);i++) {
	    //释放GPIO管脚
	    gpio_free(leds[i].gpio);
	  }
	  //注销cdev 
	  cdev_del (&led_cdev);
	  //注销设备号
	  unregister_chrdev_region (dev, 1);
	}
	module_init(led_drv_init);
	module_exit(led_drv_exit);

5.设备文件的自动创建

	自动创建设备文件的必要条件
		1) 根文件系统中存在mdev命令
		2) 挂载了procfs sysfs
			rootfs/etc/init.d/rcS
			mount -a
			rootfs/etc/fstab
			proc /proc proc defaults 0 0 
			sysfs /sys sys defaults 0 0 
			proc, 基于内存的文件系统
				向用户空间导出了内核的执行状态
				/proc/PID
				/proc/cpuinfo
				/proc/sys/kernel/printk
			sys,描述硬件驱动的模型 反映硬件之间的层次关系
				基于内存的文件系统
		
		3) 配置热插拔事件的响应动作
			echo /sbin/mdev > /proc/sys/kernel/hotpulg
		4) 驱动程序中产生热插拔事件
			狭义: U盘插入 拔出
			广义: 不但包括U盘插入 拔出
				还包括/sys 目录下的变化
			//影响"/sys/class" 创建了树枝	
			class_create(owner, name)
			//影响/sys/class/name/ 创建树枝上的果实
			device_create(struct class *class, struct device *parent, 
					dev_t devt, void *drvdata, const char *fmt, ...)
			const char *fmt, ... :决定了将来生成的设备的文件名
					例如: "ttySAC%d", i 
					进一步假设 i = 0
					创建出的设备文件"ttySAC0"
			device_destory
			class_destory
			
	vi led_drv.c
	#include <linux/init.c>
	#include <linux/module.c>
	#include <linux/fs.h>
	#include <linux/cdev.h>
	#include <linux/gpio.h>
	#include <mach/platform.h>
	#include <linux/uaccess.h>
	#include <linux/device.h>

	MODULE_LICENSE("GPL");
	//定义一个cdev变量
	struct cdev led_cdev;
	//设备号
	dev_t dev;
	unsigned int major = 0;
	unsigned int minor = 10; 
	int k_status = 1;//灭
	#define CMD_LED_ON  0x10001
	#define CMD_LED_OFF 0x10002
	struct class *cls = NULL;
	typedef struct led_desc
	{
	  int gpio; //管脚编号
	  char *name; //名称
	}led_desc_t;
	static led_desc_t leds[] = {
	  {PAD_GPIO_C + 12, "LED1"},
	  {PAD_GPIO_C + 7,  "LED2"},
	  {PAD_GPIO_C + 11, "LED3"},
	  {PAD_GPIO_C + 21, "LED4"},
	};

	int led_open (struct inode *inode, struct file *filp)
	{
	  return 0;
	}
	int led_close (struct inode *inode, struct file *filp)
	{
	  return 0;
	}
	ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
	{
	  return len;
	}
	ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t 	*offset)
	{
	  return len;
	}
	long led_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
	{
	  int ret = 0;
	  int k_index = 0;
	  ret = copy_from_user (&k_index, (const void *)arg, 4);

	  if (k_index >= 5)
	  {
	    return -EINVAL;
	  }
	  switch (cmd) {
	    case CMD_LED_ON:
	      gpio_set_value (leds[k_index-1].gpio, 0);
    	  break;
	    case CMD_LED_OFF:
	      gpio_set_value (leds[k_index-1].gpio, 1);
	      break;
	    default:
	      return -EINVAL;
	  }
	  return 0;
	}
	struct file_operations led_fops = 
	{
	  .owner = THIS_MODULE;
	  .open = led_open;
	  .release = led_close;
	  .write = led_write;
	  .read = led_read;
	  .unlocked_ioctl = led_ioctl;
	}
	int __init led_drv_init(void)
	{
	  //dev = major << 20 | minor;//自己实现
	  if (major) //静态注册
	  {
	    dev = MKDEV(major, minor);//内核中实现
	    register_chrdev_region (dev, 1, "leds");
	  } 
	  else 
	  {
    	alloc_chrdev_region (&dev, 100, 1, "leds");
	    major = MAJOR(dev);
	    minor = MINOR(dev);
	    printk ("major = %d minor = %d\n",major,minor);
	  }
	  //初始化cdev
	  cdev_init (&led_cdev, &led_fops);
	  //注册cdev
	  cdev_add (&led_cdev, dev, 1);
	  //自动创建设备文件
	  //结果是"/sys/class/leds" 树枝
	  cls = class_create (THIS_MODULE, "leds");
	  /*/sys/class/leds/myleds/文件夹出来了*/
	  device_create (cls, NULL, dev, NULL, "myleds");
	  for (; ARRAY_SIZE(leds);i++) {
    	//申请GPIO管脚
	    gpio_request (leds[i].gpio, leds[i].name);//PAD_GPIO_C 
	    //设置为输出模式
    	gpio_direction_output(leds[i].gpio, 1);
	  }
	  return 0;
	}
	void __exit led_drv_exit(void)
	{
	  for (; ARRAY_SIZE(leds);i++) {
	    //释放GPIO管脚
	    gpio_free(leds[i].gpio);
	  }
	  //销毁设备文件
	  device_destroy (cls, dev);
	  class_destroy (cls);
	  //注销cdev 
	  cdev_del (&led_cdev);
	  //注销设备号
	  unregister_chrdev_region (dev, 1);
	}
	module_init(led_drv_init);
	module_exit(led_drv_exit);
	字符设备驱动程序:
		申请注册设备号
		struct cdev 变量的定义 初始化 注册
			操作函数集合  (变化)
		设备文件的创建
		...
		设备文件的销毁
		cdev注销
		设备号的注销
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘德华海淀分华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值