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