於platform總線的中斷(按鍵)字符設備驅動設計 2011-12-23 13:02:02
分類: LINUX
- /*申請中斷*/
- request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
- /*釋放中斷*/
- void free_irq(unsigned int irq, void *dev_id)
- enum irqreturn {
- IRQ_NONE,
- IRQ_HANDLED,
- IRQ_WAKE_THREAD,
- };
- typedef enum irqreturn irqreturn_t;
- #define IRQ_RETVAL(x) ((x) != IRQ_NONE)
- #include <linux/interrupt.h>
- #ifndef IRQF_DISABLED
- #define IRQF_DISABLED SA_INTERRUPT
- #define IRQF_SAMPLE_RANDOM SA_SAMPLE_RANDOM
- #define IRQF_SHARED SA_SHIRQ
- #define IRQF_PROBE_SHARED SA_PROBEIRQ
- #define IRQF_PERCPU SA_PERCPU
- #ifdef SA_TRIGGER_MASK
- #define IRQF_TRIGGER_NONE 0
- #define IRQF_TRIGGER_LOW SA_TRIGGER_LOW
- #define IRQF_TRIGGER_HIGH SA_TRIGGER_HIGH
- #define IRQF_TRIGGER_FALLING SA_TRIGGER_FALLING
- #define IRQF_TRIGGER_RISING SA_TRIGGER_RISING
- #define IRQF_TRIGGER_MASK SA_TRIGGER_MASK
- #else /*中斷觸發方式*/
- #define IRQF_TRIGGER_NONE 0
- #define IRQF_TRIGGER_LOW 0
- #define IRQF_TRIGGER_HIGH 0
- #define IRQF_TRIGGER_FALLING 0
- #define IRQF_TRIGGER_RISING 0
- #define IRQF_TRIGGER_MASK 0
- #endif
- #endif
- #include<linux/module.h>
- #include<linux/init.h>
- #include<linux/kernel.h>
- #include<linux/string.h>
- #include<linux/platform_device.h>
- /*硬件相關的頭文件*/
- #include<mach/regs-gpio.h>
- #include<mach/hardware.h>
- #include<linux/gpio.h>
- /*這個是硬件(CPU)密切相關的中斷號*/
- #include<mach/irqs.h>
- /*硬件資源量,這是根據tq2440開發板確定的*/
- static struct resource tq2440_button_resource[]=
- {
- /*EINT0*/
- [0]=
- {
- .flags = IORESOURCE_IRQ,
- .start = IRQ_EINT0,
- .end = IRQ_EINT0,
- .name = "S3C24XX_EINT0",
- },
- /*EINT1*/
- [1]=
- {
- .flags = IORESOURCE_IRQ,
- .start = IRQ_EINT1,
- .end = IRQ_EINT1,
- .name = "S3C24xx_EINT1",
- },
- /*EINT2*/
- [2]=
- {
- .flags = IORESOURCE_IRQ,
- .start = IRQ_EINT2,
- .end = IRQ_EINT2,
- .name = "S3C24xx_EINT2",
- },
- /*EINT4*/
- [3]=
- {
- .flags = IORESOURCE_IRQ,
- .start = IRQ_EINT4,
- .end = IRQ_EINT4,
- .name = "S3C24xx_EINT4",
- },
- };
- static struct platform_device tq2440_button_device=
- {
- /*設備名*/
- .name = "tq2440_button",
- .id = -1,
- /*資源數*/
- .num_resources = ARRAY_SIZE(tq2440_button_resource),
- /*資源指針*/
- .resource = tq2440_button_resource,
- };
- static int __init tq2440_button_init(void)
- {
- int ret ;
- /*設備註冊*/
- ret = platform_device_register(&tq2440_button_device);
- }
- static void __exit tq2440_button_exit(void)
- {
- /*設備的註銷*/
- platform_device_unregister(&tq2440_button_device);
- }
- /*加載與卸載*/
- module_init(tq2440_button_init);
- module_exit(tq2440_button_exit);
- /*LICENSE和作者信息*/
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("GP-<[email protected]>");
- #include<linux/types.h>
- #include<linux/kernel.h>
- #include<linux/init.h>
- #include<linux/module.h>
- #include<linux/platform_device.h>
- #include<mach/irqs.h>
- #include<linux/irq.h>
- #include<mach/regs-gpio.h>
- #include<linux/device.h>
- #include<linux/string.h>
- #include<linux/cdev.h>
- #include<linux/fs.h>
- #include<linux/spinlock.h>
- #include<linux/wait.h>
- #include<linux/interrupt.h>
- #include<linux/uaccess.h>
- #include<linux/poll.h>
- #define NUM_RESOURCE 4
- /*主設備號*/
- int dev_major
= -1;
- /*中斷結構體定義*/
- struct irqs
- {
- int pirqs[NUM_RESOURCE];
- char *names[NUM_RESOURCE];
- }irqs;
- /*完成具體設備的結構體設計*/
- struct tq2440_button
- {
- /*添加具體的字符設備結構*/
- struct cdev cdev;
- /*用於自動創建設備*/
- struct class
*myclass;
- /*引用次數統計表*/
- unsigned int count;
- /*添加並行機制*/
- spinlock_t lock;
- /*添加等待隊列*/
- wait_queue_head_t read_wait_queue;
- /*數據*/
- int bepressed;
- /*案件值*/
- int key_values;
- };
- static struct tq2440_button tq2440_button;
- static irqreturn_t tq2440_button_interrupt_handler(int irq,void
*dev_id)
- {
- /*得到傳遞過來的參數*/
- struct tq2440_button * dev
= dev_id;
- int i;
- /*根據得到的irq值確定具體的按鍵值*/
- for (i
= 0; i
< NUM_RESOURCE;
++ i)
- {
- /*判斷條件*/
- if(irq
== irqs.pirqs[i])
- {
- /*對關鍵數據添加並行機制*/
- spin_lock(&(dev->lock));
- /*確定被按下的值*/
- dev->key_values
= i ;
- /*表示有數據可以讀*/
- dev->bepressed
= 1;
- spin_unlock(&(dev->lock));
- /*喚醒等待的隊列*/
- wake_up_interruptible(&(dev->read_wait_queue));
- }
- }
- /*返回值*/
- return IRQ_RETVAL(IRQ_HANDLED);
- }
- static int tq2440_button_open(struct inode
*inode,struct file
*filp)
- {
- int i = 0,ret
= 0;
-
- /*中斷申請*/
- /*這句話主要是實現間接控制,但是還是可以直接控制*/
- filp->private_data
= &tq2440_button;
-
- /*修改被打開的次數值*/
- spin_lock(&(tq2440_button.lock));
- tq2440_button.count
++ ;
- spin_unlock(&(tq2440_button.lock));
- /*如果是第一次打開則需要申請中斷,這是比較推薦的方法,也可以在probe函數中申請中斷*/
- if(1==tq2440_button.count)
- {
- for(i
= 0;i
< NUM_RESOURCE;
++ i)
- {
- /*request_irq操作*/
- ret = request_irq(irqs.pirqs[i],
- tq2440_button_interrupt_handler,
- IRQ_TYPE_EDGE_BOTH,irqs.names[i],(void
*)&tq2440_button);
-
- if(ret)
- {
- break;
- }
- }
- if(ret)/*錯誤處理機制*/
- {
- i --;
- for(; i
>=0;
--i)
- {
- /*禁止中斷*/
-
- disable_irq(irqs.pirqs[i]);
-
- free_irq(irqs.pirqs[i],(void
*)&tq2440_button);
- }
- return -EBUSY;
- }
- }
- return 0;
- }
- /*closed函數*/
- static int tq2440_button_close(struct inode
*inode,struct file
*filp)
- {
- /*確保是最後一次使用設備*/
- int i = 0;
- if(tq2440_button.count
== 1)
- {
- for(i
= 0; i
< NUM_RESOURCE;
++ i)
- {
- free_irq(irqs.pirqs[i],(void
*)&tq2440_button);
- }
- }
- /*更新設備文件引用次數*/
- spin_lock(&(tq2440_button.lock));
- tq2440_button.count
= 0;
- spin_unlock(&(tq2440_button.lock));
- return 0;
- }
- static unsigned long tq2440_button_read(struct file
*filp,char __user
*buff,
- size_t count,loff_t offp)
- {
- /*設備操作*/
- struct tq2440_button *dev
= filp->private_data;
-
- unsigned long err;
- if(!dev->bepressed)/*確保沒有采用非堵塞方式讀標誌*/
- {
- if(filp->f_flags
& O_NONBLOCK)
- return -EAGAIN;
- else
- /*添加等待隊列,條件是bepressed*/
- wait_event_interruptible(dev->read_wait_queue,dev->bepressed);
- }
- /*複製數據到用戶空間*/
- err = copy_to_user(buff,
&(dev->key_values),min(sizeof(dev->key_values),count));
-
- /*修改標誌表示沒有數據可讀了*/
- spin_lock(&(dev->lock));
- dev->bepressed
= 0;
- spin_unlock(&(dev->lock));
-
- /*返回數據量*/
-
- return err
? -EFAULT:min(sizeof(dev->key_values),count);
- }
- static unsigned int tq2440_button_poll(struct file
*filp,
- struct poll_table_struct *wait)
- {
- struct tq2440_button *dev
= filp->private_data;
- unsigned int mask
= 0;
-
- /*將結構體中的等待隊列添加到wait_table*/
- poll_wait(filp,&(dev->read_wait_queue),wait);
- /*
- 返回掩碼
- POLLIN|POLLRDNORM表示有數據可讀
- */
- if(dev->bepressed)
- {
- mask |= POLLIN
| POLLRDNORM;
- }
- return mask;
- }
- /*設備的具體操作函數*/
- static const struct file_operations tq2440_fops=
- {
- .owner = THIS_MODULE,
- .open = tq2440_button_open,
- .release = tq2440_button_close,
- .read = tq2440_button_read,
- .poll = tq2440_button_poll,
- };
- /*remove函數實現字符設備的註銷操作*/
- static int tq2440_button_probe(struct platform_device
*dev)
- {
- printk("The driver found a device can be handler on platform bus\n");
-
- /*用來存儲定義好的資源,即中斷號*/
-
- struct resource * irq_resource;
- struct platform_device *pdev
= dev;
- int i = 0,ret
= 0;
- /*接下來完成具體字符驅動結構體的初始化*/
- /*1、設備號申請*/
-
- dev_t devno;
- if(dev_major
> 0)/*靜態申請設備號*/
- {
- devno = MKDEV(dev_major,0);
- ret = register_chrdev_region(devno,1,"tq2440_button");
- }
- else/*動態申請設備號*/
- {
- ret = alloc_chrdev_region(&devno,0,1,"tq2440_button");
- dev_major = MAJOR(devno);
- }
- if(ret
< 0)
- {
- return ret;
- }
- /*完成設備類的創建,主要實現設備文件的自動創建*/
- tq2440_button.myclass
= class_create(THIS_MODULE,"tq2440_button_class");
-
- /*2、完成字符設備的加載*/
- cdev_init(&(tq2440_button.cdev),&tq2440_fops);
- tq2440_button.cdev.owner
= THIS_MODULE;
- ret = cdev_add(&(tq2440_button.cdev),devno,1);
- if(ret)
- {
- printk("Add device error\n");
- return ret;
- }
-
- /*初始化自旋鎖*/
- spin_lock_init(&(tq2440_button.lock));
-
- /*修改引用次數值*/
-
- spin_lock(&(tq2440_button.lock));
- /*被打開次數統計*/
- tq2440_button.count
= 0;
- /*鍵值*/
- tq2440_button.key_values
= -1;
- spin_unlock(&(tq2440_button.lock));
-
- /*初始化等待隊列*/
-
- init_waitqueue_head(&(tq2440_button.read_wait_queue));
-
-
- /*設備的創建,實現設備文件自動創建*/
-
- device_create(tq2440_button.myclass,NULL,devno,NULL,"tq2440_button");
- /*3.獲得資源*/
- for(; i
< NUM_RESOURCE;
++ i)
- {
- /*獲得設備的資源*/
- irq_resource = platform_get_resource(pdev,IORESOURCE_IRQ,i);
-
- if(NULL
== irq_resource)
- {
- return -ENOENT;
- }
- irqs.pirqs[i]
= irq_resource->start;
- /*實現名字的複製操作*/
- //strcpy(tq2440_irqs.name[i],irq_resource->name);
- /*這一句是將指針的地址指向一個具體的地址*/
- irqs.names[i]
= irq_resource->name;
- }
- /*將設備的指針指向中斷號*/
-
- return 0;
- }
- /*probe函數實現字符設備的初始化操作*/
- static int tq2440_button_remove(struct platform_device
*dev)
- {
- printk("The driver found a device be removed from the platform bus\n");
-
- /*註銷設備*/
- device_destroy(tq2440_button.myclass,MKDEV(dev_major,0));
- /*字符設備註銷*/
- cdev_del(&(tq2440_button.cdev));
- /*註銷創建的設備類*/
- class_destroy(&(tq2440_button.myclass));
- /*釋放設備號*/
- unregister_chrdev_region(MKDEV(dev_major,0),1);
- return 0;
- }
- /*完成平臺總線結構體的設計*/
- static const struct platform_driver tq2440_button_driver
=
- {
- /*完成具體設備的初始化操作*/
- .probe = tq2440_button_probe,
- /*完成具體設備的退出操作*/
- .remove = tq2440_button_remove,
- .driver =
- {
- .owner
= THIS_MODULE,
- .name
= "tq2440_button",
- },
- };
- /*總線設備初始化過程*/
- static int __init platform_driver_init(void)
- {
- int ret;
-
- /*總線驅動註冊*/
- ret = platform_driver_register(&tq2440_button_driver);
-
- /*錯誤處理*/
- if(ret)
- {
- platform_driver_unregister(&tq2440_button_driver);
- return ret;
- }
- return 0;
- }
- static void __exit platform_driver_exit(void)
- {
- /*總線驅動釋放*/
- platform_driver_unregister(&tq2440_button_driver);
- }
- /*加載和卸載*/
- module_init(platform_driver_init);
- module_exit(platform_driver_exit);
- /*LICENSE和作者信息*/
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("GP-<[email protected]>");
- #include<stdio.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<sys/ioctl.h>
- #include<sys/stat.h>
- #include<sys/select.h>
- #include<sys/time.h>
- #include<errno.h>
- int main()
- {
- int buttons_fd;
- int key_value
= 0;
-
- /*open函數測試*/
- buttons_fd = open("/dev/tq2440_button",0);
- if(buttons_fd
< 0)
- {
- perror("open device buttons\n");
- exit(1);
- }
- while(1)
- {
- fd_set rds;
- int ret;
- FD_ZERO(&rds);
- FD_SET(buttons_fd,&rds);
- /*poll函數測試*/
- ret =
select(buttons_fd
+ 1,&rds,NULL,NULL,NULL);
- if(ret
< 0)
- {
- perror("select");
- exit(1);
- }
- if(ret
== 0)
- {
- printf("Timeout.\n");
- }
- else if(FD_ISSET(buttons_fd,&rds))
- {
- /*read函數測試*/
- int ret
= read(buttons_fd,&key_value,sizeof key_value);
- if(ret
!= sizeof key_value)
- {
- if(errno
!= EAGAIN)
- perror("read buttons\n");
- continue;
- }
- else
- {
- printf("buttons_value:%d\n",key_value+1);
- }
- }
- }
- /*release函數測試*/
- close(buttons_fd);
- return 0;
- }
之前接觸到的字符設備驅動是非常單純的Linux字符設備驅動,他不具備工程中Linux驅動中的設備與驅動分離思想和設備驅動的分層思想,不具備“總線-設備-驅動”模型的概念。接下來通過分析platform設備驅動模型的搭建過程來看看Linux的設備驅動模型究竟是怎樣的?
platform驅動模型搭建:(1)platform核心層:爲設備層和驅動層提供註冊接口、爲設備層和驅動層的匹配提供標準
①搭建總線框架:
struct bus_type {
const
char
*name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int
(*match)(struct device *dev, struct device_driver *drv);
//#####
int
(*uevent)(struct device *dev, struct kobj_uevent_env *env);
int
(*probe)(struct device *dev);
int
(*remove)(struct device *dev);
void
(*shutdown)(struct device *dev);
int
(*suspend)(struct device *dev, pm_message_t state);
int
(*suspend_late)(struct device *dev, pm_message_t state);
int
(*resume_early)(struct device *dev);
int
(*resume)(struct device *dev);
struct dev_pm_ops *pm;
struct bus_type_private *p;
//看到這個private就有點C++類中的限定域關鍵字了,這個類的私有成員
};
總線類實例化:platform總線
1
2
3
4
5
6
7
|
struct bus_type platform_bus_type = { .name =
"platform" , .dev_attrs = platform_dev_attrs, .match = platform_match,
//關鍵成員 .uevent = platform_uevent, .pm = PLATFORM_PM_OPS_PTR, }; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
platform_bus_init() { ..... error = bus_register(&platform_bus_type); //註冊platform總線的核心工作 ..... } bus_register(struct bus_type *bus) { //創建bus的屬性文件
retval = bus_create_file(bus, &bus_attr_uevent); ...... //在/sys/bus/bus->name目錄下創建devices目錄 priv->devices_kset = kset_create_and_add( "devices" , NULL,&priv->subsys.kobj); .... //在/sys/bus/bus->name目錄下創建drivers目錄 priv->drivers_kset = kset_create_and_add( "drivers" , NULL,&priv->subsys.kobj); //初始化總線設備\總線驅動鏈表 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); klist_init(&priv->klist_drivers, NULL, NULL); } |
當一個驅動掛接到該總線的時候,該總線的match方法被調用。同樣的,當一個設備掛接到該總線時,platform_match也會被調用。也就是說核心層只提供匹配的方法!不會幫他們去匹配,這人生大事要他們自己去完成!
這就好辦了,都是掛接到總線上的時候,往後分析時肯定會遇到,先暫時放着,先看看他的實現:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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); /* match against the id table first */ if
(pdrv->id_table) //看看drv的id_table中是否有現成匹配的設備記錄 return
platform_match_id(pdrv->id_table, pdev) != NULL; /* fall-back to driver name match */ return
(strcmp(pdev->name, drv->name) == 0 );
/* match成功,strcmp返回0,語句邏輯返回1 */
|
1
2
3
4
5
6
7
8
|
struct platform_device { const
char *name; int
id; // 硬件設備的象徵/代表 struct device dev;
// 由此繼承基類 u32 num_resources; struct resource * resource; //這個驅動使用的資源 struct platform_device_id *id_entry; }; |
platform_device_register(struct platform_device *pdev)
platform_device_add(struct platform_device *pdev)
pdev->dev.bus = &platform_bus_type;
device_add(&pdev->dev);
bus_attach_device(struct device *dev)
device_attach(dev);
bus_for_each_drv()函數的實現:bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
1
2
3
4
5
6
7
8
|
bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void
*data, int
(*fn)(struct device_driver *, void
*)) { ...... while
((drv = next_driver(&i)) && !error) error = fn(drv, data); ...... } |
首先關心他的最後一個形參(*fn),他在註冊platform_device時最終被重定向到__device_attach()函數,回調函數的使用在內核源碼裏邊屢見不鮮!因爲它可以減少很多重複的代碼。
現在分析的焦點轉移到__device_attach函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
__device_attach(struct device_driver *drv,
void *data) { struct device *dev = data; if
(!driver_match_device(drv, dev)) return
0 ; return
driver_probe_device(drv, dev); //match成功就執行這個函數,他最終調用really_probe()函數 } driver_match_device(struct device_driver *drv,struct device *dev) { return
drv->bus->match ? drv->bus->match(dev, drv) : 1 ;
//看到這一句,上面留下的疑問就解決了:原來核心層留下的匹配判斷標準match接口就是在這裏被調用的!!!好爽!^_^ } really_probe(struct device *dev, struct device_driver *drv) { ...... if
(dev->bus->probe) //如果bus_type結構裏邊的probe成員有定義就優先調用他的 { ret = dev->bus->probe(dev); if
(ret) goto
probe_failed; }
else
if (drv->probe)
//沒有就調用匹配到的drv結構裏邊的probe成員函數 { ret = drv->probe(dev); if
(ret) goto
probe_failed; } driver_bound(dev); //bound是綁定的意思,即將match成功的設備加入驅動的設備鏈表 ...... } |
驅動基類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct device_driver { const
char *name; struct bus_type *bus; struct module *owner; const
char *mod_name;
/* used for built-in modules */ int
(*probe) (struct device *dev); int
(*remove) (struct device *dev); void
(*shutdown) (struct device *dev); int
(*suspend) (struct device *dev, pm_message_t state); int
(*resume) (struct device *dev); struct attribute_group **groups; struct dev_pm_ops *pm; struct driver_private *p; }; |
1
2
3
4
5
6
7
8
9
10
11
|
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
(*suspend_late)(struct platform_device *, pm_message_t state); int
(*resume_early)(struct platform_device *); int
(*resume)(struct platform_device *); struct device_driver driver;
//繼承基類 struct platform_device_id *id_table; }; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
platform_driver_register(struct platform_driver *drv) { /*下面進行一系列的判斷,如果派生的platform_driver中沒有對特有成員進行初始化,設置成默認的 */ drv->driver.bus = &platform_bus_type;
//指向這個驅動所屬的bus類型:platform if
(drv->probe) //有重定向 drv->driver.probe = platform_drv_probe; if
(drv->remove) //有重定向 drv->driver.remove = platform_drv_remove; ...... return
driver_register(&drv->driver); 【進入分析】 //註冊的關鍵材料是platform_driver->driver->bus:關鍵是爲了註冊總線的類型platform_bus_type } driver_register(struct device_driver *drv) { ...... struct device_driver *other; ...... other = driver_find(drv->name, drv->bus);
//在該總線上查找是否有該設備驅動名對應的驅動 if
(other) { //如果設備已經存在對應的驅動就:出錯,驅動已經存在 put_driver(other); printk(KERN_ERR
"Error: Driver '%s' is already registered, " "aborting...\n" , drv->name); return
-EEXIST; } bus_add_driver(drv);
/* 在總線上添加這個驅動,成功的話最終結果:在bus/platform/drivers目錄下面生成“name”對應的目錄 ,並且會生成 bind module uevent unbind 四個文件*/ ...... } |
bus_add_driver(struct device_driver *drv)
driver_attach(drv); /* 試圖將驅動和設備綁定起來 */
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);//到這裏可以非常明顯的發現和設備層做的事情非常相似,幾乎是對稱出現
/* 對總線上的每一個設備都會拿來執行__driver_attach,他在這裏被用作回調函數,看看是否匹配,這個函數和__device_attach函數做的事情基本一樣這裏就不再累述了*/
(2)設備層:主要工作就是把核心層提供的API用起來
1.設置好platform_device結構體成員:主要是name、resource、num_resources、id、dev->release、
2.通過platform_device_register()把這個結構體鏈入核心層的klist_devices鏈表
(3)驅動層:同樣是把核心層提供的接口函數用起來1.設置好platform_driver結構體成員:probe、remove、driver->name
2.通過platform_driver_register()函數把這個結構體鏈入核心層的klist_drivers鏈表
3.實現probe成員函數
4.通常最後纔去完成probe函數用到的材料,一般是file_operation結構體成員,這樣應用層就可以通過這個接口來操作設備