参考文章:Linux驱动实践:中断处理函数如何【发送信号】给应用层?
0.前言
最近在项目中遇到一个需求,需要将一个设备的中断状态通知到用户态的一个指定程序中,该设备的整体架构如下:
CPU通过SPI与设备通信,在设备中存在一个ISR控制单元,当设备发生中断时,会在ISR单元中给对应的寄存器置位,CPU可以通过轮询这些寄存器单元来判断这些中断是否发生。但这样做就失去了中断的意义,内核依旧是通过轮询方式来扫描中断。
在原先的实现方案中,SPI device通过内核通用接口挂载成了一个内核设备spidev,笔者在此设备基础上,重新在内核中创建了一个影子设备,用来实现用户态程序的读写接口,包括open()、release()、ioctl()接口等,但笔者在后续尝试为这段ISR寄存器地址申请内核中断时,发现官方驱动似乎存在一些问题,会出现设备无法挂载,或挂载后无法正常感知中断的结果。
所以笔者在同事的帮助下,重新做了一个解决方案,SPI device依旧通过内核通用接口挂载成内核设备,但不创建影子设备,用户态直接操作原设备;此外,为这段ISR地址单独实现一个platform driver,将地址重映射到内存地址中,这样当中断发生时,通过内核的netlink函数通知指定的端口。
1.代码实现
1)内核设备驱动实现
spidev.h:
#ifndef __SPIDEV_H__
#define __SPIDEV_H__
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/uaccess.h>
#include <linux/phy.h>
#include <net/dsa.h>
struct spidev_switch{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device *parent;
void __iomem * vir_base_addr;
unsigned long spi_base_addr;
unsigned long mem_size;
struct spi_device *spidev;
uint32_t speed_hz;
struct mutex spi_api_lock;
struct mii_bus* stmmac_mii;
int irq;
struct gpio_irq_dev *gpioDev;
};
typedef struct{
uint32_t addr;
uint32_t *data;
uint32_t regCnt;
uint32_t burstType;
uint32_t crcFlag;
int32_t ret;
}switch_reg_t;
struct gpio_irq_dev {
struct gpio_desc *gpiod;
int irq;
char irq_name[10];
char irq_drv_name[20];
}gpio_irq_dev_t;
#define SWI_DEVICE_NAME "spi-dev"
#define SWI_DEVICE_CNT 1
#define SWI_IOC_MAGIC 'k'
#define DRV_NAME "2404-spi"
#define DRV_VERSION "0.1.1"
#define GPIO_INTR_NUM 3
#define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \
| SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \
| SPI_NO_CS | SPI_READY | SPI_TX_DUAL \
| SPI_TX_QUAD | SPI_TX_OCTAL | SPI_RX_DUAL \
| SPI_RX_QUAD | SPI_RX_OCTAL)
#define WITCH_IOC_SYS_BUS_REG_RD _IOWR(SWI_IOC_MAGIC, 8, switch_reg_t)
#define WITCH_IOC_SYS_BUS_REG_WR _IOWR(SWI_IOC_MAGIC, 9, switch_reg_t)
#define WITCH_IOC_SPI_INNER_REG_RD _IOWR(SWI_IOC_MAGIC, 12, switch_reg_t)
#define WITCH_IOC_SPI_INNER_REG_WR _IOWR(SWI_IOC_MAGIC, 13, switch_reg_t)
#define WITCH_IOC_SPI_BURST_REG_RD _IOWR(SWI_IOC_MAGIC, 14, switch_reg_t)
#define WITCH_IOC_SPI_BURST_REG_WR _IOWR(SWI_IOC_MAGIC, 15, switch_reg_t)
#define SWITCH_IOC_GPIO_ISR_EN _IOWR(SWI_IOC_MAGIC, 16, switch_reg_t)
#endif
spidev.c:
#include "spidev.h"
#include "my-netlink.h"
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
static int spi_module_open (struct inode *node, struct file *filp)
{
struct cdev* cdev_t;
cdev_t = node->i_cdev;
filp->private_data = container_of(cdev_t, struct spidev_switch, cdev);
return 0;
}
static ssize_t spi_module_read(struct file *filp, char *buff, size_t count, loff_t *offp)
{
return 0;
}
static ssize_t spi_module_write(struct file *filp, const char __user *buf, size_t count, loff_t *off)
{
return 0;
}
static int spi_module_release(struct inode *node, struct file *filp)
{
return 0;
}
static long int spi_module_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int32_t retval = 0;
int32_t len;
switch_reg_t switch_reg;
uint32_t g_mem_reg_data[256] = {0};
struct spidev_switch* kgsw = file->private_data;
spidev_dbg_printf(spidev_LOG_DEBUG,"%s:cmd:%x,arg:%x\n",__func__,cmd,arg);
switch (cmd) {
case WITCH_IOC_SYS_BUS_REG_RD:
len = _IOC_SIZE(cmd);
retval = copy_from_user(&switch_reg, (const void __user *)arg, len);
spidev_dbg_printf(spidev_LOG_DEBUG,"%s:retval:%x\n",__func__,retval);
if (retval == 0){
switch_reg.ret = spidev_apb_reg_read32(kgsw,switch_reg.addr, \
switch_reg.regCnt,switch_reg.burstType,g_mem_reg_data);
retval = copy_to_user((void __user *)switch_reg.data, g_mem_reg_data, switch_reg.regCnt*sizeof(uint32_t));
spidev_dbg_printf(spidev_LOG_DEBUG,"%s:retval:%x\n",__func__,retval);
}
break;
case WITCH_IOC_SYS_BUS_REG_WR:
len = _IOC_SIZE(cmd);
retval = copy_from_user(&switch_reg, (const void __user *)arg, len);
if (retval == 0){
retval = copy_from_user(g_mem_reg_data, (const void __user *)switch_reg.data, switch_reg.regCnt*sizeof(uint32_t));
if (retval == 0){
switch_reg.ret = spidev_apb_reg_write32(kgsw,switch_reg.addr, \
switch_reg.regCnt,switch_reg.burstType,g_mem_reg_data);
spidev_dbg_printf(spidev_LOG_DEBUG,"%s:retval:%x\n",__func__,retval);
}
}
break;
case WITCH_IOC_SPI_INNER_REG_RD:
len = _IOC_SIZE(cmd);
retval = copy_from_user(&switch_reg, (const void __user *)arg, len);
if (retval == 0){
switch_reg.ret = spi_inner_reg_read32(kgsw,switch_reg.addr,g_mem_reg_data);
retval = copy_to_user((void __user *)switch_reg.data, g_mem_reg_data, switch_reg.regCnt*sizeof(uint32_t));
}
break;
case WITCH_IOC_SPI_INNER_REG_WR:
len = _IOC_SIZE(cmd);
retval = copy_from_user(&switch_reg, (const void __user *)arg, len);
if (retval == 0){
retval = copy_from_user(g_mem_reg_data, (const void __user *)switch_reg.data, switch_reg.regCnt*sizeof(uint32_t));
if (retval == 0){
switch_reg.ret = spi_inner_reg_write32(kgsw,switch_reg.addr,g_mem_reg_data);
}
}
break;
case WITCH_IOC_SPI_BURST_REG_RD:
len = _IOC_SIZE(cmd);
retval = copy_from_user(&switch_reg, (const void __user *)arg, len);
if (retval == 0){
switch_reg.ret = spi_burst_reg_read32(kgsw,switch_reg.addr, \
switch_reg.regCnt,switch_reg.burstType,g_mem_reg_data);
retval = copy_to_user((void __user *)switch_reg.data, g_mem_reg_data, switch_reg.regCnt*sizeof(uint32_t));
}
break;
case WITCH_IOC_SPI_BURST_REG_WR:
len = _IOC_SIZE(cmd);
retval = copy_from_user(&switch_reg, (const void __user *)arg, len);
if (retval == 0){
retval = copy_from_user(g_mem_reg_data, (const void __user *)switch_reg.data, switch_reg.regCnt*sizeof(uint32_t));
if (retval == 0){
switch_reg.ret = spi_burst_reg_write32(kgsw,switch_reg.addr, \
switch_reg.regCnt,switch_reg.burstType,g_mem_reg_data);
}
}
break;
case SWITCH_IOC_GPIO_ISR_EN:
len = _IOC_SIZE(cmd);
retval = copy_from_user(&switch_reg, (const void __user *)arg, len);
if (retval == 0){
retval = copy_from_user(g_mem_reg_data, (const void __user *)switch_reg.data, switch_reg.regCnt*sizeof(uint32_t));
if (retval == 0){
switch_reg.ret = gpio_irq_enable(kgsw, switch_reg.addr);
}
}
break;
default:
spidev_dbg_printf(spidev_LOG_ERROR, "%s nuknow cmd type\n",__func__);
break;
}
return retval;
}
static struct file_operations switch_module_opts = {
.owner = THIS_MODULE,
.open = spi_module_open,
.read = spi_module_read,
.write = spi_module_write,
.unlocked_ioctl = spi_module_ioctl,
.release = spi_module_release,
};
static int is_dev_creat = 0;
int spidev_dev_creat(struct spidev_switch* kgsw)
{
int major;
int ret;
if(is_dev_creat)
return 0;
/*alloc devid*/
alloc_chrdev_region(&kgsw->devid, 0, SWI_DEVICE_CNT, SWI_DEVICE_NAME);
major = MAJOR(kgsw->devid);
/*register char dev*/
cdev_init(&kgsw->cdev, &switch_module_opts);
ret = cdev_add(&kgsw->cdev, kgsw->devid, SWI_DEVICE_CNT);
if (ret) {
goto del_unrigister;
}
/*creat device node*/
kgsw->class = class_create(THIS_MODULE, SWI_DEVICE_NAME);
if (IS_ERR(kgsw->class)) {
goto del_cdev;
}
kgsw->device = device_create(kgsw->class, NULL, kgsw->devid, NULL, SWI_DEVICE_NAME);
if (IS_ERR(kgsw->device)) {
goto destroy_class;
}
find_spi_dev(kgsw);
of_parse_spidev_reg_addr(kgsw);
is_dev_creat = 1;
return 0;
destroy_class:
device_destroy(kgsw->class, kgsw->devid);
del_cdev:
cdev_del(&kgsw->cdev);
del_unrigister:
unregister_chrdev_region(kgsw->devid, SWI_DEVICE_CNT);
is_dev_creat = 0;
return -EIO;
}
static irqreturn_t switch_2404_isr_func(int irq, void *dev_id)
{
//printk(KERN_NOTICE "Enter switch 2404 irq(%d) function\n", irq);
struct spidev_switch* kgsw = (struct spidev_switch*)dev_id;
netlink_msg_t nlg;
memset(&nlg, 0, sizeof(netlink_msg_t));
void __iomem* vir_addr2 = 0x30800068 - kgsw->spi_base_addr + kgsw->vir_base_addr;
writel(1,vir_addr2);
nlg.cmd_type = REPORT_IRQ_TO_USER;
nlg.irq_type = SWITCH_INTERRUPT_TYPE;
nlg.irq_number = irq;
printk(KERN_NOTICE "Kernel netlink send to user: irq_type(0x%x) irq_number(%x)\n", nlg.irq_type, nlg.irq_number);
myspi_send_to_usr_msg(&nlg);
return IRQ_HANDLED;
}
static irqreturn_t myspi_gpio_isr_func(int irq, void *dev_id)
{
netlink_msg_t nlg;
memset(&nlg, 0, sizeof(netlink_msg_t));
//disable_irq_nosync(irq);
nlg.cmd_type = REPORT_IRQ_TO_USER;
nlg.irq_type = GPIO_INTR_TYPE;
nlg.irq_number = irq;
printk(KERN_NOTICE "Kernel netlink send to user: irq_type(0x%x) irq_number(%x)\n", nlg.irq_type, nlg.irq_number);
myspi_send_to_usr_msg(&nlg);
return IRQ_HANDLED;
}
static int myspi_gpio_irq_init(struct spidev_switch* kgsw)
{
int rc;
struct gpio_irq_dev *gpio_dev = NULL;
struct device *dev = kgsw->parent;
int index = 0;
gpio_dev = devm_kzalloc(dev, sizeof(*gpio_dev) * GPIO_INTR_NUM, GFP_KERNEL);
if (!gpio_dev)
{
dev_err(dev, "no memory.\n");
return -ENOMEM;
}
for (index = 0; index < GPIO_INTR_NUM; index++)
{
sprintf(gpio_dev[index].irq_name, "%d", index);
sprintf(gpio_dev[index].irq_drv_name, "%d-gpio-irq", index);
gpio_dev[index].gpiod = devm_gpiod_get(dev, gpio_dev[index].irq_name, GPIOD_IN);
if (IS_ERR(gpio_dev[index].gpiod)) {
rc = PTR_ERR(gpio_dev[index].gpiod);
if (rc != -EPROBE_DEFER)
dev_err(dev, "error getting gpio (%d)\n", rc);
goto irq_err;
}
gpio_dev[index].irq = gpiod_to_irq(gpio_dev[index].gpiod);
if (gpio_dev[index].irq < 0)
{
goto irq_err;
}
rc = devm_request_irq(dev, gpio_dev[index].irq, myspi_gpio_isr_func, IRQF_TRIGGER_RISING, gpio_dev[index].irq_drv_name, &gpio_dev[index]);
if (rc)
{
dev_err(dev, "unable to claim IRQ %d\n", gpio_dev[index].irq);
goto irq_err;
}
else
{
printk(KERN_NOTICE " gpio-irq%d %s request success!\n", gpio_dev[index].irq, gpio_dev[index].irq_drv_name);
}
}
kgsw->gpioDev = gpio_dev;
return 0;
irq_err:
devm_kfree(dev, gpio_dev);
return rc;
}
static int spidev_probe(struct platform_device *pdev)
{
struct spidev_switch* kgsw;
int ret = 0;
kgsw = devm_kzalloc(&pdev->dev, sizeof(struct spidev_switch), GFP_KERNEL);
kgsw->parent = &pdev->dev;
kgsw->irq = platform_get_irq(pdev, 0);
if (kgsw->irq < 0)
{
dev_err(&pdev->dev, "irq number invalid\n");
goto err_out;
}
ret = spidev_dev_creat(kgsw);
if (ret < 0) {
dev_err(&pdev->dev, "device create fail!\n");
goto err_out;
}
ret = request_irq(kgsw->irq, switch_2404_isr_func, IRQF_SHARED, dev_name(&pdev->dev), &kgsw->devid);
if (ret < 0 && ret != -ENOTCONN) {
dev_err(&pdev->dev, "can not get IRQ\n");
goto err_out;
}
platform_set_drvdata(pdev, kgsw);
myspi_gpio_irq_init(kgsw);
myspi_netlink_init();
spidev_dbg_printf(spidev_LOG_INFO, "%s probe done\n", DRV_NAME);
return 0;
err_out:
return -ENXIO;
}
static int spidev_remove(struct platform_device *pdev)
{
struct spidev_switch* kgsw = dev_get_drvdata(&pdev->dev);
printk(KERN_NOTICE " Delete device files\n");
device_destroy(kgsw->class, kgsw->devid);
printk(KERN_NOTICE " Delete class files\n");
class_destroy(kgsw->class);
printk(KERN_NOTICE " Deregister the character device driver\n");
cdev_del(&kgsw->cdev);
//Indicates device numbers for deregistering applications
unregister_chrdev_region(kgsw->devid, SWI_DEVICE_CNT);
myspi_netlink_exit();
free_irq(kgsw->irq, &kgsw->devid);
is_dev_creat = 0;
return 0;
}
static const struct of_device_id spidev_switch_of_match[] = {
{ .compatible = "my-spidev" },
{ },
};
MODULE_DEVICE_TABLE(of, spidev_switch_of_match);
static struct platform_driver spidev_driver = {
.probe = spidev_probe,
.remove = spidev_remove,
.driver = {
.name = "spidev switch driver",
.of_match_table = spidev_switch_of_match,
},
};
static int __init spidev_switch_init(void)
{
printk(KERN_NOTICE DRV_NAME ": version %s loaded\n", DRV_VERSION);
return platform_driver_register(&spidev_switch_driver);
}
static void __exit spidev_switch_exit(void)
{
printk(KERN_NOTICE DRV_NAME ": version %s unloaded\n", DRV_VERSION);
platform_driver_unregister(&spidev_switch_driver);
}
module_init(spidev_switch_init);
module_exit(spidev_switch_exit);
MODULE_DESCRIPTION("Driver for myspi switch");
MODULE_VERSION(DRV_VERSION);
MODULE_LICENSE("GPL v2");
2)消息通知实现
my_netlink.h:
#ifndef __MYSPI_SWITCH_NETLINK_H__
#define __MYSPI_SWITCH_NETLINK_H__
#define NETLINK_IRQ_REPORT_USER (MAX_LINKS-1)
#define NETLINK_DEFAULT_PORT_ID 0
#define NETLINK_DEFAULT_SEQ 0
#define NETLINK_DEFAULT_FLAGS 0
#define NETLINK_USER_PORT 100
#define INTERRUPT_TYPE 0x24040000
#define PHY_GPIO_INTR_FMC0_TYPE 0x24040001
#define PHY_GPIO_INTR_FMC1_TYPE 0x24040002
#define PHY_GPIO_INTR_FMC2_TYPE 0x24040003
enum {
ASYNC_READ_CMD = 1,
ASYNC_WRITE_CMD = 2,
SHARE_MEM_REG_UPDATE = 3,
SHARE_MEM_REG_UPDATE_STOP = 4,
REPORT_IRQ_TO_USER = 5,
};
typedef struct netlink_msg
{
int cmd_type;
int irq_type;
int irq_number;
}netlink_msg_t;
int myspi_netlink_init(void);
void myspi_netlink_exit(void);
int myspi_send_to_usr_msg(netlink_msg_t* reg);
#endif
my_netlink.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include "myspi_switch_netlink.h"
static struct sock *nlsk = NULL;
int myspi_send_to_usr_msg(netlink_msg_t* msg)
{
struct sk_buff *nl_skb;
struct nlmsghdr *nlh;
int ret;
//creat sk_buff
nl_skb = nlmsg_new(sizeof(netlink_msg_t), GFP_ATOMIC);//what time to free;
if(!nl_skb)
{
printk("netlink alloc failure\n");
return -1;
}
// set netlink head msg
// 填充nlmsg报文头,并最终将报文填充到sk_buff发送出去
nlh = nlmsg_put(nl_skb, NETLINK_DEFAULT_PORT_ID, NETLINK_DEFAULT_SEQ, NETLINK_IRQ_REPORT_USER, sizeof(netlink_msg_t), NETLINK_DEFAULT_FLAGS);
if(nlh == NULL)
{
printk("nlmsg_put failaure \n");
nlmsg_free(nl_skb);
return -1;
}
//send msg; nl_skb will free automatic
memcpy(nlmsg_data(nlh), msg, sizeof(netlink_msg_t));
ret = netlink_unicast(nlsk, nl_skb, NETLINK_USER_PORT, MSG_DONTWAIT); // 指定端口号,端口号是唯一识别目的地址的标识,广播非阻塞式发送
return ret;
}
static void netlink_rcv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh = NULL;
netlink_msg_t *reg_data = NULL;
if(skb->len >= nlmsg_total_size(0))
{
nlh = nlmsg_hdr(skb); // 取出报文头
reg_data = NLMSG_DATA(nlh); // 取出报文内容
if(reg_data)
{
//print_regdata(__func__,__LINE__,reg_data);
}
}
}
static struct netlink_kernel_cfg cfg = {
.input = netlink_rcv_msg, /* set recv callback */
};
int myspi_netlink_init(void)
{
/* create netlink socket */
printk(" Kernel netlink create");
nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_IRQ_REPORT_USER, &cfg);
if(nlsk == NULL)
{
printk("Kernel netlink create error!\n");
return -1;
}
return 0;
}
void myspi_netlink_exit(void)
{
printk(" Kernel netlink exit!\n");
if (nlsk){
netlink_kernel_release(nlsk); /* release ..*/
nlsk = NULL;
}
}
3)测试程序
test.c:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#define NETLINK_REG_ASYNC_RW (MAX_LINKS-1) //31
#define NETLINK_USER_PORT 100
enum {
ASYNC_READ_CMD = 1,
ASYNC_WRITE_CMD = 2,
SHARE_MEM_REG_UPDATE = 3,
SHARE_MEM_REG_UPDATE_STOP = 4,
REPORT_IRQ_TO_USER = 5,
};
typedef struct netlink_msg
{
struct nlmsghdr hdr;
int cmd;
int irq_type;
int irq_number;
}netlink_msg_t;
typedef struct netlink_handle
{
int skfd;
struct sockaddr_nl saddr;
struct sockaddr_nl daddr;
} netlink_handle_t;
static netlink_handle_t async_netlink_handle;
netlink_handle_t* get_async_netlink_handle(void)
{
return &async_netlink_handle;
}
int switch_netlink_init(void)
{
int skfd;
struct sockaddr_nl* saddr; //saddr 表示源端口地址,daddr表示目的端口地址
struct sockaddr_nl* daddr;
netlink_handle_t* handle;
handle = get_async_netlink_handle();
saddr = &(handle->saddr);
daddr = &(handle->daddr);
/* 创建NETLINK socket */
skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_REG_ASYNC_RW);
if(skfd == -1)
{
perror("create socket error\n");
return -1;
}
memset(saddr, 0, sizeof(struct sockaddr_nl));
saddr->nl_family = AF_NETLINK; //AF_NETLINK
saddr->nl_pid = NETLINK_USER_PORT; //端口号(port ID)
saddr->nl_groups = 0;
if(bind(skfd, (struct sockaddr *)saddr, sizeof(struct sockaddr_nl)) != 0)
{
perror("bind() error\n");
close(skfd);
return -1;
}
memset(daddr, 0, sizeof(struct sockaddr_nl));
daddr->nl_family = AF_NETLINK;
daddr->nl_pid = 0; // to kernel
daddr->nl_groups = 0;
handle->skfd = skfd;
return 0;
}
int switch_recv_reg_msg(void)
{
int ret;
netlink_msg_t recv_msg;
socklen_t len;
netlink_handle_t* handle;
handle = get_async_netlink_handle();
memset(&recv_msg, 0, sizeof(netlink_msg_t));
len = sizeof(struct sockaddr_nl);
ret = recvfrom(handle->skfd, &recv_msg, sizeof(netlink_msg_t), 0, (struct sockaddr *)&(handle->daddr), &len);
if(!ret)
{
printf("%s,%d, err\n",__func__,__LINE__);
return -1;
}
switch(recv_msg.cmd){
case REPORT_IRQ_TO_USER:
printf("User netlink recv from kernel: irq_type(%x), irq_number(%d)\n",recv_msg.irq_type, recv_msg.irq_number);
break;
default:
printf("Unknown command\n");
break;
}
return 0;
}
int main(int argc, char *argv[]){
if (fork() == 0)
{
printf("Create a child process, pid = %d\n", getpid());
int ret = 0;
ret = switch_netlink_init();
if (ret)
{
return ret;
}
while(1)
{
switch_recv_reg_msg();
}
printf("main end\n");
}
else
{
printf("Father process, pid = %d\n", getpid());
return;
}
}
2.解析
在设备驱动文件中,实现了一个platform子系统的设备驱动,通过指定的compatible来匹配,并实现了设备的probe、remove、ioctl操作等接口,并且在创建内核设备节点时,为设备申请了spi设备中断和gpio中断,并初始化了一个netlink通信端口。当设备发生中断时,会触发回调函数中的netlink接口,向指定的用户端口发送中断号以及中断类型,这样就可以在用户程序中进行后续操作。
测试小程序中主要是测试netlink的接收,在while(1)循环中一直监听等待内核驱动的信息。
注:此文中仅为大致的实现框架,仅供参考,具体设备的详细实现流程需根据实际情况进行修改。