本文章:
在Linux下,编写驱动程序实际上是实现对设备文件对应操作函数的编写,而这些操作函数是结构体file_operations中函数指针所指函数的具体实现。
因此,file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
Linux作为一个庞大的操作系统,肯定是不允许用户直接通过物理地址对存储空间进行操作的,所以为了保证CPU执行指令时可正确访问存储单元,系统需要将物理地址和用户程序中的逻辑地址进行对应的转换 ,这一过程称为地址映射。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
作用:向内核申请设备号
注意:该函数需要我们指定要申请的设备号,不能与已分配的设备号冲突
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
作用:向内核动态申请设备号
优点:无需用户指定设备号,由系统为用户分配
void unregister_chrdev_region(dev_t from, unsigned count)
作用:释放已经分配出去的设备号
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
作用:对cdev变量进行初始化
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
作用:于向 Linux 系统添加字符设备
void cdev_del(struct cdev *p)
作用:从 Linux 内核中删除相应的字符设备
struct class *class_create (struct module *owner, const char *name)
作用:创建class类
void class_destroy(struct class *cls)
作用:删除掉创建的类
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
作用:创建设备
void device_destroy(struct class *class, dev_t devt)
作用:卸载创建的设备
void __iomem *ioremap(phys_addr_t offset, size_t size)
作用:将物理地址映射为虚拟地址
void iounmap(void __iomem *addr)
作用:释放掉 ioremap 函数所做的映射
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
作用:读取地址为addr的存储内容
区别:8bit、16bit和32bit的读操作
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
作用:向地址为addr的存储空间写入数据
区别:8bit、16bit和32bit的写操作
#include
#include
#include
#include
#include
#include
#include
/* 寄存器物理地址 */
#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 *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 led_cdev_type
{
dev_t id;
struct cdev cdev;
struct class *class;
struct device *device;
};
struct led_cdev_type led_cdev;
/**
* @brief 文件操作结构体-write函数
*
*/
ssize_t led_write(struct file *file, const char __user *buf, size_t len, loff_t *loff_t)
{
unsigned char data[1];
uint32_t temp;
unsigned long err;
/* 获取应用层数据 */
err = copy_from_user(data, buf, 1);
/* 根据传入数据设置LED的开关状态 */
if(data[0] == '1')
{
temp = readl(GPIO1_DR);
temp &= ~(1 << 3);
writel(temp, GPIO1_DR);
}
else
{
temp = readl(GPIO1_DR);
temp |= (1 << 3);
writel(temp, GPIO1_DR);
}
return 0;
}
/**
* @brief 文件操作结构体
*
*/
static struct file_operations led_ops = {
.owner = THIS_MODULE,
.write = led_write,
};
/**
* @brief 配置LED的引脚
*
*/
static void led_IO_config(void)
{
uint32_t temp;
/* 1、地址映射 */
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);
/* 2、使能时钟 */
temp = readl(CCM_CCGR1);
temp &= ~(3 << 26);
temp |= (3 << 26);
writel(temp, CCM_CCGR1);
/* 3、IO复用和配置 */
writel(5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置GPIO方向 */
temp = readl(GPIO1_GDIR);
temp |= (1 << 3);
writel(temp, GPIO1_GDIR);
/* 5、默认状态:关闭 */
temp = readl(GPIO1_DR);
temp |= (1 << 3);
writel(temp, GPIO1_DR);
}
/**
* @brief 取消地址映射
*
*/
static void led_IO_unmap(void)
{
iounmap(CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
}
/**
* @brief 入口函数
*
*/
static int __init led_init(void)
{
/* 1、申请设备号 */
alloc_chrdev_region(&led_cdev.id, 0, 1, "my_led");
/* 2、初始化字符设备并向内核添加字符设备 */
cdev_init(&led_cdev.cdev, &led_ops);
cdev_add(&led_cdev.cdev, led_cdev.id, 1);
/* 3、创建类 */
led_cdev.class = class_create(THIS_MODULE, "my_led");
/* 4、创建设备 */
led_cdev.device = device_create(led_cdev.class, NULL, led_cdev.id, NULL, "my_led");
/* 5、LED IO配置 */
led_IO_config();
return 0;
}
/**
* @brief 出口函数
*
*/
static void __exit led_exit(void)
{
/*5、 取消地址映射 */
led_IO_unmap();
/* 4、删除设备 */
device_destroy(led_cdev.class, led_cdev.id);
/* 3、删除类 */
class_destroy(led_cdev.class);
/* 2、删除字符设备 */
cdev_del(&led_cdev.cdev);
/* 1、释放设备号 */
unregister_chrdev_region(led_cdev.id, 1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LSW");
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
int err;
if(argc != 3)
{
printf("Error: Invalid number of arguments\n");
return -1;
}
/* 打开文件 */
fd = open(argv[1], O_RDWR);
/* 写文件 */
write(fd, argv[2], sizeof(char));
/* 关闭文件 */
close(fd);
return 0;
}
export ARCH=arm
export CROSS_COMPILE=(对应的编译器)
KERNEL_DIR := (Linux内核路径)
CURRENT_DIR := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean