1 /*
2 * Copyright 2023 NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "sw_isr_common.h"
8
9 #include <zephyr/sw_isr_table.h>
10 #include <zephyr/spinlock.h>
11
12 /* an interrupt line can be considered shared only if there's
13 * at least 2 clients using it. As such, enforce the fact that
14 * the maximum number of allowed clients should be at least 2.
15 */
16 BUILD_ASSERT(CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS >= 2,
17 "maximum number of clients should be at least 2");
18
z_shared_isr(const void * data)19 void z_shared_isr(const void *data)
20 {
21 size_t i;
22 const struct z_shared_isr_table_entry *entry;
23 const struct _isr_table_entry *client;
24
25 entry = data;
26
27 for (i = 0; i < entry->client_num; i++) {
28 client = &entry->clients[i];
29
30 if (client->isr) {
31 client->isr(client->arg);
32 }
33 }
34 }
35
36 #ifdef CONFIG_DYNAMIC_INTERRUPTS
37
38 static struct k_spinlock lock;
39
z_isr_install(unsigned int irq,void (* routine)(const void *),const void * param)40 void z_isr_install(unsigned int irq, void (*routine)(const void *),
41 const void *param)
42 {
43 struct z_shared_isr_table_entry *shared_entry;
44 struct _isr_table_entry *entry;
45 struct _isr_table_entry *client;
46 unsigned int table_idx;
47 int i;
48 k_spinlock_key_t key;
49
50 table_idx = z_get_sw_isr_table_idx(irq);
51
52 /* check for out of bounds table index */
53 if (table_idx >= IRQ_TABLE_SIZE) {
54 return;
55 }
56
57 shared_entry = &z_shared_sw_isr_table[table_idx];
58 entry = &_sw_isr_table[table_idx];
59
60 key = k_spin_lock(&lock);
61
62 /* have we reached the client limit? */
63 __ASSERT(shared_entry->client_num < CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS,
64 "reached maximum number of clients");
65
66 if (entry->isr == z_irq_spurious) {
67 /* this is the first time a ISR/arg pair is registered
68 * for INTID => no need to share it.
69 */
70 entry->isr = routine;
71 entry->arg = param;
72
73 k_spin_unlock(&lock, key);
74
75 return;
76 } else if (entry->isr != z_shared_isr) {
77 /* INTID is being used by another ISR/arg pair.
78 * Push back the ISR/arg pair registered in _sw_isr_table
79 * to the list of clients and hijack the pair stored in
80 * _sw_isr_table with our own z_shared_isr/shared_entry pair.
81 */
82 shared_entry->clients[shared_entry->client_num].isr = entry->isr;
83 shared_entry->clients[shared_entry->client_num].arg = entry->arg;
84
85 shared_entry->client_num++;
86
87 entry->isr = z_shared_isr;
88 entry->arg = shared_entry;
89 }
90
91 /* don't register the same ISR/arg pair multiple times */
92 for (i = 0; i < shared_entry->client_num; i++) {
93 client = &shared_entry->clients[i];
94
95 __ASSERT(client->isr != routine && client->arg != param,
96 "trying to register duplicate ISR/arg pair");
97 }
98
99 shared_entry->clients[shared_entry->client_num].isr = routine;
100 shared_entry->clients[shared_entry->client_num].arg = param;
101 shared_entry->client_num++;
102
103 k_spin_unlock(&lock, key);
104 }
105
swap_client_data(struct _isr_table_entry * a,struct _isr_table_entry * b)106 static void swap_client_data(struct _isr_table_entry *a,
107 struct _isr_table_entry *b)
108 {
109 struct _isr_table_entry tmp;
110
111 tmp.arg = a->arg;
112 tmp.isr = a->isr;
113
114 a->arg = b->arg;
115 a->isr = b->isr;
116
117 b->arg = tmp.arg;
118 b->isr = tmp.isr;
119 }
120
shared_irq_remove_client(struct z_shared_isr_table_entry * shared_entry,int client_idx,unsigned int table_idx)121 static void shared_irq_remove_client(struct z_shared_isr_table_entry *shared_entry,
122 int client_idx, unsigned int table_idx)
123 {
124 int i;
125
126 shared_entry->clients[client_idx].isr = NULL;
127 shared_entry->clients[client_idx].arg = NULL;
128
129 /* push back the removed client to the end of the client list */
130 for (i = client_idx; i <= (int)shared_entry->client_num - 2; i++) {
131 swap_client_data(&shared_entry->clients[i],
132 &shared_entry->clients[i + 1]);
133 }
134
135 shared_entry->client_num--;
136
137 /* "unshare" interrupt if there will be a single client left */
138 if (shared_entry->client_num == 1) {
139 _sw_isr_table[table_idx].isr = shared_entry->clients[0].isr;
140 _sw_isr_table[table_idx].arg = shared_entry->clients[0].arg;
141
142 shared_entry->clients[0].isr = NULL;
143 shared_entry->clients[0].arg = NULL;
144
145 shared_entry->client_num--;
146 }
147 }
148
arch_irq_disconnect_dynamic(unsigned int irq,unsigned int priority,void (* routine)(const void * parameter),const void * parameter,uint32_t flags)149 int __weak arch_irq_disconnect_dynamic(unsigned int irq, unsigned int priority,
150 void (*routine)(const void *parameter),
151 const void *parameter, uint32_t flags)
152 {
153 ARG_UNUSED(priority);
154 ARG_UNUSED(flags);
155
156 return z_isr_uninstall(irq, routine, parameter);
157 }
158
z_isr_uninstall(unsigned int irq,void (* routine)(const void *),const void * parameter)159 int z_isr_uninstall(unsigned int irq,
160 void (*routine)(const void *),
161 const void *parameter)
162 {
163 struct z_shared_isr_table_entry *shared_entry;
164 struct _isr_table_entry *entry;
165 struct _isr_table_entry *client;
166 unsigned int table_idx;
167 size_t i;
168 k_spinlock_key_t key;
169
170 table_idx = z_get_sw_isr_table_idx(irq);
171
172 /* check for out of bounds table index */
173 if (table_idx >= IRQ_TABLE_SIZE) {
174 return -EINVAL;
175 }
176
177 shared_entry = &z_shared_sw_isr_table[table_idx];
178 entry = &_sw_isr_table[table_idx];
179
180 key = k_spin_lock(&lock);
181
182 /* note: it's important that we remove the ISR/arg pair even if
183 * the IRQ line is not being shared because z_isr_install() will
184 * not overwrite it unless the _sw_isr_table entry for the given
185 * IRQ line contains the default pair which is z_irq_spurious/NULL.
186 */
187 if (!shared_entry->client_num) {
188 if (entry->isr == routine && entry->arg == parameter) {
189 entry->isr = z_irq_spurious;
190 entry->arg = NULL;
191 }
192
193 goto out_unlock;
194 }
195
196 for (i = 0; i < shared_entry->client_num; i++) {
197 client = &shared_entry->clients[i];
198
199 if (client->isr == routine && client->arg == parameter) {
200 /* note: this is the only match we're going to get */
201 shared_irq_remove_client(shared_entry, i, table_idx);
202 goto out_unlock;
203 }
204 }
205
206 out_unlock:
207 k_spin_unlock(&lock, key);
208 return 0;
209 }
210
211 #endif /* CONFIG_DYNAMIC_INTERRUPTS */
212