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