记录一种在内核空间向用户空间通知中断的方法
记录一种在内核空间向用户空间通知中断的方法
- 0.前言
- 1.代码实现
- 1)内核设备驱动实现
- 2)消息通知实现
- 3)测试程序
- 2.解析
参考文章: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 applicationsunregister_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 0x24040003enum {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_buffnl_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 automaticmemcpy(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 100enum {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_NETLINKsaddr->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)循环中一直监听等待内核驱动的信息。
注:此文中仅为大致的实现框架,仅供参考,具体设备的详细实现流程需根据实际情况进行修改。