Linux下的Platform总线驱动

补充:
平台数据是定义在BSP里面的上层platform设备的一些配置。主要是因为platform涉及的都是一些总线,总线本身会有一些配置信息。但不是platform_bus_type,这是比上层platform设备还高的级别,一般都是CPU内部总线。
而私有数据是对平台数据的进一步指定,总线上的哪个设备。一般,这两个数据实际是同一个结构体类型,只是具体的platform设备会对这个结构体进一步填充。平台数据里面有些细节实际没有初始化,因为用不到。
以AM335x举例:
硬件:ARM 总线              ——>                             GPMC                                      ——>NAND Flash
软件:Platform_bus_type——>platform_gpmc_device(这个实际没有显式名称)——>gpmc_nand_device
后两个都定义成platform_device,都各自有自己的platform_driver


版权所有,转载请说明转自  http://my.csdn.net/weiqing1981127
 

原创作者:南京邮电大学  通信与信息系统专业 研二 魏清

一.Platform设备驱动概念

主要讲解平台设备驱动的模型和基本概念,同时因为驱动加载的方式有动态加载和静态加载两种方式,这里我们分别对动态加载和静态加载两种情况下,如何使用平台设备和驱动加以叙述。最后使用mini2440开发板,运用Platformdevice_attribute机制,编写按键驱动代码和测试代码。

 

我们知道linux内核中常见的的总线有I2C总线,PCI总线,串口总线,SPI总线,PCI总线,CAN总线,单总线等,所以有些设备和驱动就可以挂在这些总线上,然后通过总线上的match进行设备和驱动的匹配。但是有的设备并不属于这些常见总线,所以我们引入了一种虚拟总线,也就是platform总线的概念,对应的设备叫做platform设备,对应的驱动叫做platform驱动。当然引入platform的概念,可以做的与板子相关的代码和驱动的代码分离,使得驱动有更好的可扩展性和跨平台性。

 

1.Platform总线

struct bus_type platform_bus_type = {

       .name             = "platform",  //

       .dev_attrs       = platform_dev_attrs,  //属性

       .match           = platform_match,  //设备和驱动的匹配函数

       .uevent           = platform_uevent,   //卸载处理

       .pm         = &platform_dev_pm_ops,  //电源管理

};

我们看看设备和驱动的匹配函数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);    //获得平台驱动

       if (pdrv->id_table)           //如果平台驱动有支持项,进入platform_match_id

              return platform_match_id(pdrv->id_table, pdev) != NULL;

       return (strcmp(pdev->name, drv->name) == 0);   //没有支持项,则老实匹配名字

}

通过上面这个match函数我们知道,如果驱动中定义了驱动支持项,那么在总线执行match函数中,就会将驱动支持项中每一个名字和设备名字匹配,看看是否匹配成功。如果驱动没有设置支持项,就会把驱动的名字和设备的名字匹配,如果一样,则匹配成功。

 

2.Platform设备

struct platform_device {

       const char      * name;  //

       int           id;  

       struct device   dev;      //内嵌设备

       u32         num_resources;   //资源个数

       struct resource       * resource;   //资源结构体

       struct platform_device_id      *id_entry;

       struct pdev_archdata     archdata;

};

我们重点来看看platform_device中资源结构体的定义

struct resource {

       resource_size_t start;  //起始地址

       resource_size_t end;  //结束地址

       const char *name;   //

       unsigned long flags;  //标号

       struct resource *parent, *sibling, *child; 

};

对于这个资源结构体中的flags标号可以有IORESOURCE_IOIORESOURCE_MEMIORESOURCE_IRQIORESOURCE_DMA四种选择,重点是申请内存(IORESOURCE_MEM)和申请中断号(IORESOURCE_IRQ)用的比较多。

 

2.1Platform设备的静态加载

所谓的静态加载,就是把platform设备编译进内核,对于platform_device的定义常常在BSP中实现,我们这里拿Mini2440举例,看看对于的BSP文件mach-smdk2440.c

struct platform_device s3c_device_lcd = {

       .name               = "s3c2410-lcd",

       .id             = -1,

       .num_resources       = ARRAY_SIZE(s3c_lcd_resource),

       .resource   = s3c_lcd_resource,

       .dev              = {

              .dma_mask            = &s3c_device_lcd_dmamask,

              .coherent_dma_mask     = 0xffffffffUL

       }

};

这是基于Mini2440LCD平台设备在BSP文件中的定义,那么我们怎么把它加入内核呢?

static struct platform_device *smdk2440_devices[] __initdata = {

       &s3c_device_usb,

       &s3c_device_lcd,  //添加LCD平台设备

       &s3c_device_wdt,

       &s3c_device_i2c0,

       &s3c_device_iis,

};

嗯,原来我们建立了一个platform_device数组,然后把LCDplatform_device添加到这个数组中,那么这个platform_device数组怎么注册到内核的呢?

static void __init smdk2440_machine_init(void)

{

       s3c24xx_fb_set_platdata(&smdk2440_fb_info);

       s3c_i2c0_set_platdata(NULL);

       platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));//加到内核

       smdk_machine_init();

}

看到了吧,在smdk2440_machine_init中,我们调用了platform_add_devices函数来把platform_device注册到内核,再继续跟踪下platform_add_devices

int platform_add_devices(struct platform_device **devs, int num)

{

       int i, ret = 0;

       for (i = 0; i < num; i++) {

              ret = platform_device_register(devs[i]);

              if (ret) {

                     while (--i >= 0)

                            platform_device_unregister(devs[i]);  //注册设备

                     break;

              }

       }

       return ret;

}

好了,到此为止,我们已经看到了如果添加platform_device,以及这个platform_device又是如何被注册到内核的全过程。

 

除了BSP中定义的资源外,有的设备可能还会有一些配置信息,而这些配置信息依赖于板子,不适合放到驱动中,为此,我们的platform提供了平台数据platform_data的支持。在内核中添加平台数据有两种方式,仍然以LCD为例

static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {  //平台数据

       .displays  = &smdk2440_lcd_cfg,

       .num_displays = 1,

       .default_display = 0,

       .lpcsel            = ((0xCE6) & ~7) | 1<<4,

};

上面的smdk2440_fb_info就是LCD的平台数据,我们怎么把这个LCD的平台数据告诉LCDplatform_device呢?

static void __init smdk2440_machine_init(void)

{

       s3c24xx_fb_set_platdata(&smdk2440_fb_info);  //添加平台数据

       s3c_i2c0_set_platdata(NULL);

       platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

       smdk_machine_init();

}

看到没?上面的s3c24xx_fb_set_platdata函数就完成了平台数据的添加,继续跟踪这个s3c24xx_fb_set_platdata函数

void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)

{

       struct s3c2410fb_mach_info *npd;

       npd = kmalloc(sizeof(*npd), GFP_KERNEL);

       if (npd) {

              memcpy(npd, pd, sizeof(*npd));

              s3c_device_lcd.dev.platform_data = npd;  //平台数据添加的实现

       } else {

              printk(KERN_ERR "no memory for LCD platform data\n");

       }

}

好了,我们可以看到其实把这个平台数据保存在了平台设备中内嵌的设备结构体的platform_data中。刚才说了添加平台数据有两种方式,根据上面的原理,其实我们可以直接把平台数据保存在了平台设备中内嵌的设备结构体的platform_data中,具体代码如下

struct platform_device s3c_device_lcd = {

       .name               = "s3c2410-lcd",

       .id             = -1,

       .num_resources       = ARRAY_SIZE(s3c_lcd_resource),

       .resource   = s3c_lcd_resource,

       .dev              = {

              .dma_mask            = &s3c_device_lcd_dmamask,

              .coherent_dma_mask     = 0xffffffffUL

              .platform_data=&smdk2440_fb_info  //添加平台数据

       }

};

到此为止,我们已经明白了platform_device的静态添加全过程。

 

2.2 Platform设备的动态加载

由于静态添加platform_device需要最后编译内核,这个不利于修改,所以在开发阶段,我们可以采用platform设备的动态加载方法。具体操作是:先分配platform_device,然后向platform_device中添加资源结构体,最后把platform_device注册到内核,对应三个函数如下

struct platform_device  my_device = platform_device_alloc("s3c2410-buttons", -1);

platform_device_add_resources(my_device, s3c_buttons_resource, 3);

ret =  platform_device_add(my_device);

当然,上面三个函数还是封装在模块加载函数中,也就是把平台设备的加载写成一个模块的形式。

 

3. Platform驱动

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;        //内嵌设备驱动

       struct platform_device_id *id_table;  //驱动支持项

};

根据上面的platform_driver结构体的定义,我们需要思考下platform驱动名字在哪里呢?实际上在内嵌的设备驱动中定义的。

 

3.1 Platform驱动的静态加载

写一个驱动,测试驱动阶段我们一般采用动态加载的方式,当驱动已经成型,我们就会采用静态加载的方式,把驱动编译入内核。把驱动静态编译入内核的方式主要是根据MakefileKconfig两张地图,在Makefile中添加驱动文件名,在Kconfig中添加对应的驱动菜单选项,当我面make zImage时就会自动编译我们的驱动文件。

 

3.2 Platform驱动的动态加载

拿一个基于平台设备的按键驱动例子看看

static struct platform_driver my_driver = {

     .probe = my_probe,   //探测函数

     .remove = my_remove,

     .driver = {

          .owner = THIS_MODULE,

          .name = "s3c2410-buttons",

     },

};

上面是按键驱动中定义了的一个platform_driver,然后我们只需要在驱动模块加载函数中执行platform_driver_register(&my_driver)就可以把platform驱动加入内核了。在我们进行insmod加载时就会调用这个模块加载函数,从而注册platform驱动。

 

二.平台设备的资源

1.平台数据和私有数据的区别

前面在讲平台设备的静态加载的时候,我们提到平台数据的概念,在内核驱动代码中还会出现私有数据这一名词。那么平台数据和私有数据有什么区别呢?首先平台数据是由于引入平台设备而产生的,平台数据主要保存的是一些依赖的板子的配置信息,平台数据的定义是定义在平台设备所在的BSP中的,我们在平台驱动中可以进行读取到在BSP中定义的平台数据。而私有数据是作为一个驱动中保存设备信息的一个结构体,它定义在平台驱动中,而不是BSP中,我们在平台驱动中可以把一个设备结构体设置为这个平台驱动的私有数据,也可以根据这个平台设备,读取这个平台设备的私有数据。

 

好了,下面我们先看看怎么在平台驱动中读取在BSP中定义的平台数据,仍然以LCD为例,只需要在设备驱动需要获取平台数据的地方执行如下代码

struct s3c2410fb_mach_info *pdata=pdev->dev.platform_data;

 

接下来,我们研究下私有数据。私有数据的定义各种各样,总之是一个结构体。那么怎么将一个设备结构体设置为平台设备的私有数据呢?

struct  buttons  *key

platform_set_drvdata(pdev, key)

同样怎么根据这个平台设备,读取这个平台设备的私有数据呢?

Struct  buttons  *keyt=platform_get_drvdata(pdev)

 

最后补充两个点:第一,根据经验发现平台数据是为私有数据服务的,也就是平台数据可能成为私有数据的一部分。第二,对于由设备获得平台设备的情况,我们可以通过*pdev=to_platform_device(dev)代码获得。

 

2. Platform设备资源的读取

我们在BSP中定义了平台设备的资源,那么怎么获取这些资源呢?首先我们要明白,设备和驱动的第一次匹配是发生在总线上的match函数中,这次匹配成功后所做的操作只是把设备和驱动相连。当我们执行平台驱动中的probe时,会发生第二次设备和驱动的匹配,也就是所谓的探测。所以,我们对在BSP中定义的平台设备的资源会在平台驱动的probe函数中读取到,下面我们就看看如何读取这些资源了。

对于资源中的存储空间的资源读取,首先读取资源,然后申请空间,最后完成由虚拟地址到物理地址的映射。具体函数如下

struct resource      *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

struct resource  *buttons_mem = request_mem_region(res->start,

                                    res->end-res->start+1, pdev->name);

void __iomem  *buttons_base = ioremap(res->start, res->end - res->start + 1);

对于中断资源的读取,只要一步如下操作即可。

struct resource  *buttons_irq1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);


三.平台设备驱动测试

这里我们采用Mini2440开发板,编写基于平台设备的按键驱动,要求按键驱动触发方式为单边沿触发,同时要求添加设备属性项。因为这个驱动比较简单,我就不去细致分析了,如果对硬件不理解可以参考mini2440开发板数据手册,如果对软件不理解,可以参考上文平台设备的讲解。在此,我提供platform设备模块代码,platform驱动模块代码,应用层测试代码,需要注意的是在动态加载测试时需要先加载设备模块,再加载驱动模块。

 

1. platform设备模块代码

#include <linux/device.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/string.h>

#include <linux/platform_device.h>

#include <linux/interrupt.h>

#include <linux/sysfs.h>

#include <linux/stat.h>

#define GPGCON   0x56000060   //控制端口地址

#define GPGDAT   0x56000064  //数据端口地址

ssize_t test_show(struct device *dev, struct attribute *attr, char *buf);

ssize_t test_store(struct device *dev, struct attribute *attr, char *buf,size_t count);

 

static DEVICE_ATTR(buttons, S_IRWXUGO, test_show, test_store);  //设备属性

 

ssize_t test_show(struct device *dev, struct attribute *attr, char *buf)  //读设备属性

{

       printk("call :  test_show . \n");

       printk("attrname:%s . \n",attr->name);

       sprintf(buf,"%s\n",attr->name);

       return strlen(attr->name)+2;

}

 

ssize_t test_store(struct device *dev, struct attribute *attr, char *buf,size_t count) //写设备属性

{

       printk("call :  test_store . \n");

       printk("write : %s . \n",buf);

       strcpy(attr->name,buf);

       return count;

}

 

static struct resource s3c_buttons_resource[]=                 

{

       [0]={                                   //内存资源

              .start = GPGCON,

              .end = GPGDAT,

              .flags=IORESOURCE_MEM,

              },

       [1]={                                   //中断号

              //KEY1

              .start = IRQ_EINT8,

              .end = IRQ_EINT8,

              .flags=IORESOURCE_IRQ,

              },   

       [2]={

              //KEY2

              .start = IRQ_EINT11,

              .end = IRQ_EINT11,

              .flags=IORESOURCE_IRQ,

              },

};

 

MODULE_AUTHOR("WJB");

MODULE_LICENSE("Dual BSD/GPL");

 

static struct platform_device *my_device = NULL;

 

static int __init my_device_init(void)

{

     int ret = 0;

     my_device = platform_device_alloc("s3c2410-buttons", -1);   //申请平台设备

     platform_device_add_resources(my_device, s3c_buttons_resource, 3);  //添加资源

     ret =  platform_device_add(my_device);       //注册平台设备

        device_create_file(&my_device->dev,&dev_attr_buttons);  //添加设备属性

     if(ret)

         platform_device_put(my_device);

     return ret;

}

 

static void my_device_exit(void)

{

     platform_device_unregister(my_device);

        device_remove_file(&my_device->dev,&dev_attr_buttons);

}

 

module_init(my_device_init);

module_exit(my_device_exit);

 

2. platform驱动模块代码

#include <linux/device.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/string.h>

#include <linux/platform_device.h>

#include <asm/io.h>

#include <linux/poll.h>        

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <linux/miscdevice.h>

#include <linux/sched.h>

MODULE_AUTHOR("WJB");

MODULE_LICENSE("Dual BSD/GPL");

 

#define BUTTONS_12INPUT_MASK   0x41

struct button_irq_desc {               //私有数据结构体

    int number;

    char *name;   

};

static struct button_irq_desc buttons_irqs [] = {  //私有数据

    { 0, "KEY1"},

    { 1, "KEY2"},

};

 

static volatile char key_values [] = {'0', '0'};

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);      //定义等待队列

static volatile int ev_press = 0;

 

static struct resource     *buttons_irq1;

static struct resource     *buttons_irq2;

static struct resource *buttons_mem;

static void __iomem *buttons_base;

 

static irqreturn_t s3c2410buttons_irq(int irq, void *dev_id)

{  

    struct button_irq_desc *buttons_irqs = (struct button_irq_desc *)dev_id;

    unsigned int tmp;

    void __iomem *base = buttons_base;

   tmp=readb(base+0x04);

   if(buttons_irqs->number==0)

   {

     tmp &=0x01;

   }else{

     tmp &=0x08;

    }  

    // process data

    if (tmp == (key_values[buttons_irqs->number] & 1)) { // Changed

       key_values[buttons_irqs->number] = '1' ;

    }

        ev_press = 1;

      wake_up_interruptible(&button_waitq);

    return IRQ_RETVAL(IRQ_HANDLED);

}

 

static int s3c24xx_buttons_open(struct inode *inode, struct file *file)

{   int ret;

    unsigned int tmp;

    void __iomem *base = buttons_base;

    // set key1 and key2 input

    tmp=readb(base);

    writeb(tmp|BUTTONS_12INPUT_MASK ,base);

       ret = request_irq(buttons_irq1->start, s3c2410buttons_irq,IRQ_TYPE_EDGE_FALLING, "KET1", (void *)&buttons_irqs[0]);

       if (ret != 0) {

              printk( "failed to install irq (%d)\n", ret);

              goto err1;

       }

     ret = request_irq(buttons_irq2->start, s3c2410buttons_irq, IRQ_TYPE_EDGE_FALLING, "KET2", (void *)&buttons_irqs[1]);

       if (ret != 0) {

              printk( "failed to install irq (%d)\n", ret);

              goto err2;

       }

       ev_press = 1;

   

        return 0;

err2: disable_irq(buttons_irq2->start);

            free_irq(buttons_irq2->start, (void *)&buttons_irqs[1]);

err1: disable_irq(buttons_irq1->start);

            free_irq(buttons_irq1->start, (void *)&buttons_irqs[0]);

      

        return -EBUSY;

}

 

static int s3c24xx_buttons_close(struct inode *inode, struct file *file)

{

           disable_irq(buttons_irq2->start);

        free_irq(buttons_irq2->start, (void *)&buttons_irqs[1]);

           disable_irq(buttons_irq1->start);

        free_irq(buttons_irq1->start, (void *)&buttons_irqs[0]);

    return 0;

}

 

static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)

{

    unsigned long err;

    int i;

    if (!ev_press) {

       if (filp->f_flags & O_NONBLOCK)

           return -EAGAIN;

       else

           wait_event_interruptible(button_waitq, ev_press);

    }  

    ev_press = 0;

    err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));

   for (i=0;i<2;i++)

    {

    key_values[i]='0';

    }   

    return err ? -EFAULT : min(sizeof(key_values), count);

}

 

static struct file_operations dev_fops = {

    .owner   =   THIS_MODULE,

    .open    =   s3c24xx_buttons_open,

    .release =   s3c24xx_buttons_close,

    .read    =   s3c24xx_buttons_read,

};

static struct miscdevice s3c2410buttons_miscdev = {

       .minor = MISC_DYNAMIC_MINOR,

       .name = "s3c2410-buttons",

       .fops = &dev_fops,

};

 

static int my_probe(struct platform_device* pdev)

{  

      int ret;

        struct resource *res;

       struct device *dev;

       dev = &pdev->dev;

// get resource

       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

       if (res == NULL) {

              dev_err(&pdev->dev, "failed to get memory region resource\n");

              return -ENOENT;

       }

       buttons_mem = request_mem_region(res->start,

                                    res->end-res->start+1,

                                    pdev->name);

       if (buttons_mem == NULL) {

              dev_err(&pdev->dev, "failed to reserve memory region\n");

              ret = -ENOENT;

              goto err_nores;

       }

       buttons_base = ioremap(res->start, res->end - res->start + 1);

       if (buttons_base == NULL) {

              dev_err(&pdev->dev, "failed ioremap()\n");

              ret = -EINVAL;

              goto err_nores;

       }

 //get key1 interrupt

       buttons_irq1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

       if (buttons_irq1 == NULL) {

              dev_err(dev, "no irq resource specified\n");

              ret = -ENOENT;

              goto err_map;

       }

 //get key2 interrupt

       buttons_irq2 = platform_get_resource(pdev, IORESOURCE_IRQ, 1);

       if (buttons_irq2 == NULL) {

              dev_err(dev, "no irq resource specified\n");

              ret = -ENOENT;

              goto err_map;

       }

// register misc device

       ret = misc_register(&s3c2410buttons_miscdev);

       if (ret) {

              dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",

                     WATCHDOG_MINOR, ret);

              goto err_map;

       }

 

     printk("driver found device which my driver can handle!\n");

err_map:

       iounmap(buttons_base);

err_nores:

       release_resource(buttons_mem);

       kfree(buttons_mem);

 

     return ret;

}

 

 

static int my_remove(struct platform_device* pdev)

{

    release_resource(buttons_mem);

       kfree(buttons_mem);

       buttons_mem = NULL;

       free_irq(buttons_irq1->start, (void *)&buttons_irqs[0]);

       buttons_irq1 = NULL;

       free_irq(buttons_irq2->start, (void *)&buttons_irqs[1]);

       buttons_irq2 = NULL;

       iounmap(buttons_base);

       misc_deregister(&s3c2410buttons_miscdev); 

    printk("drvier found device unpluged!/n");

    return 0;

}

 

static struct platform_driver my_driver = {

     .probe = my_probe,

     .remove = my_remove,

     .driver = {

          .owner = THIS_MODULE,

          .name = "s3c2410-buttons",

     },

};

 

static int __init my_driver_init(void)

{

     return platform_driver_register(&my_driver);

}

 

static void my_driver_exit(void)

{

     platform_driver_unregister(&my_driver);

}

 

module_init(my_driver_init);

module_exit(my_driver_exit);

 

 

3.应用层测试代码

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/select.h>

#include <sys/time.h>

#include <errno.h>

int main(void)

{

       int buttons_fd;

       char buttons[2] = {'0', '0'};

       buttons_fd = open("/dev/s3c2410-buttons", 0);

       if (buttons_fd < 0) {

              perror("open device buttons");

              exit(1);

       }

       for (;;) {

              char current_buttons[2];

              int count_of_changed_key;

              int i;

              if (read(buttons_fd, current_buttons, sizeof current_buttons) != sizeof current_buttons) {

                     perror("read buttons:");

                     exit(1);

              }

              for (i = 0, count_of_changed_key = 0; i < sizeof buttons / sizeof buttons[0]; i++) {

                     if (buttons[i] != current_buttons[i]) {

                            printf("%skey %d is %s", count_of_changed_key? ", ": "", i+1, buttons[i] == '0' ? "up" : "down");

                            count_of_changed_key++;

                     }

              }

              if (count_of_changed_key) {

                     printf("\n");

              }           

       }

       close(buttons_fd);

       return 0;

}

 

平台设备测试:

在超级终端下:

cd  /home/platform/device

insmod device.ko

cd  ../driver

insmod driver.ko

cd ../

./buttons

然后通过Mini2440开发板的按键,观察到超级终端的按键信息。

 

设备属性项测试:

在超级终端下:

cd /sys/platform/ s3c2410-buttons

ls后,会显示buttons这一目录

读取设备属性:cat buttons

修改设备属性:echo modify>buttons


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值