IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux驱动开发篇(四)—— platform平台设备驱动 -> 正文阅读

[系统运维]linux驱动开发篇(四)—— platform平台设备驱动

linux系列目录:
linux基础篇(一)——GCC和Makefile编译过程
linux基础篇(二)——静态和动态链接
ARM裸机篇(一)——i.MX6ULL介绍
ARM裸机篇(二)——i.MX6ULL启动过程
ARM裸机篇(三)——i.MX6ULL第一个裸机程序
ARM裸机篇(四)——重定位和地址无关码
ARM裸机篇(五)——异常和中断
linux系统移植篇(一)—— linux系统组成
linux系统移植篇(二)—— Uboot使用介绍
linux系统移植篇(三)—— Linux 内核使用介绍
linux系统移植篇(四)—— 根文件系统使用介绍
linux驱动开发篇(一)—— Linux 内核模块介绍
linux驱动开发篇(二)—— 字符设备驱动框架
linux驱动开发篇(三)—— 总线设备驱动模型
linux驱动开发篇(四)—— platform平台设备驱动
linux驱动开发篇(五)—— linux驱动面向对象的编程思想
linux驱动开发篇(六)—— 设备树的引入


一、平台设备驱动

在设备驱动模型中,引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们 SOC 中物理总线的概念并不严格相等:

  • 物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
  • 驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为它们进行配对。

一般对于 I2C、 SPI、 USB 这些常见类型的物理总线来说, Linux 内核会自动创建与之相应的驱动总线,因此 I2C 设备、 SPI 设备、 USB 设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。它们也就没有相应的物理总线,比如 led、 rtc 时钟、蜂鸣器、按键等等, Linux 内核将不会为它们创建相应的驱动总线。为了使这部分设备的驱动开发也能够遵循设备驱动模型, Linux 内核引入了一种虚拟的总线——平台总线(platform bus)

平台设备驱动的核心依然是 Linux 设备驱动模型,平台设备使用 platform_device 结构体来进行表示,其继承了设备驱动模型中的device 结构体。而平台驱动使用 platform_driver 结构体来进行表示,其则是继承了设备驱动模型中的 device_driver结构体。

1、平台总线

内核中使用 bus_type 来抽象描述系统中的总线,平台总线结构体原型如下所示:
platform_bus_type 结 构 体 (内 核 源码/driver/base/platform.c)

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

内核用 platform_bus_type 来描述平台总线,该总线在 linux 内核启动的时候自动进行注册。

这里重点是 platform 总线的 match 函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线,它都必须实例化该函数指针。 platform_match 的函数原型如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

platform 总线提供了四种匹配方式,并且这四种方式存在着优先级:设备树机
制 >ACPI 匹配模式 >id_table 方式 > 字符串比较。

2、平台设备

内核使用 platform_device 结构体来描述平台设备,platform_device 结 构 体 (内 核 源码/include/linux/platform_device.h)结构体原型如下:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};
  • name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
  • id: 指定设备的编号, Linux 支持同名的设备,而同名设备之间则是通过该编号进行区分;
  • dev: Linux 设备模型中的 device 结构体, linux 内核大量使用了面向对象思想, platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
  • num_resources: 记录资源的个数,当结构体成员 resource 存放的是数组时,需要记录 resource数组的个数,内核提供了宏定义 ARRAY_SIZE 用于计算数组的个数;
  • resource: 平台设备提供给驱动的资源,如 irq, dma,内存等等。该结构体会在接下来的内容进行讲解;
  • id_entry: 平台总线提供的另一种匹配方式,原理依然是通过比较字符串,这部分内容会在平台总线小节中讲,这里的 id_entry 用于保存匹配的结果;

平台设备的工作是为驱动程序提供设备信息, 设备信息包括硬件信息和软件信息两部分。

  • 硬件信息:驱动程序需要使用到什么寄存器,占用哪些中断号、内存资源、 IO 口等等
  • 软件信息:以太网卡设备中的 MAC 地址、 I2C 设备中的设备地址、 SPI 设备的片选信号线等等。

对于硬件信息,使用结构体 struct resource 来保存设备所提供的资源,比如设备使用的中断编号,寄存器物理地址等,结构体原型如下:

struct resource {
 resource_size_t start;
 resource_size_t end;
 const char *name;
 unsigned long flags;
};
  • name: 指定资源的名字,可以设置为 NULL;
  • start、 end: 指定资源的起始地址以及结束地址
  • flags: 用于指定该资源的类型,在 Linux 中,资源包括 I/O、 Memory、 Register、 IRQ、 DMA、Bus 等多种类型,最常见的有以下几种:
资源宏定义描述
IORESOURCE_IO用于 IO 地址空间,对应于 IO 端口映射方式
IORESOURCE_MEM用于外设的可直接寻址的地址空间
IORESOURCE_IRQ用于指定该设备使用某个中断
IORESOURCE_DMA用于指定使用的 DMA 通道

注册/注销平台设备API原型:

int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);

3、平台驱动

内核中使用 platform_driver 结构体来描述平台驱动,platform_driver 结 构 体 (内 核 源
码/include/platform_device.h)结构体原型如下所示:

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};
  • probe: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
  • remove: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是 probe 函数实现操作的逆过程。
  • driver: Linux 设备模型中用于抽象驱动的 device_driver 结构体, platform_driver 继承该结构体,也就获取了设备模型驱动对象的特性;
  • id_table: 表示该驱动能够兼容的设备类型。

platform_device_id 结构体原型如下所示:

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	kernel_ulong_t driver_data;
};

在 platform_device_id 这个结构体中,有两个成员,第一个是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的 name 成员与 platform_device 中的变量 name 进行比较匹配,另一个成员变量 driver_data,则是用于来保存设备的配置。

注册/注销平台驱动API

int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);

4、平台驱动获取设备信息

在学习平台设备的时候,我们知道平台设备使用结构体 resource 来抽象表示硬件信息,而软件信息则可以利用设备结构体 device 中的成员 platform_data 来保存。先看一下如何获取平台设备中结构体 resource 提供的资源。
platform_get_resource() 函数通常会在驱动的 probe 函数中执行,用于获取平台设备提供的资源结构体,最终会返回一个 struct resource 类型的指针,该函数原型如下:

struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
  • dev: 指定要获取哪个平台设备的资源;
  • type: 指定获取资源的类型,如 IORESOURCE_MEM、 IORESOURCE_IO 等;
  • num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。

对于存放在 device 结构体中成员 platform_data 的软件信息,我们可以使用 dev_get_platdata 函数来获取,函数原型如下所示:

static inline void *dev_get_platdata(const struct device *dev)
{
	return dev->platform_data;
}

总结一下平台驱动需要实现 probe 函数,当平台总线成功匹配驱动和设备时,则会调用驱动的 probe 函数,在该函数中使用上述的函数接口来获取资源,以初始化设备,最后
填充结构体 platform_driver,调用 platform_driver_register 进行注册。

二、平台设备驱动实验

把平台设备驱动应用到 LED 字符设备驱动的代码中,实现硬件与软件代码相分离。

1、编程思路

  1. 编写第一个内核模块 led_pdev.c
  2. 在内核模块中定义一个平台设备,并填充 RGB 灯相关设备信息
  3. 在该模块入口函数,注册/挂载这个平台设备
  4. 编写第二个内核模块 led_pdrv.c
  5. 在内核模块中定义一个平台驱动,在 probe 函数中完成字符设备驱动的创建
  6. 在该模块入口函数,注册/挂载这个平台驱动

在平台设备总线上,注册/挂载平台设备和平台驱动时,会自动进行配对。配对成功后,回调执行平台驱动的 probe 函数,从而完成字符设备驱动的创建。

2、定义平台设备

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define CCM_CCGR1 															0x20C406C	//时钟控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 				0x20E006C	//GPIO1_04复用功能选择寄存器
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 				0x20E02F8	//PAD属性设置寄存器
#define GPIO1_GDIR 															0x0209C004	//GPIO方向设置寄存器(输入或输出)
#define GPIO1_DR 																0x0209C000	//GPIO输出状态寄存器

#define CCM_CCGR3 															0x020C4074
#define GPIO4_GDIR 															0x020A8004
#define GPIO4_DR 																0x020A8000

#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020 			0x020E01E0
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020 			0x020E046C

#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019 			0x020E01DC
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019 			0x020E0468

static struct resource rled_resource[] = {
	[0] = DEFINE_RES_MEM(GPIO1_DR, 4),
	[1] = DEFINE_RES_MEM(GPIO1_GDIR, 4),
	[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04, 4),
	[3] = DEFINE_RES_MEM(CCM_CCGR1, 4),
	[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04, 4),
};

static struct resource gled_resource[] = {
	[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
	[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
	[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020, 4),
	[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
	[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020, 4),
};

static struct resource bled_resource[] = {
	[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
	[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
	[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019, 4),
	[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
	[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019, 4),
};
/* not used */ 
static void led_release(struct device *dev)
{

}

/* led hardware information */
unsigned int rled_hwinfo[2] = { 4, 26 };
unsigned int gled_hwinfo[2] = { 20, 12 };
unsigned int bled_hwinfo[2] = { 19, 12 };

/* red led device */ 
static struct platform_device rled_pdev = {
	.name = "led_pdev",
	.id = 0,
	.num_resources = ARRAY_SIZE(rled_resource),
	.resource = rled_resource,
	.dev = {
		.release = led_release,
		.platform_data = rled_hwinfo,
		},
};
/* green led device */ 
static struct platform_device gled_pdev = {
	.name = "led_pdev",
	.id = 1,
	.num_resources = ARRAY_SIZE(gled_resource),
	.resource = gled_resource,
	.dev = {
		.release = led_release,
		.platform_data = gled_hwinfo,
		},
};
/* blue led device */ 
static struct platform_device bled_pdev = {
	.name = "led_pdev",
	.id = 2,
	.num_resources = ARRAY_SIZE(bled_resource),
	.resource = bled_resource,
	.dev = {
		.release = led_release,
		.platform_data = bled_hwinfo,
		},
};

static __init int led_pdev_init(void)
{
	printk("pdev init\n");
	platform_device_register(&rled_pdev);
	platform_device_register(&gled_pdev);
	platform_device_register(&bled_pdev);
	return 0;
}

module_init(led_pdev_init);

static __exit void led_pdev_exit(void)
{
	printk("pdev exit\n");
	platform_device_unregister(&rled_pdev);
	platform_device_unregister(&gled_pdev);
	platform_device_unregister(&bled_pdev);
}

module_exit(led_pdev_exit);

MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");

3、定义平台驱动

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#define DEV_MAJOR 243
#define DEV_NAME  "led"

static struct class *my_led_class;

//结构体led_data来管理我们LED灯的硬件信息
struct led_data {
	unsigned int led_pin;
	unsigned int clk_regshift;

	unsigned int __iomem *va_dr;
	unsigned int __iomem *va_gdir;
	unsigned int __iomem *va_iomuxc_mux;
	unsigned int __iomem *va_ccm_ccgrx;
	unsigned int __iomem *va_iomux_pad;	

	struct cdev led_cdev;

};

static int led_cdev_open(struct inode *inode, struct file *filp)
{
	printk("%s\n", __func__);
	
	struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
	unsigned int val = 0;

	val = readl(cur_led->va_ccm_ccgrx);
	val &= ~(3 << cur_led->clk_regshift);
	val |= (3 << cur_led->clk_regshift);
	writel(val, cur_led->va_ccm_ccgrx);

	writel(5, cur_led->va_iomuxc_mux);

	writel(0x1F838, cur_led->va_iomux_pad);

	val = readl(cur_led->va_gdir);
	val &= ~(1 << cur_led->led_pin);
	val |= (1 << cur_led->led_pin);
	writel(val, cur_led->va_gdir);

	val = readl(cur_led->va_dr);
	val |= (0x01 << cur_led->led_pin);
	writel(val, cur_led->va_dr);

	filp->private_data = cur_led;

	return 0;
}


static int led_cdev_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
			      size_t count, loff_t * ppos)
{
	unsigned long val = 0;
	unsigned long ret = 0;

	int tmp = count;

	struct led_data *cur_led = (struct led_data *)filp->private_data;

	kstrtoul_from_user(buf, tmp, 10, &ret);

	val = readl(cur_led->va_dr);
	if (ret == 0)
		val &= ~(0x01 << cur_led->led_pin);
	else
		val |= (0x01 << cur_led->led_pin);

	writel(val, cur_led->va_dr);
	*ppos += tmp;

	return tmp;
}

static struct file_operations led_cdev_fops = {
	.open = led_cdev_open,
	.release = led_cdev_release,
	.write = led_cdev_write,
};


//probe函数中,驱动需要去提取设备的资源,完成字符设备的注册等工作
static int led_pdrv_probe(struct platform_device *pdev)
{
	struct led_data *cur_led;
	unsigned int *led_hwinfo;
	
	struct resource *mem_dr;
	struct resource *mem_gdir;
	struct resource *mem_iomuxc_mux;
	struct resource *mem_ccm_ccgrx;
	struct resource *mem_iomux_pad; 	

	dev_t cur_dev;

	int ret = 0;
	
	printk("led platform driver probe\n");

	//第一步:提取平台设备提供的资源
	//devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小
	cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
	if(!cur_led)
		return -ENOMEM;
	led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
	if(!led_hwinfo)
		return -ENOMEM;

	/* get the pin for led and the reg's shift */
	//dev_get_platdata函数获取私有数据,得到LED灯的寄存器偏移量,并赋值给cur_led->led_pin和cur_led->clk_regshift
	led_hwinfo = dev_get_platdata(&pdev->dev);

	cur_led->led_pin = led_hwinfo[0];
	cur_led->clk_regshift = led_hwinfo[1];
	/* get platform resource */
	//利用函数platform_get_resource可以获取到各个寄存器的地址
	mem_dr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	mem_iomuxc_mux = platform_get_resource(pdev, IORESOURCE_MEM, 2);
	mem_ccm_ccgrx = platform_get_resource(pdev, IORESOURCE_MEM, 3);
	mem_iomux_pad = platform_get_resource(pdev, IORESOURCE_MEM, 4);

	//使用devm_ioremap将获取到的寄存器地址转化为虚拟地址
	cur_led->va_dr =
	    devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr));
	cur_led->va_gdir =
	    devm_ioremap(&pdev->dev, mem_gdir->start, resource_size(mem_gdir));
	cur_led->va_iomuxc_mux =
	    devm_ioremap(&pdev->dev, mem_iomuxc_mux->start,
			 resource_size(mem_iomuxc_mux));
	cur_led->va_ccm_ccgrx =
	    devm_ioremap(&pdev->dev, mem_ccm_ccgrx->start,
			 resource_size(mem_ccm_ccgrx));
	cur_led->va_iomux_pad =
	    devm_ioremap(&pdev->dev, mem_iomux_pad->start,
			 resource_size(mem_iomux_pad));

	//第二步:注册字符设备
	cur_dev = MKDEV(DEV_MAJOR, pdev->id);

	register_chrdev_region(cur_dev, 1, "led_cdev");

	cdev_init(&cur_led->led_cdev, &led_cdev_fops);

	ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
	if(ret < 0)
	{
		printk("fail to add cdev\n");
		goto add_err;
	}
	
	device_create(my_led_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id);

	/* save as drvdata */ 
	//platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中
	platform_set_drvdata(pdev, cur_led);

	return 0;

add_err:
	unregister_chrdev_region(cur_dev, 1);
	return ret;
}


static int led_pdrv_remove(struct platform_device *pdev)
{
	dev_t cur_dev; 
	//platform_get_drvdata,获取当前LED灯对应的结构体
	struct led_data *cur_data = platform_get_drvdata(pdev);


	printk("led platform driver remove\n");

	cur_dev = MKDEV(DEV_MAJOR, pdev->id);

	//cdev_del删除对应的字符设备
	cdev_del(&cur_data->led_cdev);

	//删除/dev目录下的设备
	device_destroy(my_led_class, cur_dev);

	//unregister_chrdev_region, 注销掉当前的字符设备编号
	unregister_chrdev_region(cur_dev, 1);

	return 0;
}

static struct platform_device_id led_pdev_ids[] = {
	{.name = "led_pdev"},
	{}
};

MODULE_DEVICE_TABLE(platform, led_pdev_ids);

//led_pdrv中定义了两种匹配模式
//平台总线匹配过程中 ,只会根据id_table中的name值进行匹配,若和平台设备的name值相等,则表示匹配成功; 反之,则匹配不成功,表明当前内核没有该驱动能够支持的设备。
static struct platform_driver led_pdrv = {
	
	.probe = led_pdrv_probe,
	.remove = led_pdrv_remove,
	.driver.name = "led_pdev",
	.id_table = led_pdev_ids,
};


static __init int led_pdrv_init(void)
{
	printk("led platform driver init\n");
	//class_create,来创建一个led类
	my_led_class = class_create(THIS_MODULE, "my_leds");
	//调用函数platform_driver_register,注册我们的平台驱动结构体,这样当加载该内核模块时, 就会有新的平台驱动加入到内核中。 第20-27行,注销
	platform_driver_register(&led_pdrv);

	return 0;
}
module_init(led_pdrv_init);


static __exit void led_pdrv_exit(void)
{
	printk("led platform driver exit\n");	
	platform_driver_unregister(&led_pdrv);
	class_destroy(my_led_class);
}
module_exit(led_pdrv_exit);

MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");

4、编译

makefile文件

KERNEL_DIR=../ebf_linux_kernel

ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export  ARCH  CROSS_COMPILE

obj-m := led_pdev.o led_pdrv.o

all:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
modules clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean	

编译成功后,实验目录下会生成两个名为“led_pdev.ko”、” led_pdrv.ko”的驱动模块文件。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-06-16 21:53:05  更:2022-06-16 21:54:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 2:15:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码