07-ioctl控制LED软件实现(寄存器操作)

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知


&emsp:上一节讲到了LED的硬件分析,得到了LED控制的流程,详见
ioctl LED硬件分析
这一节将接着上一节的分析,对LED控制的软件部分进行实现,本节实现依据的是直接对相关的寄存器进行操作,下一节将利用库函数对LED的软件控制部分进行实现。

1. 预备知识

 在驱动中不能对实际的物理地址进行操作,操作的都是虚拟地址。因此需要先了解物理地址和虚拟地址进行转换的相关函数。

1.1 ioremap

 将物理地址映射为虚拟地址的函数是ioremap,函数定义在 include/asm/io.h 中,原型如下:

void __iomem *ioremap(phys_addr_t paddr, unsigned long size)

 各参数的定义如下

@param1: 要映射的起始的IO地址(物理地址)
@param2: 要映射的空间的大小
@return: 返回映射后的内核虚拟地址(3G-4G). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源
示例:
	#define CONF_GPMC_AD4 0x44E10810
	void __iomem *conf_gpmc_ad4_vir = NULL;
	conf_gpmc_ad4_vir  = (unsigned int *)ioremap(CONF_GPMC_AD4, 4);

1.2 readl

 内存映射之后,可以通过 readl 函数获取寄存器的状态,函数定义在 include/asm/io.h 中,原型如下

static inline u32 readl(const volatile void __iomem *addr)

 各参数的定义如下

@param1: 映射后的内核虚拟地址,ioremap的返回值
@return: 读到的虚拟地址中的值
示例:
	readl(conf_gpmc_ad4_vir);

1.3 writel

 将内存中的数据读出来后,可以通过 writel 往内存映射的 I/O 空间上写数据,函数定义在 include/asm/io.h 中,原型如下

static inline void writel(u32 b, volatile void __iomem *addr)

 各参数的定义如下

@param1: 要写入的数据
@param2: 映射后的内核虚拟地址,ioremap的返回值
@return: 无返回值
示例:
	writel(( readl(conf_gpmc_ad4_vir) | 0x7 | 0x1<<5), conf_gpmc_ad4_vir);
	// 读出寄存器中的值,设置相应位然后写进去

1.4 iounmap

 有映射函数就有相对应的取消映射函数,函数定义在 include/asm/io.h 中,原型如下

static inline void iounmap(volatile void __iomem *addr)

 各参数的定义如下

@param1: 映射后的内核虚拟地址,ioremap的返回值
@return: 无返回值
示例:
	iounmap(conf_gpmc_ad4_vir);

2. 代码实现

2.1 demo.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>

#include "chrdev.h"

int major;
const char *name = "demoname";

struct class *cls;
struct device *dev;

#define CONF_GPMC_AD4 	0x44E10810
#define GPIO1_OE  		0x4804C134
#define GPIO1_DATAOUT	0x4804C13C

void __iomem *conf_gpmc_ad4_vir = NULL;
void __iomem *conf_gpmc_ad5_vir = NULL;
void __iomem *conf_gpmc_ad6_vir = NULL;
void __iomem *conf_gpmc_ad7_vir = NULL;
void __iomem *gpio1_oe_vir = NULL;
void __iomem *gpio1_dataout_vir = NULL; 

int demo_open(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

int demo_release(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

ssize_t demo_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	//copy_to_user(void __user * to, const void * from, size_t n);

	return 0;
}

ssize_t demo_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	//copy_from_user(void * to, const void __user * from, unsigned long n);

	return 0;
}

long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{	
	led_node_t led_t;
	int ret, led_num;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	if ( _IOC_TYPE(cmd) != 'd')		/* 判断命令与头文件定义的是否一致 */
	{
		return -ENOTTY;	/* not a typewriter */
	}

	ret = copy_from_user(&led_t, (led_node_t *)args, sizeof(led_t));	//成功返回0,失败返回有多少个Bytes未完成copy。
	if (ret)
	{
		printk("copy_from_user failed.\n");
		goto err0;
	}

	led_num = led_t.which;
	switch(cmd)
	{
	case LEDON:
		if (led_num == 1)
		{
			printk("led1 on.\n");
			writel(readl(gpio1_dataout_vir) | (0X1 << 4), gpio1_dataout_vir);	// 置1,高电平
		}
		else if (led_num == 2)
		{
			printk("led2 on.\n");
			writel(readl(gpio1_dataout_vir) | (0X1 << 5), gpio1_dataout_vir);	// 置1,高电平
		}
		else if (led_num == 3)
		{
			printk("led3 on.\n");
			writel(readl(gpio1_dataout_vir) | (0X1 << 6), gpio1_dataout_vir);	// 置1,高电平
		}
		else if (led_num == 4)
		{
			printk("led4 on.\n");
			writel(readl(gpio1_dataout_vir) | (0X1 << 7), gpio1_dataout_vir);	// 置1,高电平
		}
		break;
	case LEDOFF:
		if (led_num == 1)
		{
			printk("led1 off.\n");
			writel(readl(gpio1_dataout_vir) & (~(0X1 << 4)), gpio1_dataout_vir);	// 清0,低电平
		}
		else if (led_num == 2)
		{
			printk("led2 off.\n");
			writel(readl(gpio1_dataout_vir) & (~(0X1 << 5)), gpio1_dataout_vir);	// 清0,低电平
		}
		else if (led_num == 3)
		{
			printk("led3 off.\n");
			writel(readl(gpio1_dataout_vir) & (~(0X1 << 6)), gpio1_dataout_vir);	// 清0,低电平
		}
		else if (led_num == 4)
		{
			printk("led4 off.\n");
			writel(readl(gpio1_dataout_vir) & (~(0X1 << 7)), gpio1_dataout_vir);	// 清0,低电平
		}
		break;
	default:
		printk("cmd id error.\n");
	}

	return 0;

err0:
	return ret;
}

const struct file_operations fops = {
	.open = demo_open,
	.release = demo_release,
	.read = demo_read,
	.write = demo_write,
	.unlocked_ioctl = demo_ioctl,
};

void gpio_iounmap(void)
{
	iounmap(gpio1_oe_vir);
}

void gpio_ioremap(void)
{
	conf_gpmc_ad4_vir = (unsigned int *)ioremap(CONF_GPMC_AD4, 4);
	conf_gpmc_ad5_vir = conf_gpmc_ad4_vir + 4;
	conf_gpmc_ad6_vir = conf_gpmc_ad4_vir + 8;
	conf_gpmc_ad7_vir = conf_gpmc_ad4_vir + 12;
	
	gpio1_oe_vir = (unsigned int *)ioremap(GPIO1_OE, 4);
	gpio1_dataout_vir = gpio1_oe_vir + 8;
}

void led_init(void)
{
	gpio_ioremap();

	writel(( readl(conf_gpmc_ad4_vir) | 0x7 | 0x1<<5), conf_gpmc_ad4_vir);		/* 引脚复用,选择MOD7,即GPIO */
	writel(( readl(conf_gpmc_ad5_vir) | 0x7 | 0x1<<5), conf_gpmc_ad5_vir);
	writel(( readl(conf_gpmc_ad6_vir) | 0x7 | 0x1<<5), conf_gpmc_ad6_vir);
	writel(( readl(conf_gpmc_ad7_vir) | 0x7 | 0x1<<5), conf_gpmc_ad7_vir);
	writel(( readl(gpio1_oe_vir) & (~(0xF << 4)) ), gpio1_oe_vir);				/* GPIO1_4~GPIO1_7引脚设置为输出功能(0表示输出) */
}

static int __init demo_init(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	major = register_chrdev(0, name, &fops);
	if (major <= 0)
	{
		printk("register_chrdev failed.\n");
		goto err0;
	}

	cls = class_create(THIS_MODULE, "char_class");
	if (cls == NULL)
	{
		printk("class_create failed.\n");
		goto err1;
	}

	dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "chrdev%d", 0);
	if (dev ==NULL)
	{
		printk("device_create failed.\n");
		goto err2;
	}

	led_init();	// 设置为输出模式
	
	return 0;

err2:
	class_destroy(cls);
err1:
	unregister_chrdev(major, name);
err0:
	return major;
}

static void __exit demo_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	gpio_iounmap();		//解除映射
	device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, name);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

2.2 test.c

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "chrdev.h"

const char *pathname = "/dev/chrdev0";
#define LED_NUM 4

int main(void)
{
	int fd, i;
	led_node_t led;

	fd = open(pathname, O_RDWR, 0666);
	if (fd <= 0)
	{
		printf("open failed.\n");
		return -1;
	}

	for (i=1; i<=LED_NUM; i++)
	{
		led.which  = i;
		
		led.status = 0; // 0表示灭
		ioctl(fd, LEDOFF, &led);
		sleep(1);

		led.status = 1;	// 1表示亮
		ioctl(fd, LEDON, &led);
		sleep(1);
	}

	close(fd);
	
	return 0;
}

2.3 chrdev.h

#ifndef _CHRDEV_H_
#define _CHRDEV_H_

typedef struct led_node
{
	int which;
	int status;
}led_node_t;

#define LED_MAGIC 'q'
#define LEDON  _IOW(LED_MAGIC, 0, struct led_node)
#define LEDOFF _IOW(LED_MAGIC, 1, struct led_node)

#endif /* chrdev.h */

2.4 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-05.02.00.10/board-support/linux-4.14.79/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

2.5 输出结果

root@am335x-evm:~# insmod demo.ko 
[ 3485.051113] demo_init -- 169.
root@am335x-evm:~# ./app
[ 3486.920446] demo_open -- 28.
[ 3486.923853] demo_ioctl -- 81.
[ 3486.927475] led1 off.
[ 3487.930225] demo_ioctl -- 81.
[ 3487.934385] led1 on.
[ 3488.936823] demo_ioctl -- 81.
[ 3488.939868] led2 off.
[ 3489.948584] demo_ioctl -- 81.
[ 3489.951628] led2 on.
[ 3490.960095] demo_ioctl -- 81.
[ 3490.964276] led3 off.
[ 3491.966745] demo_ioctl -- 81.
[ 3491.969787] led3 on.
[ 3492.972221] demo_ioctl -- 81.
[ 3492.975268] led4 off.
[ 3493.977737] demo_ioctl -- 81.
[ 3493.980786] led4 on.
[ 3494.992661] demo_release -- 35.
root@am335x-evm:~# rmmod demo.ko 
[ 3503.501455] demo_exit -- 206.
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 Linux 中,可以使用 ioctl 系统调用来控制设备 I/O。下面我将以控制 GPIO(通用输入输出)为例,介绍如何使用 ioctl 控制一个 I/O。 1. 打开设备文件 首先,需要打开对应的 GPIO 设备文件。比如,在 Raspberry Pi 上,GPIO 设备文件一般为 /dev/gpiochip0。 ```c int fd = open("/dev/gpiochip0", O_RDWR); if (fd == -1) { perror("open"); return 1; } ``` 2. 设置 GPIO 方向 GPIO 可以作为输入或输出,需要先设置 GPIO 的方向。可以使用 ioctl 系统调用,发送 GPIO_SET_DIRECTION 命令,来设置 GPIO 的方向。GPIO_SET_DIRECTION 命令的请求码为 0x40046B04,请求参数为一个指向 gpiochip_ioctl_data 结构体的指针,其中 direction 字段表示 GPIO 的方向,0 表示输入,1 表示输出。 ```c #include <linux/gpio.h> struct gpiochip_ioctl_data data; data.lineoffset = 17; // GPIO 编号 data.flags = GPIOHANDLE_REQUEST_OUTPUT; strcpy(data.consumer_label, "mygpio"); int ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &data); if (ret == -1) { perror("ioctl"); close(fd); return 1; } ``` 3. 控制 GPIO 输出 设置 GPIO 方向后,就可以通过 ioctl 系统调用,发送 GPIOHANDLE_SET_LINE_VALUES_IOCTL 命令,来控制 GPIO 输出。GPIOHANDLE_SET_LINE_VALUES_IOCTL 命令的请求码为 0xc0484b2f,请求参数为一个指向 gpiohandle_data 结构体的指针,其中 values 数组表示要输出的值,数组长度为 lines 数组的长度。 ```c #include <linux/gpio.h> struct gpiohandle_data data; data.values[0] = 1; // 设置输出值为高电平 data.values[1] = 0; data.lines = 2; // GPIO 数量 ret = ioctl(data.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); if (ret == -1) { perror("ioctl"); close(fd); return 1; } ``` 4. 关闭设备文件 完成操作后,需要关闭设备文件。 ```c close(fd); ``` 以上就是使用 ioctl 系统调用控制 GPIO 的基本步骤。需要注意的是,GPIO 的具体控制方式和参数格式可能因设备而异,使用 ioctl 进行 I/O 操作需要注意安全性和稳定性问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值