-
Notifications
You must be signed in to change notification settings - Fork 5
/
rtdm_pruss_irq.c
434 lines (398 loc) · 13.4 KB
/
rtdm_pruss_irq.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
#include <linux/module.h>
#include <rtdm/driver.h>
#include "rtdm_pruss_irq.h"
MODULE_LICENSE("GPL");
#define PRU_ICSS_EVTOUT0 20 // TRM 6.3, table 6-1
u32 PRU_INTC_HMR_REGS[] = {
0x800,
0x804,
0x808,
};
#define AM33XX_INTC_PHYS_BASE 0x4a320000
#define PRU_INTC_SIZE 0x2000
#define PRU_INTC_SIPR0_REG 0xD00
#define PRU_INTC_SIPR1_REG 0xD04
#define PRU_INTC_ESR0_REG 0x300
#define PRU_INTC_ESR1_REG 0x304
#define PRU_INTC_SITR0_REG 0xD80
#define PRU_INTC_SITR1_REG 0xD84
static u32 PRU_INTC_CMR_REGS[] = {
0x400,
0x404,
0x408,
0x40C,
0x410,
0x414,
0x418,
0x41C,
0x420,
0x424,
0x428,
0x42C,
0x430,
0x434,
0x438,
0x43C,
};
#define PRU_INTC_SECR0_REG 0x280
#define PRU_INTC_SECR1_REG 0x284
#define PRU_INTC_HIER_REG 0x1500
#define PRU_INTC_GER_REG 0x010
#define PRU_INTC_EISR_REG 0x028
#define PRU_INTC_HIEISR_REG 0x034
struct rtdm_pruss_irq_context {
int size;
void* gpio1_addr;
rtdm_irq_t irq_handle;
int irq_handle_inited;
void* pruintc_io;
unsigned int linux_irq;
rtdm_event_t event;
int event_inited;
nanosecs_abs_t irq_start;
nanosecs_abs_t irq_stop;
u32 pru_intc_channel;
u32 pru_intc_host;
s32 arm_irq_handle_number;
u8* pru_system_events;
u8 pru_system_events_count;
u8 verbose;
void* pOffset;
};
static int irq_handler(rtdm_irq_t *irq_handle){
struct rtdm_pruss_irq_context *ctx;
int status;
u32 pru_system_event;
u32 pru_system_event_bit;
u32 pru_intc_secr_reg;
ctx = ((struct rtdm_pruss_irq_context*)irq_handle->cookie);
pru_system_event = ctx->pru_system_events[0];
pru_intc_secr_reg = pru_system_event < 32 ? PRU_INTC_SECR0_REG : PRU_INTC_SECR1_REG;
pru_system_event_bit = pru_system_event & 31;
// 4.4.2.3.6 Interrupt status clearing
// check the pending enabled status (is it enabled AND has it been triggered?)
status = ioread32(ctx->pruintc_io + pru_intc_secr_reg) & (1 << pru_system_event_bit);
if(status)
{
rtdm_event_signal(&ctx->event);
// clear the event
iowrite32((1 << pru_system_event_bit), ctx->pruintc_io + pru_intc_secr_reg);
return RTDM_IRQ_HANDLED;
} else {
return RTDM_IRQ_NONE;
}
}
static void iowrite8p(u8 val, void* dest, struct rtdm_pruss_irq_context* ctx)
{
if(ctx->verbose > 1)
printk(KERN_INFO ": %#X = %#X [u8]\n", dest - ctx->pOffset, val);
iowrite8(val, dest);
}
static void iowrite32p(u32 val, void* dest, struct rtdm_pruss_irq_context* ctx)
{
if(ctx->verbose > 1)
printk(KERN_INFO ": %#X = %#X [u32]\n", dest - ctx->pOffset, val);
iowrite32(val, dest);
}
static void init_pru(struct rtdm_pruss_irq_context *ctx){
u32 value;
u32 pru_intc_channel = ctx->pru_intc_channel;
u32 pru_intc_host = ctx->pru_intc_host;
u32 n;
if(ctx->verbose > 1) {
for(n = 0; n < ctx->pru_system_events_count; ++n)
printk(KERN_INFO "INITING_PRU: system_event: %u, intc_channel: %u, intc_host: %u\n", ctx->pru_system_events[n], pru_intc_channel, pru_intc_host);
}
ctx->pruintc_io = ioremap(AM33XX_INTC_PHYS_BASE, PRU_INTC_SIZE);
ctx->pOffset = ctx->pruintc_io;
// 4.4.2.5 INTC Basic Programming Model
// 4.4.2.5 (1.)
// Set polarity of system events: HIGH is active
// Polarity all system events is always high.
iowrite32p(0xFFFFFFFF, ctx->pruintc_io + PRU_INTC_SIPR0_REG, ctx);
iowrite32p(0xFFFFFFFF, ctx->pruintc_io + PRU_INTC_SIPR1_REG, ctx);
// 4.4.2.5 (1.)
// Set type of system events: PULSE
// Type of all system events is always pulse.
iowrite32p(0x0, ctx->pruintc_io + PRU_INTC_SITR0_REG, ctx);
iowrite32p(0x0, ctx->pruintc_io + PRU_INTC_SITR1_REG, ctx);
// PRU has 64 system events that are internally mapped
// to 10 channels of the PRU's INTC.
// These are in turn mapped to 10 host channels, of which
// 0 and 1 are exported to PRUs' R31 bit 30 and 31
// while 2 to 9 are exported to ARM's INTC.
// 4.4.2.5 (2.)
// map system event pru_system_event to interrupt controller channel pru_intc_channel
if(ctx->verbose > 1)
printk(KERN_INFO "cmr_reg");
for(n = 0; n < ctx->pru_system_events_count; ++n)
{
u8 pru_system_event = ctx->pru_system_events[n];
iowrite8p(pru_intc_channel, ctx->pruintc_io + PRU_INTC_CMR_REGS[pru_system_event >> 2] + (pru_system_event & 3), ctx);
}
// 4.4.2.5 (3.)
// map PRU channel interrupt to host
if(ctx->verbose > 1)
printk(KERN_INFO "hmr_reg");
iowrite8p(pru_intc_host, ctx->pruintc_io + PRU_INTC_HMR_REGS[pru_intc_channel >> 2] + (pru_intc_channel & 3), ctx);
// 4.4.2.5 (4.)
//clear system events
if(ctx->verbose > 1)
printk(KERN_INFO "secr0");
iowrite32p(0xFFFFFFFF, ctx->pruintc_io + PRU_INTC_SECR0_REG, ctx);
if(ctx->verbose > 1)
printk(KERN_INFO "secr1");
iowrite32p(0xFFFFFFF, ctx->pruintc_io + PRU_INTC_SECR1_REG, ctx);
// 4.4.2.5 (5.)
//enable host interrupt
// 4.4.3.2.1 INTC methodology > Interrupt Processing > Interrupt Enabling
// this register is write-only
if(ctx->verbose > 1)
printk(KERN_INFO "eisr_reg");
for(n = 0; n < ctx->pru_system_events_count; ++n)
{
u8 pru_system_event = ctx->pru_system_events[n];
iowrite32p(pru_system_event, ctx->pruintc_io + PRU_INTC_EISR_REG, ctx); // TODO: disable this in _close() (EICR)
}
// enable host interrupt output
if(ctx->verbose > 1)
printk(KERN_INFO "hieisr_reg");
iowrite32p(pru_intc_host, ctx->pruintc_io + PRU_INTC_HIEISR_REG, ctx); // TODO: disable this in _close() (HIDISR)
// HIER: The Host Interrupt Enable Registers enable or disable individual host interrupts. These work separately from the global enables. There is one bit per host interrupt. These bits are updated when writing to the Host Interrupt Enable Index Set and Host Interrupt Enable Index Clear registers.
if(ctx->verbose > 1)
printk(KERN_INFO "hier");
//iowrite32p((1 << pru_intc_host), ctx->pruintc_io + PRU_INTC_HIER_REG, ctx);
value = ioread32(ctx->pruintc_io + PRU_INTC_HIER_REG);
if(ctx->verbose > 1)
printk(KERN_INFO "hier read: %x\n", value);
// Enable system event to trigger the output (not clearly exlplained in the manual)
for(n = 0; n < ctx->pru_system_events_count; ++n)
{
u8 pru_system_event = ctx->pru_system_events[n];
if(pru_system_event < 32)
{
if(ctx->verbose > 1)
printk(KERN_INFO "esr0");
iowrite32p((1 << pru_system_event), ctx->pruintc_io + PRU_INTC_ESR0_REG, ctx); // TODO: disable this in _close() (ECR0)
}
else
{
if(ctx->verbose > 1)
printk(KERN_INFO "esr1");
iowrite32p((1 << (pru_system_event - 32)), ctx->pruintc_io + PRU_INTC_ESR1_REG, ctx); // TODO: disable this in _close() (ECR1)
}
}
// 4.4.2.5 (7.)
// enable Global Enable Register
if(ctx->verbose > 1)
printk(KERN_INFO "intc_ger");
iowrite32p(1, ctx->pruintc_io + PRU_INTC_GER_REG, ctx);
}
static int init_arm_intc(struct rtdm_pruss_irq_context *ctx){
int res;
struct device_node *of_node = of_find_node_by_name(NULL, "interrupt-controller");
struct irq_domain *intc_domain = irq_find_matching_fwnode(&of_node->fwnode, DOMAIN_BUS_ANY);
ctx->linux_irq = irq_create_mapping(intc_domain, ctx->arm_irq_handle_number);
res = rtdm_irq_request(&ctx->irq_handle, ctx->linux_irq, irq_handler, 0, "rtdm_pruss_irq_irq", (void*)ctx);
if(ctx->verbose > 1)
printk(KERN_INFO "rtdm_pruss_irq linux_irq: %i for arm_irq_handle_number: %i\n", ctx->linux_irq, ctx->arm_irq_handle_number);
if(res != 0)
{
printk(KERN_INFO "rtdm interrupt registered: %i FAILED\n", res);
return -1;
}
ctx->irq_handle_inited = 1;
return 0;
}
static int refcount = 0;
static int allocate_pru_system_events(struct rtdm_pruss_irq_context* ctx)
{
ctx->pru_system_events = (u8*)rtdm_malloc(sizeof(ctx->pru_system_events[0]) * ctx->pru_system_events_count);
if(!ctx->pru_system_events)
return 1;
return 0;
}
static int rtdm_pruss_irq_ioctl(struct rtdm_fd* fd, unsigned int request, void __user* arg){
int err = 0;
u32 n;
struct rtdm_pruss_irq_context *ctx = rtdm_fd_to_private(fd);
struct rtdm_pruss_irq_registration reg;
if(_IOC_TYPE(request) != RTDM_PRUSS_IRQ_IOC_MAGIC)
{
printk(KERN_WARNING "rtdm_pruss_irq_ioctl: Unknown ioctl");
return -ENOTTY;
}
// if user wants to write, check that we can read
// from user the appropriate size
if(_IOC_DIR(request) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, arg, _IOC_SIZE(request));
if(err)
{
printk(KERN_WARNING "rtdm_pruss_irq_ioctl: unable to access argument memory");
return -EFAULT;
}
// TODO: guard against device removal while executing this ioctl.
// (using spinlocks)
switch(request)
{
case RTDM_PRUSS_IRQ_VERBOSE:
if(ctx->verbose > 1 || (u32)arg > 1)
printk(KERN_WARNING "rtdm_vpruss_irq_ioctl verbose: %u\n", (u32)arg);
ctx->verbose = (u32)arg;
return 0;
case RTDM_PRUSS_IRQ_REGISTER_FULL:
err = rtdm_copy_from_user(fd, ®, arg, sizeof(struct rtdm_pruss_irq_registration));
if(err)
{
return -1;
}
ctx->pru_system_events_count = reg.pru_system_events_count;
allocate_pru_system_events(ctx);
err = !access_ok(VERIFY_READ, reg.pru_system_events, _IOC_SIZE(reg.pru_system_events_count * sizeof(reg.pru_system_events[0])));
if(err)
{
printk(KERN_WARNING "rtdm_pruss_irq_ioctl: unable to access pru_system_events");
return -EFAULT;
}
err = rtdm_copy_from_user(fd,
ctx->pru_system_events,
(u8 __user*)reg.pru_system_events,
sizeof(ctx->pru_system_events[0]) * ctx->pru_system_events_count);
if(ctx->verbose > 1) {
for(n = 0; n < ctx->pru_system_events_count; ++n)
printk(KERN_INFO "events: %u\n", ctx->pru_system_events[n]);
}
ctx->pru_intc_channel = reg.pru_intc_channel;
ctx->pru_intc_host = reg.pru_intc_host;
break;
case RTDM_PRUSS_IRQ_REGISTER:
ctx->pru_system_events_count = 1;
allocate_pru_system_events(ctx);
ctx->pru_system_events[0] = (u8)(u32)arg;
// apply "default" mapping
// (ranges checked below)
ctx->pru_intc_channel = ctx->pru_system_events[0] - 16;
ctx->pru_intc_host = ctx->pru_intc_channel;
break;
default:
break;
}
for(n = 0; n < ctx->pru_system_events_count; ++n)
if(ctx->pru_system_events[n] > 64)
return -EINVAL;
if(
ctx->pru_intc_channel > 9 ||
ctx->pru_intc_host > 9
)
{
return -EINVAL;
}
// -2 because PRUSS INTC 2:9 are mapped to PRU_ICSS_EVTOUT0:PRU_ICSS_EVTOUT7
// (channels 0 and 1 cannot be routed to ARM)
ctx->arm_irq_handle_number = ctx->pru_intc_host >= 2 ? ctx->pru_intc_channel - 2 + PRU_ICSS_EVTOUT0 : -1;
if(ctx->verbose > 0)
printk(KERN_INFO
"Registering PRU interrupt:\n"
" PRU ICSS system event: %u\n"
" PRU INTC channel: %u\n"
" PRU INTC host interrupt: %u\n"
" ARM IRQ number: %d\n",
ctx->pru_system_events[0], // TODO: print other pru_system_events_as_well
ctx->pru_intc_channel,
ctx->pru_intc_host,
ctx->arm_irq_handle_number
);
if(ctx->arm_irq_handle_number >= 0)
{
if((err = init_arm_intc(ctx)))
{
printk(KERN_WARNING "rtdm_pruss_irq_ioctl: unable to register interrupt\n");
return err;
}
rtdm_event_init(&ctx->event, 0);
ctx->event_inited = 1;
}
init_pru(ctx);
refcount++;
return 0;
}
static int rtdm_pruss_irq_open(struct rtdm_fd *fd, int oflags){
struct rtdm_pruss_irq_context *ctx = rtdm_fd_to_private(fd);
printk(KERN_INFO "rtdm_pruss_irq_open\n");
memset(ctx, 0, sizeof(*ctx));
return 0;
}
static void rtdm_pruss_irq_close(struct rtdm_fd *fd){
struct rtdm_pruss_irq_context *ctx = rtdm_fd_to_private(fd);
refcount--;
printk(KERN_INFO "rtdm_pruss_irq_close\n");
rtdm_free(ctx->pru_system_events);
// disable the Global Enable Register of the PRU INTC
if(ctx->pruintc_io)
iowrite32(0, ctx->pruintc_io + PRU_INTC_GER_REG); // TODO: only clear the enabled IRQ and do not disable the global (unless it's the last one)
if(ctx->irq_handle_inited)
{
if(ctx->verbose > 1)
printk(KERN_INFO "rtdm_irq_free\n");
rtdm_irq_free(&ctx->irq_handle);
}
if(ctx->event_inited)
{
if(ctx->verbose > 1)
printk(KERN_INFO "rtdm_event_pulse\n");
rtdm_event_pulse(&ctx->event);
if(ctx->verbose > 1)
printk(KERN_INFO "rtdm_event_destroy\n");
rtdm_event_destroy(&ctx->event);
}
//irq_dispose_mapping(ctx->linux_irq); // calling this causes a stack trace in dmesg
}
static ssize_t rtdm_pruss_irq_read(struct rtdm_fd *fd, void __user *buf, size_t size){
int ret;
nanosecs_rel_t timeout = 100000000;
struct rtdm_pruss_irq_context *ctx = rtdm_fd_to_private(fd);
if(ctx->arm_irq_handle_number < 0)
{
// the ARM interrupt has not been registered
return -ECANCELED;
}
// wait till event gets signalled or timeout occurs
// if the event has been signalled already, will return immediately
ret = rtdm_event_timedwait(&ctx->event, timeout, NULL);
//rtdm_printk(KERN_INFO "rtdm_pruss_irq event got released\n");
// ret will be 0 on success or -ETIMEDOUT if the PRU interrupt did not come through on time
if(ret)
return -ETIMEDOUT;
else
return 0;
}
static struct rtdm_driver rtdm_pruss_irq_driver = {
.profile_info = RTDM_PROFILE_INFO(rtdm_test_rtdm_pruss_irq,
RTDM_CLASS_EXPERIMENTAL,
RTDM_SUBCLASS_GENERIC,
0),
.device_flags = RTDM_NAMED_DEVICE,
.device_count = 1,
.context_size = sizeof(struct rtdm_pruss_irq_context),
.ops = {
.open = rtdm_pruss_irq_open,
.close = rtdm_pruss_irq_close,
.ioctl_rt = rtdm_pruss_irq_ioctl,
.ioctl_nrt = rtdm_pruss_irq_ioctl,
.read_rt = rtdm_pruss_irq_read,
},
};
static struct rtdm_device device = {
.driver = &rtdm_pruss_irq_driver,
.label = "rtdm_pruss_irq_%d",
};
static int __init rtdm_pruss_irq_init(void){
printk(KERN_INFO "rtdm_pruss_irq version %d loaded\n", RTDM_PRUSS_IRQ_VERSION);
return rtdm_dev_register(&device);
}
static void __exit rtdm_pruss_irq_exit(void){
printk(KERN_INFO "rtdm_pruss_irq unloaded\n");
return rtdm_dev_unregister(&device);
}
module_init(rtdm_pruss_irq_init);
module_exit(rtdm_pruss_irq_exit);