一,ZYNQ中断底层分类详解
1,ZYNQ CPU 软件中断(SGI,Software generatedinterrupts):ZYNQ 共有两个 CPU,每个 CPU 具备各自的 16 个软件中断(中断号0-15)(16–26 reserved) :被路由到一个或者两个CPU上,通过写ICDSGIR寄存器产生SGI.
2,CPU私有外设中断(PPI,private peripheralinterrupts ):私有中断是固定的不能修改。这里有 2 个 PL 到 CPU 的快速中断 nFIQ(中断号27-31):每个CPU都有一组PPI,包括全局定时器、私有看门狗定时器、私有定时器和来自PL的FIQ/IRQ.
3,ZYNQ PS 和 PL 共享中断(SPI,shared peripheralinterrupts):共享中断就是一些端口共用一个中断请求线:(中断号32-95):由PS和PL上的各种I/O控制器和存储器控制器产生,这些中断信号被路由到相应的CPU, PL部分有16个共享中断,它们的触发方式可以设置
共享中断就是 PL 的中断可以发送给 PS 处理,总共16 个 PL 的中断,它们可以设置为高电平或者低电平触发。
二,中断的软件设置流程:
根据中断ID查找GIC中断配置——>初始化GIC中断控制器——>异常初始化——>将中断注册到异常处理——>使能异常——>将中断处理函数与中断异常连接起来——>设置中断优先级触发方式——>GIC中断使能
1,中断控制器实例
init.c定义了一个XScuGic类型的结构体变量,设计者需要为系统中的每个中断设备分配一个此类型的变量。其它中断相关的API需要指向此变量的指针作为传入参数。
中断初始化函数中又定义了一个指向XScuGic_Config类型的结构体变量的指针,这个结构体包含了中断设备的配置信息。两个结构体的原型如下(注意XScuGic_Config也是XscuGic的一个成员):
typedef struct
{
u16 DeviceId;
u32 CpuBaseAddress;
u32 DistBaseAddress;
XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];// Vector table of interrupt handlers
} XScuGic_Config;
typedef struct
{
XScuGic_Config *Config;
IRQF_TRIGGER_NONE
IRQF_TRIGGER_RISING
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_HIGH
IRQF_TRIGGER_LOW
IRQF_ONESHOT
IRQF_TIMER
IRQF_SHARED
(2)中断释放函数free_irq,如果目标中断不是共享中断,那举free_irq函数在释放中断后,会禁止中断并删除中断服务凼数,原型:void free_irq(unsigned int irq, void *dev);
参数说明: irq:需要释放的中断。 dev:释放的中断如果是共享中断,用这个参数来区分具体的中断,只有共享中断下所有的dev都被释放时,free_irq函数才会禁止中断并删除中断服务函数。
2,实现服务申请函数:irqreturn_t (*irq_handler_t) (int, void *);
第一个参数int型时中断服务函数对应的中断号。 第二个参数是通用指针,需要和request_irq函数的参数 dev 一致。 返回值 irqreturn_t 为return IRQ_RETVAL(IRQ_HANDLED);
3,中断使能和禁止函数
(1) enable_irq(unsigned int irq),disable_irq(unsigned int irq),disable_irq_nosync(unsigned int irq) enable_irq和disable_irq 分别是中断使能和禁止函数,irq是目标中断号。disable_irq会等待目标中断的中断服务函数执行结束才会禁止中断,如果想禁止中断则可以使用 disable_irq_nosync()
(2) local_irq_enable()和local_irq_disable() local_irq_enable()函数用于使能当前处理器的中断系统。 local_irq_disable()函数用于禁止当前处理器的中断系统。
(3)local_irq_save(flags)和local_irq_restore(flags) local_irq_save(flags)凼数也是用于禁止当前处理器中断,但是会把进之前的中断状态保存在输入参数 flags 中。而 local_irq_restore(flags)函数则是把中断恢复到 flags 中记录的状态。
四,linux 的底半部机制(上半部机制就是顶半部机制,下半部机制就是地半部机制)
上半部下半部是为了尽量缩短中断服务函数的处理过程而引入的机制,上半部就是中断出发后立即执行的中断服务函数,而下半部就是对中断服务函数的延时处理。因为中断的优先级较高,如果处理内容过多,长时间占用处理器会影响其他代码的运行,所以上半部要尽量的短。裸机程序里,在中断处理函数中标记flag,然后到主程序大循环中去轮询判断这个flag再做相应的操作,就是一种上半部下半部的思想。linux 中的上半部就是指中断服务函数irq_handler_t。 至于那些任务放在上半部哪些放在下半部,没有明确的界限,一般我们把对时间敏感、步能被打断的任务放到上半部中,其他的都可以考虑放到下半部。下面是一些针对下半部机制的:
1,软中断:
软中断结构体定义在include/linux/interrupt.h文件中,具体如下:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
内核在kernel/softirq.c文件中定义了全局的软中断向量表:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
NR_SOFTIRQS 为枚举类型的最大值,该枚举类型定义在include/linux/interrupt.h中,代表了十个软中断,要使用软中断只能向内核定义的软中断向量表注册,注册软中断需要使用函数:
void (int nr, void (*action)(struct softirq_action *));
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
NR_SOFTIRQS
};
参数说明:
nr:小于NR_SOFTIRQS的枚举值。 action:对应的软中断服务凼数。 软中断必项在编译时静态注册,注册完成就需要使用raise_softirq(unsigned int nr)触发,nr 即为需要触发的软中断。 但下半部机制通常不用软中断,而是使用tasklets机制。
2,tasklets 机制
linux内核在softirq_int函数中初始化软中断,其中HI_SOFTIRQ和TASKLET_SOFTIRQ 是默认打开的。tasklets机制就是在这两个软中断基础上实现的。tasklets的结构体定义在头文linux/interrupt.h
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
其中func就是相当于是tasklet的中断服务函数,tasklet的定义初始化可以用下面的宏定义完成:
DECLARE_TASKLET( name//tasklet 的名字,
func//tasklet 触发时的处理函数,
data//传递给func 的输入参数);
初始化完成后,调用tasklet_schedule(struct tasklet_struct *t)激活tesklet。激活后 esklet的服务函数就会在合适的时间运行。用作中断的下半段时,就在上半段中调用该函数。如果要用优先级较高的tasklet,就用tasklet_hi_schedule(struct tasklet_struct *t)函数激活。tasklet 的下半部机制示例:
struct tasklet_struct example;
void testtasklet_func(unsigned long data)
{
}
irqreturn_t test_handler(int irq, void *dev_id)
{
tasklet_schedule(&example);
}
static int __init xxxx_init(void)
{
tasklet_init(&example, testtasklet_func, data);
request_irq(irq, test_handler, 0, "name", &dev);
}
3,工作队列
工作队列也是下半部的实现方案。与tasklet相对的,工作队列是可阻塞的,因此不能在中断上下文中运行。工作队列的队列实现我们可以不用去管,要使用工作队列,只要定一个工作即可。
工作结构体是work_struct,定义在/include/linux/workqueue.h 文件中:
struct work_struct
{
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
func 即为需要处理的函数。可使用以下宏定义来创建并初始化工作:DECLARE_WORK(n, f);
n:需要创建并初始化的工作结构体work_struct 的名称。 f:工作队列需要处理的函数。
初始化完成后,使用bool schedule_work(struct work_struct *work)函数来调用工作队列。
work:需要调用的工作。 返回值:0 成功,1 失败。
工作队列workqueue的下半段机制使用示例:
struct work_struct example;
void work_func_t(struct work_struct *work);
{
}
irqreturn_t test_handler(int irq, void *dev_id)
{
schedule_work(&example);
}
static int __init xxxx_init(void)
{
......
INIT_WORK(&example, work_func_t);
request_irq(irq, test_handler, 0, "name", &dev);
}
五,设备树的中断
设备树中,通用的中断设置方法可参考文档 Documentation/devicetree/bindings/arm/arm,gi c.txt。Xilinx的设备树中断控制器的讴置不 linux 内核的通用设置稍有区别,可以查看文档 Docu mentation/devicetree/bindings/arm/xilinx,intc.txt具体了解。
axi_intc_0: interrupt-controller@41800000
{
#interrupt-cells = <2>;
//属性"interrupt-controller"代表这个节点是一个中断控制器
compatible = "xlnx,xps-intc-1.00.a";
//"interrupt-parent"属性表明这个设备属于哪个中断控制器,
如果没有这个属性会自动依附于父节点的"interrupt-parent"。
interrupt-controller;
//"interrupts",第一个值为0表示SPI中断,1表示PPI中断。
在zynqmp中,第一个值如果是 0,则中断号等于第二个值加32。
interrupt-parent = <&ps7_scugic_0>;
//中断号
interrupts = <0 29 4>;
//暂时不清楚
reg = <0x41800000 0x10000>;
//"xlnx,kind-of-intr"表示为每个可能的中断指定中断类型,1 表示 edge,0 表示 level。
xlnx,kind-of-intr = <0x1>;
//"xlnx,num-intr-inputs"属性指定控制器的特定实现支持的中断数,范围是 1~32。
xlnx,num-intr-inputs = <0x1>;
};
我们需要从设备树中获取设备号信息,以向内核注册中断,of 函数中有对应的函数:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
要使用这个函数的话,需要我们在对应的设备中设置好"interrupts"属性。
dev 是设备节点 index 是对属性"interrupts"元素的索引,因为中断号的位置有可能不同。 返回值就是中断号。
如果是gpio,内核提供了更方便的函数获取中断号:int gpio_to_irq(unsigned int gpio);
gpio 为需要申请中断号的 gpio 编号。gpio 为需要申请中断号的 gpio 编号。
zynqmp下gpio是共享的一个中断,针对单个io去设置"interrupts"属性比较复杂,gpio_to_ irq函数做了很多事,下面的gpio中断实验,我们就直接使用这个函数。
六,中断驱动程序(用 petalinux-config -c rootfs 命令选上新增的驱动程序)
关于自旋锁保护的对象,实际上就是 alinx_char.key_sts 这个值,因为这个值在读函数中操作了,在中断开启定时器回掉函数中也操作了,这两个操作是有可能同时发生的,因此需要保护。
#include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME "interrupt_led" #define DEVID_COUNT 1 #define DRIVE_COUNT 1 #define MAJOR_U #define MINOR_U 0 struct alinx_char_dev { dev_t devid; //设备号 struct cdev cdev; //字符设备 struct class *class; //类 struct device *device; //设备 struct device_node *nd; //设备树的设备节点 spinlock_t lock; //自旋锁变量 int alinx_key_gpio; //gpio号 int key_sts; //记录按键状态, 为1时被按下 unsigned int irq; //中断号 struct timer_list timer; //定时器 }; static struct alinx_char_dev alinx_char = { .cdev = { .owner = THIS_MODULE, }, }; static irqreturn_t key_handler(int irq, void *dev) { //实现key_handler,先是开启了一个 50ms 的 timer,然后返回IRQ_RETVAL(IRQ_HANDLED)就行了。 mod_timer(&alinx_char.timer, jiffies + msecs_to_jiffies(50)); return IRQ_RETVAL(IRQ_HANDLED); } void timer_function(struct timer_list *timer) { unsigned long flags; spin_lock_irqsave(&alinx_char.lock, flags); unsigned char value; value = gpio_get_value(alinx_char.alinx_key_gpio); if(value == 0) { alinx_char.key_sts = 1; } else { } spin_unlock_irqrestore(&alinx_char.lock, flags); } static int char_drv_open(struct inode *inode_p, struct file *file_p) { printk("gpio_test module openn"); return 0; } static ssize_t char_drv_read(struct file *file_p, char __user *buf, size_t len, loff_t *loff_t_p) { unsigned long flags; int ret; spin_lock_irqsave(&alinx_char.lock, flags); ret = copy_to_user(buf, &alinx_char.key_sts, sizeof(alinx_char.key_sts)); alinx_char.key_sts = 0; spin_unlock_irqrestore(&alinx_char.lock, flags); return 0; } static int char_drv_release(struct inode *inode_p, struct file *file_p) { printk("gpio_test module releasen"); return 0; } static struct file_operations ax_char_fops = { .owner = THIS_MODULE, .open = char_drv_open, .read = char_drv_read, .release = char_drv_release, }; static int __init char_drv_init(void) { u32 ret = 0; spin_lock_init(&alinx_char.lock); alinx_char.nd = of_find_node_by_path("/alinxkey"); if(alinx_char.nd == NULL) { printk("alinx_char node not findrn"); return -EINVAL; } else { printk("alinx_char node findrn"); } alinx_char.alinx_key_gpio = of_get_named_gpio(alinx_char.nd, "alinxkey-gpios", 0); if(alinx_char.alinx_key_gpio < 0) { printk("can not get alinxkey-gpios"); return -EINVAL; } printk("alinxkey-gpio num = %drn", alinx_char.alinx_key_gpio); ret = gpio_request(alinx_char.alinx_key_gpio, "alinxkey"); if(ret != 0) { printk("can not request gpiorn"); return -EINVAL; } ret = gpio_direction_input(alinx_char.alinx_key_gpio); if(ret < 0) { printk("can not set gpiorn"); return -EINVAL; } //获取中断号,初始化gpio后,使用gpio_to_irq凼数通过gpio端口号来获取中断号。 alinx_char.irq = gpio_to_irq(alinx_char.alinx_key_gpio); ret = request_irq(alinx_char.irq, key_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "alinxkey", NULL); if(ret < 0) { printk("irq %d request failedrn", alinx_char.irq); return -EFAULT; } timer_setup(&alinx_char.timer, timer_function, NULL); alloc_chrdev_region(&alinx_char.devid, MINOR_U, DEVID_COUNT, DEVICE_NAME); cdev_init(&alinx_char.cdev, &ax_char_fops); cdev_add(&alinx_char.cdev, alinx_char.devid, DRIVE_COUNT); alinx_char.class = class_create(THIS_MODULE, DEVICE_NAME); if(IS_ERR(alinx_char.class)) { return PTR_ERR(alinx_char.class); } alinx_char.device = device_create(alinx_char.class, NULL, alinx_char.devid, NULL, DEVICE_NAME); if (IS_ERR(alinx_char.device)) { return PTR_ERR(alinx_char.device); } return 0; } static void __exit char_drv_exit(void) { gpio_free(alinx_char.alinx_key_gpio); free_irq(alinx_char.irq, NULL); del_timer_sync(&alinx_char.timer); cdev_del(&alinx_char.cdev); unregister_chrdev_region(alinx_char.devid, DEVID_COUNT); device_destroy(alinx_char.class, alinx_char.devid); class_destroy(alinx_char.class); printk("timer_led_dev_exit_okn"); } module_init(char_drv_init); module_exit(char_drv_exit); MODULE_AUTHOR("subomb"); MODULE_ALIAS("subomb char"); MODULE_DESCRIPTION("INTERRUPT LED driver"); MODULE_VERSION("v1.0"); MODULE_LICENSE("GPL");
七,linux中断测试程序
#include#include #include #include int main(int argc, char *argv[]) { int fd, fd_l ,ret; char *filename, led_value = 0; unsigned int key_value; if(argc != 2) { printf("Error Usagern"); return -1; } filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("file %s open failedrn", argv[1]); return -1; } while(1) { ret = read(fd, &key_value, sizeof(key_value)); if(ret < 0) { printf("read failedrn"); break; } if(1 == key_value) { printf("ps_key1 pressrn"); led_value = !led_value; fd_l = open("/dev/gpio_leds", O_RDWR); if(fd_l < 0) { printf("file /dev/gpio_leds open failedrn"); break; } ret = write(fd_l, &led_value, sizeof(led_value)); if(ret < 0) { printf("write failedrn"); break; } ret = close(fd_l); if(ret < 0) { printf("file /dev/gpio_leds close failedrn"); break; } } } ret = close(fd); if(ret < 0) { printf("file %s close failedrn", argv[1]); return -1; } return 0; }



