Linux Platform设备驱动分析3

Linux Platform设备驱动分析3(基于Linux6.6)---应用解析介绍

一、platform 驱动的工作过程

platform模型驱动编程,需要实现platform_device(设备)platform_driver(驱动)platform(虚拟总线)上的注册、匹配,相互绑定,然后再做为一个普通的字符设备进行相应的应用,总之如果编写的是基于字符设备的platform驱动,在遵循并实现platform总线上驱动与设备的特定接口的情况下,最核心的还是字符设备的核心结构:cdev、 file_operations(他包含的操作函数接口)、dev_t(设备号)、设备文件(/dev)等,因为用platform机制编写的字符驱动,它的本质是字符驱动。

platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳。

1.1、platform驱动模型三个对象:platform总线platform设备platform驱动

在 Linux 内核中,platform 驱动模型由三个主要的对象组成:platform 总线(platform bus)、platform 设备(platform device)、platform 驱动(platform driver)。这三个对象共同协作,形成了平台设备驱动的工作模型。下面详细介绍这三个对象及其各自的作用和相互关系:

1. platform 总线(platform bus)

  • 定义: platform 总线是一种特殊的总线类型,用于连接那些不通过标准总线(如 PCI、I2C、SPI 等)连接到 CPU 或处理器的设备。通常这些设备直接连接到芯片或系统的某个地方,或是通过某个 SoC(系统级芯片)与处理器通信。

  • 作用: platform 总线是设备和驱动之间的媒介。设备通过 platform 总线与驱动进行绑定,驱动则通过该总线与设备进行交互。通常,platform 总线是内核中的一个虚拟总线,设备通过它来注册到内核,而驱动则通过该总线来管理硬件设备。

  • 特点:

    • platform 总线不依赖于硬件总线协议,而是直接映射到硬件设备(通常是嵌入式平台中的 SoC)。
    • 与其他总线(如 PCI、USB)不同,platform 总线没有具体的硬件接口,而是用于描述与硬件直接相关的设备。
  • 平台总线的注册: platform 总线在内核中通常是自动注册的,不需要显式地创建。内核的 platform_driverplatform_device 使用此总线来进行设备和驱动的管理。

2. platform 设备(platform device)

  • 定义: platform device 是一种直接连接到系统处理器(通常是 SoC 或其他芯片)的设备。它没有标准总线协议(如 PCI、I2C 等)来管理硬件,而是通过 platform 总线和驱动直接交互。

  • 作用: platform device 表示实际的硬件设备,在内核中用结构体 platform_device 来表示。它是一个硬件设备实例,通常由嵌入式硬件平台或者开发板上的某个设备构成。

  • 平台设备的创建:

    • 可以通过手动创建(例如 platform_device_register())或通过设备树(Device Tree)来描述设备。
    • 每个 platform_device 结构体包含了该设备的资源、IO 地址、硬件信息、设备树描述等信息。
  • 平台设备的常见字段:

    • name: 设备的名字。
    • id: 设备的 ID(通常用于标识多个相同类型的设备)。
    • dev: 指向 device 结构体的指针,代表与设备相关的通用信息。
    • num_resources: 设备使用的资源数量(如内存、IO端口、中断等)。
    • resource: 设备的资源信息,如内存区、IO端口、IRQ等。

    示例代码(设备注册):

  • struct platform_device *pdev;
    pdev = platform_device_register_simple("my_platform_device", -1, NULL, 0);
    
  • 与驱动的绑定: platform_device 会通过 platform_driver 与具体的硬件驱动进行绑定,从而使驱动能够管理设备并进行操作。

3. platform 驱动(platform driver)

  • 定义: platform_driver 是用于管理 platform_device 的驱动,通常用于嵌入式系统或者没有标准总线的设备。它通过 platform_driver 结构体来定义驱动的功能,并与特定的设备进行匹配和管理。

  • 作用: platform_driver 用于描述设备驱动与平台设备之间的交互。驱动程序在内核加载时,会通过设备树或硬编码的方式与特定的 platform_device 进行绑定。当设备被注册到系统时,驱动会根据设备类型或设备树的匹配信息来初始化硬件设备,配置资源和中断等。

  • 平台驱动的常见字段:

    • driver.name: 驱动名称,用于驱动与设备的匹配。
    • driver.of_match_table: 设备树匹配表,用于根据设备树中的信息选择合适的驱动。
    • probe: 设备初始化函数,当设备和驱动匹配时,会调用此函数来初始化设备。
    • remove: 卸载函数,当设备被移除时,会调用此函数来清理资源。
    • shutdown: 关机时调用的函数。
  • 平台驱动的注册: platform_driver_register() 用于将驱动注册到内核,使其能够管理与之匹配的设备。

    示例代码(平台驱动注册):

  • static struct platform_driver my_driver = {
        .driver = {
            .name = "my_platform_device",  // 驱动名称
            .of_match_table = my_driver_of_match, // 设备树匹配表
        },
        .probe = my_driver_probe,  // 设备初始化函数
        .remove = my_driver_remove, // 设备卸载函数
    };
    
    platform_driver_register(&my_driver);
    
  • 与设备的匹配: platform_driver 通过设备树中的 compatible 字段与设备进行匹配,或者通过设备的名称(name)进行匹配。如果匹配成功,内核将调用驱动的 probe 函数来初始化设备。

4. 三个对象的关系与协作

这三个对象是 Linux 平台驱动模型中的核心组成部分,彼此之间有密切的协作关系。

  1. 平台总线(platform bus)提供了设备和驱动之间的通信通道。设备通过 platform 总线注册到系统,而驱动通过该总线与设备绑定。

  2. 平台设备(platform device)是实际硬件设备的代表,它在内核中描述了硬件设备的信息,并通过 platform 总线与驱动连接。设备可以通过设备树或者手动注册的方式被创建。

  3. 平台驱动(platform driver)用于管理 platform_device 设备,通常用于不需要标准总线的设备,如直接连接到处理器或 SoC 的外设。驱动通过设备名称或者设备树信息与设备匹配,一旦匹配成功,就会调用驱动的 probe 函数进行设备的初始化工作。

1.2、具体platform驱动的工作过程是什么呢?

平台驱动的工作流程可以分为以下几个主要步骤:

1.驱动注册(Platform Driver Registration)

平台驱动通过 platform_driver_register() 来注册到内核。这个过程告诉内核该驱动的相关信息(包括设备匹配信息、驱动操作函数等),内核将根据设备树中描述的设备信息来决定是否加载该驱动。

驱动结构体定义如下:

static struct platform_driver my_driver = {
    .driver = {
        .name = "my_platform_device", // 驱动名称
        .of_match_table = my_driver_of_match, // 设备树匹配表
    },
    .probe = my_driver_probe, // 设备初始化函数
    .remove = my_driver_remove, // 设备卸载函数
};

其中,.probe 函数用于设备的初始化,.remove 函数则用于设备的卸载。of_match_table 用于设备树与驱动匹配。

2.设备匹配(Device Matching)

平台设备通常会在设备树中进行描述。设备树通过 compatible 属性与驱动程序中的 of_device_id 匹配数组进行匹配,从而确定是否加载该驱动。

例如:

设备树中的描述:

dts
my_platform_device {
    compatible = "my,platform-device";
    reg = <0x1000 0x100>;
    interrupt-parent = <&intc>;
    interrupts = <5>;
};

驱动中的匹配表:

static const struct of_device_id my_driver_of_match[] = {
    { .compatible = "my,platform-device", },
    {},
};

当内核解析设备树并找到与 compatible 匹配的设备节点时,会调用与之匹配的驱动的 probe 函数。

3.Probe 函数的调用(设备初始化)

probe 函数用于初始化设备,它会在设备和驱动匹配成功后被调用。probe 函数中通常会完成设备的初始化工作,例如:

  • 分配资源(内存、IO端口、GPIO等)。
  • 注册设备。
  • 初始化硬件。
static int my_driver_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int ret;

    // 读取设备树信息
    int irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(dev, "Failed to get IRQ\n");
        return irq;
    }

    // 请求中断
    ret = devm_request_irq(dev, irq, my_irq_handler, 0, dev_name(dev), dev);
    if (ret) {
        dev_err(dev, "Failed to request IRQ\n");
        return ret;
    }

    // 初始化硬件或注册设备
    // 例如,配置GPIO、I2C、SPI等

    dev_info(dev, "Device initialized successfully\n");
    return 0;
}

probe函数中,通常会执行以下操作:

  • 获取设备资源(如中断、IO端口、内存区域等)。
  • 初始化硬件或外围设备。
  • 通过调用 devm_* API 函数来自动管理资源的分配和释放。

4.设备使用和操作

一旦设备初始化完成,用户空间或者内核空间的其他部分就可以开始与设备进行交互。例如,GPIO、I2C、SPI等接口会被初始化并配置,以便外部设备可以通过它们与系统进行通信。

5.Remove 函数的调用(设备卸载)

当设备被移除时,内核会调用 remove 函数。remove 函数用于清理设备资源,如释放内存、取消注册的中断、关闭硬件等。

static int my_driver_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;

    // 清理资源
    devm_free_irq(dev, platform_get_irq(pdev, 0), dev);
    // 其他资源的清理,例如释放GPIO、关闭设备等

    dev_info(dev, "Device removed successfully\n");
    return 0;
}

6.驱动卸载(Platform Driver Unregistration)

当设备被卸载时,platform_driver_unregister() 函数会被调用,驱动会从内核中注销,内核将不再使用该驱动来管理硬件。

二、实现platform 驱动与设备的详细过程

1、思考问题?

在分析platform 之前,可以先思考一下下面的问题:

a -- 为什么要用 platform 驱动?不用platform驱动可以吗?

b -- 设备驱动中引入platform 概念有什么好处?

2、platform_device 结构体 VS platform_driver 结构体

      这两个结构体分别描述了设备和驱动,二者有什么关系呢?先看一下具体结构体对比

设备(硬件部分):中断号,寄存器,DMA等

                   platform_device 结构体

  驱动(软件部分)

                          platform_driver 结构体       

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;

    /* arch specific additions */

    struct pdev_archdata    archdata;

};

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;  八字

};

设备实例:

static struct platform_device hello_device=

{

    .name = "bigbang",

    .id = -1,

    .dev.release = hello_release,

};

驱动实例:

static struct platform_driver hello_driver=

{

    .driver.name = "bigbang",

    .probe = hello_probe,

    .remove = hello_remove,

};

前面提到,实现platform模型的过程就是总线对设备和驱动的匹配过程 。

platform 模型的匹配过程简化流程图

+------------------------+
|  初始化设备树或平台设备  |<--------------------------------------------------+
+------------------------+                                                   |
        |                                                                |
        v                                                                v
+----------------------------+                                +-------------------------+
|   platform_device 注册      |                                | platform_driver 注册    |
|    (设备信息到内核)         |                                | (驱动信息到内核)         |
+----------------------------+                                +-------------------------+
        |                                                                |
        v                                                                v
+------------------------+                                    +---------------------------+
|    platform_bus 总线     |                                    |  platform 驱动匹配过程     |
|    创建并管理设备与驱动 |                                    |  (设备树描述与驱动名称匹配) |
+------------------------+                                    +---------------------------+
        |                                                                |
        v                                                                v
+------------------------+                                     +-------------------------+
|  设备与驱动匹配         | <----------------------------------|   驱动与设备绑定         |
| (平台设备与驱动匹配)    |                                     | (驱动初始化与设备关联)   |
+------------------------+                                     +-------------------------+
        |                                                                |
        v                                                                v
+-------------------------+                                     +---------------------------+
|  注册驱动程序并启动      | <---------------------------------|    驱动初始化             |
| (驱动加载,设备开始工作) |                                     |  (驱动开始控制设备)       |
+-------------------------+                                     +---------------------------+

详细说明每一步骤

(1)初始化设备树或平台设备

设备树(Device Tree)是描述硬件平台的关键数据结构。它包含了关于设备的基本信息,如设备类型、配置和连接等。设备树的解析通常发生在 Linux 内核启动时,系统会扫描设备树并提取设备信息,或者平台设备通过代码注册。

(2)platform_device 注册

platform_device 是平台设备的描述结构体,在此步骤中,设备通过 platform_device_register() 函数注册到内核中。它包含设备的资源信息(如内存区域、I/O 区域和中断),以及设备的名称和其他设备特性。

(3)platform_driver 注册

platform_driver 是一个用于操作平台设备的驱动描述结构体。在此步骤中,驱动通过 platform_driver_register() 函数注册到内核中。驱动结构体定义了驱动程序的一些操作函数(如 proberemove 等),并且该结构体会与平台设备进行匹配。

(4)platform 总线管理设备和驱动

内核通过 platform_bus 总线来管理和协调平台设备和驱动程序。platform_bus 本身并不直接对设备进行处理,而是充当平台设备和驱动之间的桥梁,负责设备与驱动的匹配与管理。

(5)设备与驱动匹配

platform 模型中,设备和驱动的匹配主要通过设备的名称、ID 或设备树中的 compatible 字段来实现。内核在 platform_bus 总线上遍历所有已注册的设备和驱动,尝试找到匹配的设备和驱动。驱动匹配的标准通常是设备树中的 compatible 字段与驱动的名称或ID匹配。

(6)驱动与设备绑定

匹配成功后,内核会通过 probe 函数将驱动与设备绑定。这时,驱动的初始化函数(probe)会被调用,驱动程序可以在这里初始化设备并配置硬件资源(如 I/O 区域、中断、DMA 等)。

(7)注册驱动程序并启动

驱动程序被成功加载并开始控制设备。此时,设备开始工作,驱动程序将负责设备的后续操作,包括 I/O 请求、设备状态管理等。

3、设备资源结构体

在struct platform_device 结构体中有一重要成员 struct resource *resource

struct resource {
	resource_size_t start;  资源起始地址   
	resource_size_t end;   资源结束地址
	const char *name;      
	unsigned long flags;   区分是资源什么类型的
	struct resource *parent, *sibling, *child;
};
 
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_IRQ        0x00000400   

lags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ  这两种。start 和 end 的含义会随着 flags而变更,如

a -- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;  

b -- flags为 IORESOURCE_IRQ   时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值; 

下面看一个实例:

static struct  resource beep_resource[] =
{
	[0] = {
        	.start = 0x114000a0,
		.end = 0x114000a0+0x4,
        	.flags = IORESOURCE_MEM,
	},
 
	[1] = {
        	.start = 0x139D0000,
        	.end = 0x139D0000+0x14,
        	.flags = IORESOURCE_MEM,
	},
};

4、将字符设备添加到 platform的driver中

platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳,下面看一下添加的过程:

static struct file_operations hello_ops=
{
	.open = hello_open,
	.release = hello_release,
	.unlocked_ioctl = hello_ioctl,
};
 
static int hello_remove(struct platform_device *pdev)
{
	注销分配的各种资源
}
 
static int hello_probe(struct platform_device *pdev)
{
	1.申请设备号
	2.cdev初始化注册,&hello_ops
	3.从pdev读出硬件资源
	4.对硬件资源初始化,ioremap,request_irq( )
}
 
static int hello_init(void)
{
	只注册 platform_driver
}
 
static void hello_exit(void)
{
	只注销 platform_driver
}

  可以看到,模块加载和卸载函数仅仅通过paltform_driver_register()、paltform_driver_unregister() 函数进行 platform_driver 的注册和注销,而原先注册和注销字符设备的工作已经被移交到 platform_driver 的 probe() 和 remove() 成员函数中

5、platform是如何匹配device和driver

这时就该总线出场了,系统为platform总线定义了一个bus_type 的实例platform_bus_type,其定义如下:

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

 其又是怎样工作的呢?

__platform_driver_register()
{
	drv->driver.bus = &platform_bus_type; 
}

在 platform_bus_type 中调用 了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);
 
	匹配设备树信息,如果有设备树,就调用 of_driver_match_device() 函数进行匹配
	if (of_driver_match_device(dev, drv))
		return 1;
 
 
	匹配id_table
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;
 
	最基本匹配规则
	return (strcmp(pdev->name, drv->name) == 0);
}

6、解决问题

现在可以回答这两个问题了

a -- 为什么要用 platform 驱动?不用platform驱动可以吗?

b -- 设备驱动中引入platform 概念有什么好处?

      引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;

      设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

三、举例应用

这是一个蜂鸣器的驱动, 下面来看一下,套上platform 外壳后的程序:

1、device.c

#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
 
static struct resource beep_resource[] =
{
	[0] ={
		.start = 0x114000a0,
		.end =  0x114000a0 + 0x4,
		.flags = IORESOURCE_MEM,
	},
 
	[1] ={
		.start = 0x139D0000,
		.end =  0x139D0000 + 0x14,
		.flags = IORESOURCE_MEM,
	}
};
 
static void hello_release(struct device *dev)
{
	printk("hello_release\n");
	return ;
}
 
 
 
static struct platform_device hello_device=
{
    .name = "bigbang",
    .id = -1,
    .dev.release = hello_release,
    .num_resources = ARRAY_SIZE(beep_resource),
    .resource = beep_resource,
};
 
static int hello_init(void)
{
	printk("hello_init");
	return platform_device_register(&hello_device);
}
 
static void hello_exit(void)
{
	printk("hello_exit");
	platform_device_unregister(&hello_device);
	return;
}
 
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

2、driver.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/io.h>
 
static int major = 250;
static int minor=0;
static dev_t devno;
static struct class *cls;
static struct device *test_device;
         
#define TCFG0         0x0000               
#define TCFG1         0x0004                            
#define TCON          0x0008             
#define TCNTB0        0x000C          
#define TCMPB0        0x0010           
 
static unsigned int *gpd0con;
static void *timer_base;
 
#define  MAGIC_NUMBER    'k'
#define  BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define  BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define  BEEP_FREQ   _IO(MAGIC_NUMBER   ,2)
 
static void fs4412_beep_init(void)
{	
	writel ((readl(gpd0con)&~(0xf<<0)) | (0x2<<0),gpd0con);
	writel ((readl(timer_base +TCFG0  )&~(0xff<<0)) | (0xff <<0),timer_base +TCFG0); 
	writel ((readl(timer_base +TCFG1 )&~(0xf<<0)) | (0x2 <<0),timer_base +TCFG1 ); 
 
	writel (500, timer_base +TCNTB0  );
	writel (250, timer_base +TCMPB0 );
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x2 <<0),timer_base +TCON ); 
}
 
void fs4412_beep_on(void)
{
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x9 <<0),timer_base +TCON );
}
 
void fs4412_beep_off(void)
{
	writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x0 <<0),timer_base +TCON );
}
 
static void beep_unmap(void)
{
		iounmap(gpd0con);
		iounmap(timer_base);
}
 
static int beep_open (struct inode *inode, struct file *filep)
{
	fs4412_beep_on();
	return 0;
}
 
static int beep_release(struct inode *inode, struct file *filep)
{
	 fs4412_beep_off();
	 return 0;
}
 
#define BEPP_IN_FREQ 100000
static void beep_freq(unsigned long arg)
{
	writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0  );
	writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );
 
}
 
static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
		case BEEP_ON:
			fs4412_beep_on();
			break;
		case BEEP_OFF:
			fs4412_beep_off();
			break;
		case BEEP_FREQ:
			beep_freq( arg );
			break;
		default :
			return -EINVAL;
	}
	return 0;
}
 
static struct file_operations beep_ops=
{
	.open     = beep_open,
	.release = beep_release,
	.unlocked_ioctl      = beep_ioctl,
};
 
static int beep_probe(struct platform_device *pdev)
{
	int ret;	
	printk("match ok!");
	
	gpd0con = ioremap(pdev->resource[0].start,pdev->resource[0].end - pdev->resource[0].start);
	timer_base = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start);
 
	devno = MKDEV(major,minor);
	ret = register_chrdev(major,"beep",&beep_ops);
 
	cls = class_create(THIS_MODULE, "myclass");
	if(IS_ERR(cls))
	{
		unregister_chrdev(major,"beep");
		return -EBUSY;
	}
 
	test_device = device_create(cls,NULL,devno,NULL,"beep");//mknod /dev/hello
	if(IS_ERR(test_device))
	{
		class_destroy(cls);
		unregister_chrdev(major,"beep");
		return -EBUSY;
	}
	
	fs4412_beep_init();
	
	return 0;
}
 
static int beep_remove(struct platform_device *pdev)
{
	beep_unmap();
	device_destroy(cls,devno);
	class_destroy(cls);	
	unregister_chrdev(major,"beep");
 
	return 0;
}
 
 
static struct platform_driver beep_driver=
{
    .driver.name = "bigbang",
    .probe = beep_probe,
    .remove = beep_remove,
};
 
 
static int beep_init(void)
{
	printk("beep_init");
	
	return platform_driver_register(&beep_driver);
}
 
static void beep_exit(void)
{
	printk("beep_exit");
	platform_driver_unregister(&beep_driver);
	
	return;
}
 
 
MODULE_LICENSE("GPL");
module_init(beep_init);
module_exit(beep_exit);

3、makefile   

ifneq  ($(KERNELRELEASE),)
obj-m:=device.o driver.o
$(info "2nd")
else
#KDIR := /lib/modules/$(shell uname -r)/build
KDIR := /home/fs/linux/linux-3.14-fs4412
PWD:=$(shell pwd)
all:
	$(info "1st")
	make -C $(KDIR) M=$(PWD) modules
clean:
	rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order
endif

4、test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
main()
{
	int fd,i,lednum;
 
	fd = open("/dev/beep",O_RDWR);
	if(fd<0)
	{
		perror("open fail \n");
		return ;
	}
	
	sleep(10);
	close(fd);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值