]>
Commit | Line | Data |
---|---|---|
5ba3f43e A |
1 | /* |
2 | * Copyright (c) 2000-2015 Apple Inc. All rights reserved. | |
3 | */ | |
4 | ||
5 | /* | |
6 | * file: pe_serial.c Polled-mode UART0 driver for S3c2410 and PL011. | |
7 | */ | |
8 | ||
9 | ||
10 | #include <kern/clock.h> | |
11 | #include <kern/debug.h> | |
12 | #include <libkern/OSBase.h> | |
cb323159 | 13 | #include <libkern/section_keywords.h> |
5ba3f43e | 14 | #include <mach/mach_time.h> |
d9a64523 | 15 | #include <machine/atomic.h> |
5ba3f43e A |
16 | #include <machine/machine_routines.h> |
17 | #include <pexpert/pexpert.h> | |
18 | #include <pexpert/protos.h> | |
19 | #include <pexpert/device_tree.h> | |
20 | #if defined __arm__ | |
21 | #include <arm/caches_internal.h> | |
22 | #include <arm/machine_routines.h> | |
23 | #include <arm/proc_reg.h> | |
24 | #include <pexpert/arm/board_config.h> | |
25 | #include <vm/pmap.h> | |
26 | #elif defined __arm64__ | |
27 | #include <pexpert/arm/consistent_debug.h> | |
28 | #include <pexpert/arm64/board_config.h> | |
29 | #include <arm64/proc_reg.h> | |
30 | #endif | |
f427ee49 A |
31 | #if HIBERNATION |
32 | #include <machine/pal_hibernate.h> | |
33 | #endif /* HIBERNATION */ | |
5ba3f43e A |
34 | |
35 | struct pe_serial_functions { | |
36 | void (*uart_init) (void); | |
37 | void (*uart_set_baud_rate) (int unit, uint32_t baud_rate); | |
38 | int (*tr0) (void); | |
39 | void (*td0) (int c); | |
40 | int (*rr0) (void); | |
41 | int (*rd0) (void); | |
cb323159 | 42 | struct pe_serial_functions *next; |
5ba3f43e A |
43 | }; |
44 | ||
cb323159 | 45 | SECURITY_READ_ONLY_LATE(static struct pe_serial_functions*) gPESF = NULL; |
5ba3f43e | 46 | |
cb323159 A |
47 | static int uart_initted = 0; /* 1 if init'ed */ |
48 | static vm_offset_t uart_base = 0; | |
5ba3f43e | 49 | |
5ba3f43e A |
50 | /*****************************************************************************/ |
51 | ||
0a7de745 | 52 | #ifdef S3CUART |
5ba3f43e A |
53 | |
54 | static int32_t dt_pclk = -1; | |
55 | static int32_t dt_sampling = -1; | |
56 | static int32_t dt_ubrdiv = -1; | |
57 | ||
cb323159 A |
58 | static void ln2410_uart_set_baud_rate(__unused int unit, uint32_t baud_rate); |
59 | ||
5ba3f43e A |
60 | static void |
61 | ln2410_uart_init(void) | |
62 | { | |
0a7de745 | 63 | uint32_t ucon0 = 0x405; /* NCLK, No interrupts, No DMA - just polled */ |
5ba3f43e | 64 | |
0a7de745 | 65 | rULCON0 = 0x03; /* 81N, not IR */ |
5ba3f43e A |
66 | |
67 | // Override with pclk dt entry | |
0a7de745 | 68 | if (dt_pclk != -1) { |
5ba3f43e | 69 | ucon0 = ucon0 & ~0x400; |
0a7de745 | 70 | } |
5ba3f43e A |
71 | |
72 | rUCON0 = ucon0; | |
0a7de745 | 73 | rUMCON0 = 0x00; /* Clear Flow Control */ |
5ba3f43e | 74 | |
cb323159 | 75 | ln2410_uart_set_baud_rate(0, 115200); |
5ba3f43e | 76 | |
0a7de745 A |
77 | rUFCON0 = 0x03; /* Clear & Enable FIFOs */ |
78 | rUMCON0 = 0x01; /* Assert RTS on UART0 */ | |
5ba3f43e A |
79 | } |
80 | ||
81 | static void | |
82 | ln2410_uart_set_baud_rate(__unused int unit, uint32_t baud_rate) | |
83 | { | |
84 | uint32_t div = 0; | |
85 | uint32_t uart_clock = 0; | |
86 | uint32_t sample_rate = 16; | |
0a7de745 A |
87 | |
88 | if (baud_rate < 300) { | |
5ba3f43e | 89 | baud_rate = 9600; |
0a7de745 | 90 | } |
5ba3f43e | 91 | |
0a7de745 | 92 | if (rUCON0 & 0x400) { |
5ba3f43e A |
93 | // NCLK |
94 | uart_clock = (uint32_t)gPEClockFrequencyInfo.fix_frequency_hz; | |
0a7de745 A |
95 | } else { |
96 | // PCLK | |
5ba3f43e | 97 | uart_clock = (uint32_t)gPEClockFrequencyInfo.prf_frequency_hz; |
0a7de745 | 98 | } |
5ba3f43e A |
99 | |
100 | if (dt_sampling != -1) { | |
101 | // Use the sampling rate specified in the Device Tree | |
102 | sample_rate = dt_sampling & 0xf; | |
103 | } | |
0a7de745 | 104 | |
5ba3f43e A |
105 | if (dt_ubrdiv != -1) { |
106 | // Use the ubrdiv specified in the Device Tree | |
107 | div = dt_ubrdiv & 0xffff; | |
108 | } else { | |
109 | // Calculate ubrdiv. UBRDIV = (SourceClock / (BPS * Sample Rate)) - 1 | |
110 | div = uart_clock / (baud_rate * sample_rate); | |
0a7de745 | 111 | |
5ba3f43e A |
112 | uint32_t actual_baud = uart_clock / ((div + 0) * sample_rate); |
113 | uint32_t baud_low = uart_clock / ((div + 1) * sample_rate); | |
114 | ||
115 | // Adjust div to get the closest target baudrate | |
0a7de745 | 116 | if ((baud_rate - baud_low) > (actual_baud - baud_rate)) { |
5ba3f43e | 117 | div--; |
0a7de745 | 118 | } |
5ba3f43e A |
119 | } |
120 | ||
121 | // Sample Rate [19:16], UBRDIV [15:0] | |
122 | rUBRDIV0 = ((16 - sample_rate) << 16) | div; | |
123 | } | |
124 | ||
125 | static int | |
126 | ln2410_tr0(void) | |
127 | { | |
128 | return rUTRSTAT0 & 0x04; | |
129 | } | |
130 | static void | |
131 | ln2410_td0(int c) | |
132 | { | |
133 | rUTXH0 = (unsigned)(c & 0xff); | |
134 | } | |
135 | static int | |
136 | ln2410_rr0(void) | |
137 | { | |
138 | return rUTRSTAT0 & 0x01; | |
139 | } | |
140 | static int | |
141 | ln2410_rd0(void) | |
142 | { | |
143 | return (int)rURXH0; | |
144 | } | |
145 | ||
cb323159 A |
146 | SECURITY_READ_ONLY_LATE(static struct pe_serial_functions) ln2410_serial_functions = |
147 | { | |
148 | .uart_init = ln2410_uart_init, | |
149 | .uart_set_baud_rate = ln2410_uart_set_baud_rate, | |
150 | .tr0 = ln2410_tr0, | |
151 | .td0 = ln2410_td0, | |
152 | .rr0 = ln2410_rr0, | |
153 | .rd0 = ln2410_rd0 | |
0a7de745 | 154 | }; |
5ba3f43e | 155 | |
0a7de745 | 156 | #endif /* S3CUART */ |
5ba3f43e A |
157 | |
158 | /*****************************************************************************/ | |
159 | ||
cb323159 A |
160 | static void |
161 | dcc_uart_init(void) | |
162 | { | |
163 | } | |
5ba3f43e A |
164 | |
165 | static unsigned int | |
166 | read_dtr(void) | |
167 | { | |
168 | #ifdef __arm__ | |
0a7de745 A |
169 | unsigned int c; |
170 | __asm__ volatile ( | |
171 | "mrc p14, 0, %0, c0, c5\n" | |
172 | : "=r"(c)); | |
5ba3f43e A |
173 | return c; |
174 | #else | |
175 | /* ARM64_TODO */ | |
176 | panic_unimplemented(); | |
177 | return 0; | |
178 | #endif | |
179 | } | |
180 | static void | |
181 | write_dtr(unsigned int c) | |
182 | { | |
183 | #ifdef __arm__ | |
0a7de745 A |
184 | __asm__ volatile ( |
185 | "mcr p14, 0, %0, c0, c5\n" | |
186 | : | |
187 | :"r"(c)); | |
5ba3f43e A |
188 | #else |
189 | /* ARM64_TODO */ | |
190 | (void)c; | |
191 | panic_unimplemented(); | |
192 | #endif | |
193 | } | |
194 | ||
195 | static int | |
196 | dcc_tr0(void) | |
197 | { | |
198 | #ifdef __arm__ | |
199 | return !(arm_debug_read_dscr() & ARM_DBGDSCR_TXFULL); | |
200 | #else | |
201 | /* ARM64_TODO */ | |
202 | panic_unimplemented(); | |
203 | return 0; | |
204 | #endif | |
205 | } | |
206 | ||
207 | static void | |
208 | dcc_td0(int c) | |
209 | { | |
210 | write_dtr(c); | |
211 | } | |
212 | ||
213 | static int | |
214 | dcc_rr0(void) | |
215 | { | |
216 | #ifdef __arm__ | |
217 | return arm_debug_read_dscr() & ARM_DBGDSCR_RXFULL; | |
218 | #else | |
219 | /* ARM64_TODO */ | |
220 | panic_unimplemented(); | |
221 | return 0; | |
222 | #endif | |
223 | } | |
224 | ||
225 | static int | |
226 | dcc_rd0(void) | |
227 | { | |
228 | return read_dtr(); | |
229 | } | |
230 | ||
cb323159 A |
231 | SECURITY_READ_ONLY_LATE(static struct pe_serial_functions) dcc_serial_functions = |
232 | { | |
233 | .uart_init = dcc_uart_init, | |
234 | .uart_set_baud_rate = NULL, | |
235 | .tr0 = dcc_tr0, | |
236 | .td0 = dcc_td0, | |
237 | .rr0 = dcc_rr0, | |
238 | .rd0 = dcc_rd0 | |
0a7de745 | 239 | }; |
5ba3f43e A |
240 | |
241 | /*****************************************************************************/ | |
242 | ||
243 | #ifdef SHMCON | |
244 | ||
0a7de745 | 245 | #define CPU_CACHELINE_SIZE (1 << MMU_CLINE) |
5ba3f43e A |
246 | |
247 | #ifndef SHMCON_NAME | |
0a7de745 | 248 | #define SHMCON_NAME "AP-xnu" |
5ba3f43e A |
249 | #endif |
250 | ||
0a7de745 A |
251 | #define SHMCON_MAGIC 'SHMC' |
252 | #define SHMCON_VERSION 2 | |
253 | #define CBUF_IN 0 | |
254 | #define CBUF_OUT 1 | |
255 | #define INBUF_SIZE (panic_size / 16) | |
256 | #define FULL_ALIGNMENT (64) | |
5ba3f43e | 257 | |
0a7de745 A |
258 | #define FLAG_CACHELINE_32 1 |
259 | #define FLAG_CACHELINE_64 2 | |
5ba3f43e A |
260 | |
261 | /* Defines to clarify the master/slave fields' use as circular buffer pointers */ | |
0a7de745 A |
262 | #define head_in sidx[CBUF_IN] |
263 | #define tail_in midx[CBUF_IN] | |
264 | #define head_out midx[CBUF_OUT] | |
265 | #define tail_out sidx[CBUF_OUT] | |
5ba3f43e A |
266 | |
267 | /* TODO: get from device tree/target */ | |
0a7de745 | 268 | #define NUM_CHILDREN 5 |
5ba3f43e A |
269 | |
270 | #define WRAP_INCR(len, x) do{ (x)++; if((x) >= (len)) (x) = 0; } while(0) | |
271 | #define ROUNDUP(a, b) (((a) + ((b) - 1)) & (~((b) - 1))) | |
272 | ||
0a7de745 A |
273 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) |
274 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) | |
5ba3f43e A |
275 | |
276 | #define shmcon_barrier() do {__asm__ volatile("dmb ish" : : : "memory");} while(0) | |
277 | ||
278 | struct shm_buffer_info { | |
0a7de745 A |
279 | uint64_t base; |
280 | uint32_t unused; | |
281 | uint32_t magic; | |
5ba3f43e A |
282 | }; |
283 | ||
284 | struct shmcon_header { | |
0a7de745 A |
285 | uint32_t magic; |
286 | uint8_t version; | |
287 | uint8_t children; /* number of child entries in child_ent */ | |
288 | uint16_t flags; | |
289 | uint64_t buf_paddr[2]; /* Physical address for buffers (in, out) */ | |
290 | uint32_t buf_len[2]; | |
291 | uint8_t name[8]; | |
5ba3f43e A |
292 | |
293 | /* Slave-modified data - invalidate before read */ | |
0a7de745 | 294 | uint32_t sidx[2] __attribute__((aligned(FULL_ALIGNMENT))); /* In head, out tail */ |
5ba3f43e A |
295 | |
296 | /* Master-modified data - clean after write */ | |
0a7de745 | 297 | uint32_t midx[2] __attribute__((aligned(FULL_ALIGNMENT))); /* In tail, out head */ |
5ba3f43e | 298 | |
0a7de745 | 299 | uint64_t child[0]; /* Physical address of child header pointers */ |
5ba3f43e A |
300 | }; |
301 | ||
302 | static volatile struct shmcon_header *shmcon = NULL; | |
303 | static volatile uint8_t *shmbuf[2]; | |
304 | #ifdef SHMCON_THROTTLED | |
305 | static uint64_t grace = 0; | |
306 | static uint64_t full_timeout = 0; | |
307 | #endif | |
308 | ||
0a7de745 A |
309 | static void |
310 | shmcon_set_baud_rate(__unused int unit, __unused uint32_t baud_rate) | |
5ba3f43e A |
311 | { |
312 | return; | |
313 | } | |
314 | ||
0a7de745 A |
315 | static int |
316 | shmcon_tr0(void) | |
5ba3f43e A |
317 | { |
318 | #ifdef SHMCON_THROTTLED | |
319 | uint32_t head = shmcon->head_out; | |
320 | uint32_t tail = shmcon->tail_out; | |
321 | uint32_t len = shmcon->buf_len[CBUF_OUT]; | |
322 | ||
323 | WRAP_INCR(len, head); | |
324 | if (head != tail) { | |
325 | full_timeout = 0; | |
326 | return 1; | |
327 | } | |
328 | ||
329 | /* Full. Is this buffer being serviced? */ | |
330 | if (full_timeout == 0) { | |
331 | full_timeout = mach_absolute_time() + grace; | |
332 | return 0; | |
333 | } | |
0a7de745 | 334 | if (full_timeout > mach_absolute_time()) { |
5ba3f43e | 335 | return 0; |
0a7de745 | 336 | } |
5ba3f43e A |
337 | |
338 | /* Timeout - slave not really there or not keeping up */ | |
339 | tail += (len / 4); | |
0a7de745 | 340 | if (tail >= len) { |
5ba3f43e | 341 | tail -= len; |
0a7de745 | 342 | } |
5ba3f43e A |
343 | shmcon_barrier(); |
344 | shmcon->tail_out = tail; | |
345 | full_timeout = 0; | |
346 | #endif | |
347 | return 1; | |
348 | } | |
349 | ||
0a7de745 A |
350 | static void |
351 | shmcon_td0(int c) | |
5ba3f43e A |
352 | { |
353 | uint32_t head = shmcon->head_out; | |
354 | uint32_t len = shmcon->buf_len[CBUF_OUT]; | |
355 | ||
356 | shmbuf[CBUF_OUT][head] = (uint8_t)c; | |
357 | WRAP_INCR(len, head); | |
358 | shmcon_barrier(); | |
359 | shmcon->head_out = head; | |
360 | } | |
361 | ||
0a7de745 A |
362 | static int |
363 | shmcon_rr0(void) | |
5ba3f43e | 364 | { |
0a7de745 | 365 | if (shmcon->tail_in == shmcon->head_in) { |
5ba3f43e | 366 | return 0; |
0a7de745 | 367 | } |
5ba3f43e A |
368 | return 1; |
369 | } | |
370 | ||
0a7de745 A |
371 | static int |
372 | shmcon_rd0(void) | |
5ba3f43e A |
373 | { |
374 | int c; | |
375 | uint32_t tail = shmcon->tail_in; | |
376 | uint32_t len = shmcon->buf_len[CBUF_IN]; | |
377 | ||
378 | c = shmbuf[CBUF_IN][tail]; | |
379 | WRAP_INCR(len, tail); | |
380 | shmcon_barrier(); | |
381 | shmcon->tail_in = tail; | |
382 | return c; | |
383 | } | |
384 | ||
0a7de745 A |
385 | static void |
386 | shmcon_init(void) | |
5ba3f43e | 387 | { |
0a7de745 | 388 | DTEntry entry; |
f427ee49 | 389 | uintptr_t const *reg_prop; |
0a7de745 A |
390 | volatile struct shm_buffer_info *end; |
391 | size_t i, header_size; | |
392 | unsigned int size; | |
393 | vm_offset_t pa_panic_base, panic_size, va_buffer_base, va_buffer_end; | |
5ba3f43e | 394 | |
f427ee49 | 395 | if (kSuccess != SecureDTLookupEntry(0, "pram", &entry)) { |
5ba3f43e | 396 | return; |
0a7de745 | 397 | } |
5ba3f43e | 398 | |
f427ee49 | 399 | if (kSuccess != SecureDTGetProperty(entry, "reg", (void const **)®_prop, &size)) { |
5ba3f43e | 400 | return; |
0a7de745 | 401 | } |
5ba3f43e A |
402 | |
403 | pa_panic_base = reg_prop[0]; | |
404 | panic_size = reg_prop[1]; | |
405 | ||
406 | shmcon = (struct shmcon_header *)ml_map_high_window(pa_panic_base, panic_size); | |
407 | header_size = sizeof(*shmcon) + (NUM_CHILDREN * sizeof(shmcon->child[0])); | |
408 | va_buffer_base = ROUNDUP((uintptr_t)(shmcon) + header_size, CPU_CACHELINE_SIZE); | |
409 | va_buffer_end = (uintptr_t)shmcon + panic_size - (sizeof(*end)); | |
410 | ||
411 | if ((shmcon->magic == SHMCON_MAGIC) && (shmcon->version == SHMCON_VERSION)) { | |
412 | vm_offset_t pa_buffer_base, pa_buffer_end; | |
413 | ||
414 | pa_buffer_base = ml_vtophys(va_buffer_base); | |
415 | pa_buffer_end = ml_vtophys(va_buffer_end); | |
416 | ||
417 | /* Resume previous console session */ | |
418 | for (i = 0; i < 2; i++) { | |
419 | vm_offset_t pa_buf; | |
420 | uint32_t len; | |
421 | ||
422 | pa_buf = (uintptr_t)shmcon->buf_paddr[i]; | |
423 | len = shmcon->buf_len[i]; | |
424 | /* Validate buffers */ | |
425 | if ((pa_buf < pa_buffer_base) || | |
0a7de745 A |
426 | (pa_buf >= pa_buffer_end) || |
427 | ((pa_buf + len) > pa_buffer_end) || | |
428 | (shmcon->midx[i] >= len) || /* Index out of bounds */ | |
429 | (shmcon->sidx[i] >= len) || | |
430 | (pa_buf != ROUNDUP(pa_buf, CPU_CACHELINE_SIZE)) || /* Unaligned pa_buffer */ | |
431 | (len < 1024) || | |
432 | (len > (pa_buffer_end - pa_buffer_base)) || | |
433 | (shmcon->children != NUM_CHILDREN)) { | |
5ba3f43e | 434 | goto validation_failure; |
0a7de745 | 435 | } |
5ba3f43e A |
436 | /* Compute the VA offset of the buffer */ |
437 | shmbuf[i] = (uint8_t *)(uintptr_t)shmcon + ((uintptr_t)pa_buf - (uintptr_t)pa_panic_base); | |
438 | } | |
439 | /* Check that buffers don't overlap */ | |
440 | if ((uintptr_t)shmbuf[0] < (uintptr_t)shmbuf[1]) { | |
0a7de745 | 441 | if ((uintptr_t)(shmbuf[0] + shmcon->buf_len[0]) > (uintptr_t)shmbuf[1]) { |
5ba3f43e | 442 | goto validation_failure; |
0a7de745 | 443 | } |
5ba3f43e | 444 | } else { |
0a7de745 | 445 | if ((uintptr_t)(shmbuf[1] + shmcon->buf_len[1]) > (uintptr_t)shmbuf[0]) { |
5ba3f43e | 446 | goto validation_failure; |
0a7de745 | 447 | } |
5ba3f43e A |
448 | } |
449 | shmcon->tail_in = shmcon->head_in; /* Clear input buffer */ | |
450 | shmcon_barrier(); | |
451 | } else { | |
452 | validation_failure: | |
453 | shmcon->magic = 0; | |
454 | shmcon_barrier(); | |
455 | shmcon->buf_len[CBUF_IN] = (uint32_t)INBUF_SIZE; | |
456 | shmbuf[CBUF_IN] = (uint8_t *)va_buffer_base; | |
457 | shmbuf[CBUF_OUT] = (uint8_t *)ROUNDUP(va_buffer_base + INBUF_SIZE, CPU_CACHELINE_SIZE); | |
458 | for (i = 0; i < 2; i++) { | |
459 | shmcon->midx[i] = 0; | |
460 | shmcon->sidx[i] = 0; | |
461 | shmcon->buf_paddr[i] = (uintptr_t)ml_vtophys((vm_offset_t)shmbuf[i]); | |
462 | } | |
463 | shmcon->buf_len[CBUF_OUT] = (uint32_t)(va_buffer_end - (uintptr_t)shmbuf[CBUF_OUT]); | |
464 | shmcon->version = SHMCON_VERSION; | |
465 | #pragma clang diagnostic push | |
466 | #pragma clang diagnostic ignored "-Wcast-qual" | |
467 | memset((void *)shmcon->name, ' ', sizeof(shmcon->name)); | |
468 | memcpy((void *)shmcon->name, SHMCON_NAME, MIN(sizeof(shmcon->name), strlen(SHMCON_NAME))); | |
469 | #pragma clang diagnostic pop | |
0a7de745 | 470 | for (i = 0; i < NUM_CHILDREN; i++) { |
5ba3f43e | 471 | shmcon->child[0] = 0; |
0a7de745 | 472 | } |
5ba3f43e A |
473 | shmcon_barrier(); |
474 | shmcon->magic = SHMCON_MAGIC; | |
475 | } | |
476 | end = (volatile struct shm_buffer_info *)va_buffer_end; | |
477 | end->base = pa_panic_base; | |
478 | end->unused = 0; | |
479 | shmcon_barrier(); | |
480 | end->magic = SHMCON_MAGIC; | |
481 | #ifdef SHMCON_THROTTLED | |
482 | grace = gPEClockFrequencyInfo.timebase_frequency_hz; | |
483 | #endif | |
484 | ||
485 | PE_consistent_debug_register(kDbgIdConsoleHeaderAP, pa_panic_base, panic_size); | |
486 | } | |
487 | ||
cb323159 | 488 | SECURITY_READ_ONLY_LATE(static struct pe_serial_functions) shmcon_serial_functions = |
5ba3f43e A |
489 | { |
490 | .uart_init = shmcon_init, | |
491 | .uart_set_baud_rate = shmcon_set_baud_rate, | |
492 | .tr0 = shmcon_tr0, | |
493 | .td0 = shmcon_td0, | |
494 | .rr0 = shmcon_rr0, | |
495 | .rd0 = shmcon_rd0 | |
496 | }; | |
497 | ||
0a7de745 A |
498 | int |
499 | pe_shmcon_set_child(uint64_t paddr, uint32_t entry) | |
5ba3f43e | 500 | { |
0a7de745 | 501 | if (shmcon == NULL) { |
5ba3f43e | 502 | return -1; |
0a7de745 | 503 | } |
5ba3f43e | 504 | |
0a7de745 | 505 | if (shmcon->children >= entry) { |
5ba3f43e | 506 | return -1; |
0a7de745 | 507 | } |
5ba3f43e A |
508 | |
509 | shmcon->child[entry] = paddr; | |
510 | return 0; | |
511 | } | |
512 | ||
513 | #endif /* SHMCON */ | |
514 | ||
515 | /*****************************************************************************/ | |
516 | ||
5ba3f43e | 517 | #ifdef DOCKCHANNEL_UART |
0a7de745 | 518 | #define DOCKCHANNEL_WR_MAX_STALL_US (30*1000) |
5ba3f43e | 519 | |
0a7de745 A |
520 | static vm_offset_t dock_agent_base; |
521 | static uint32_t max_dockchannel_drain_period; | |
522 | static bool use_sw_drain; | |
f427ee49 | 523 | static uint32_t dock_wstat_mask; |
0a7de745 A |
524 | static uint64_t prev_dockchannel_drained_time; // Last time we've seen the DockChannel drained by an external agent |
525 | static uint64_t prev_dockchannel_spaces; // Previous w_stat level of the DockChannel. | |
526 | static uint64_t dockchannel_stall_grace; | |
cb323159 | 527 | static vm_offset_t dockchannel_uart_base = 0; |
5ba3f43e A |
528 | |
529 | //======================= | |
530 | // Local funtions | |
531 | //======================= | |
532 | ||
0a7de745 A |
533 | static int |
534 | dockchannel_drain_on_stall() | |
5ba3f43e A |
535 | { |
536 | // Called when DockChannel runs out of spaces. | |
537 | // Check if the DockChannel reader has stalled. If so, empty the DockChannel ourselves. | |
538 | // Return number of bytes drained. | |
539 | ||
540 | if ((mach_absolute_time() - prev_dockchannel_drained_time) >= dockchannel_stall_grace) { | |
541 | // It's been more than DOCKCHANEL_WR_MAX_STALL_US and nobody read from the FIFO | |
542 | // Drop a character. | |
f427ee49 | 543 | (void)rDOCKCHANNELS_DOCK_RDATA1(DOCKCHANNEL_UART_CHANNEL); |
cb323159 | 544 | os_atomic_inc(&prev_dockchannel_spaces, relaxed); |
5ba3f43e A |
545 | return 1; |
546 | } | |
547 | return 0; | |
548 | } | |
549 | ||
0a7de745 A |
550 | static int |
551 | dockchannel_uart_tr0(void) | |
5ba3f43e A |
552 | { |
553 | if (use_sw_drain) { | |
f427ee49 | 554 | uint32_t spaces = rDOCKCHANNELS_DEV_WSTAT(DOCKCHANNEL_UART_CHANNEL) & dock_wstat_mask; |
5ba3f43e A |
555 | if (spaces > prev_dockchannel_spaces) { |
556 | // More spaces showed up. That can only mean someone read the FIFO. | |
557 | // Note that if the DockFIFO is empty we cannot tell if someone is listening, | |
558 | // we can only give them the benefit of the doubt. | |
559 | prev_dockchannel_drained_time = mach_absolute_time(); | |
560 | } | |
561 | prev_dockchannel_spaces = spaces; | |
562 | ||
563 | return spaces || dockchannel_drain_on_stall(); | |
564 | } else { | |
565 | // Returns spaces in dockchannel fifo | |
f427ee49 | 566 | return rDOCKCHANNELS_DEV_WSTAT(DOCKCHANNEL_UART_CHANNEL) & dock_wstat_mask; |
5ba3f43e A |
567 | } |
568 | } | |
569 | ||
0a7de745 A |
570 | static void |
571 | dockchannel_uart_td0(int c) | |
5ba3f43e A |
572 | { |
573 | rDOCKCHANNELS_DEV_WDATA1(DOCKCHANNEL_UART_CHANNEL) = (unsigned)(c & 0xff); | |
574 | if (use_sw_drain) { | |
cb323159 | 575 | os_atomic_dec(&prev_dockchannel_spaces, relaxed); // After writing a byte we have one fewer space than previously expected. |
5ba3f43e A |
576 | } |
577 | } | |
578 | ||
0a7de745 A |
579 | static int |
580 | dockchannel_uart_rr0(void) | |
5ba3f43e A |
581 | { |
582 | return rDOCKCHANNELS_DEV_RDATA0(DOCKCHANNEL_UART_CHANNEL) & 0x7f; | |
583 | } | |
584 | ||
0a7de745 A |
585 | static int |
586 | dockchannel_uart_rd0(void) | |
5ba3f43e | 587 | { |
0a7de745 | 588 | return (int)((rDOCKCHANNELS_DEV_RDATA1(DOCKCHANNEL_UART_CHANNEL) >> 8) & 0xff); |
5ba3f43e A |
589 | } |
590 | ||
cb323159 A |
591 | static void |
592 | dockchannel_uart_clear_intr(void) | |
593 | { | |
594 | rDOCKCHANNELS_AGENT_AP_INTR_CTRL &= ~(0x3); | |
595 | rDOCKCHANNELS_AGENT_AP_INTR_STATUS |= 0x3; | |
596 | rDOCKCHANNELS_AGENT_AP_ERR_INTR_CTRL &= ~(0x3); | |
597 | rDOCKCHANNELS_AGENT_AP_ERR_INTR_STATUS |= 0x3; | |
598 | } | |
599 | ||
0a7de745 A |
600 | static void |
601 | dockchannel_uart_init(void) | |
5ba3f43e A |
602 | { |
603 | if (use_sw_drain) { | |
604 | nanoseconds_to_absolutetime(DOCKCHANNEL_WR_MAX_STALL_US * NSEC_PER_USEC, &dockchannel_stall_grace); | |
605 | } | |
606 | ||
607 | // Clear all interrupt enable and status bits | |
cb323159 | 608 | dockchannel_uart_clear_intr(); |
5ba3f43e A |
609 | |
610 | // Setup DRAIN timer | |
611 | rDOCKCHANNELS_DEV_DRAIN_CFG(DOCKCHANNEL_UART_CHANNEL) = max_dockchannel_drain_period; | |
612 | ||
a39ff7e2 | 613 | // Drain timer doesn't get loaded with value from drain period register if fifo |
0a7de745 | 614 | // is already full. Drop a character from the fifo. |
5ba3f43e A |
615 | rDOCKCHANNELS_DOCK_RDATA1(DOCKCHANNEL_UART_CHANNEL); |
616 | } | |
617 | ||
cb323159 | 618 | SECURITY_READ_ONLY_LATE(static struct pe_serial_functions) dockchannel_uart_serial_functions = |
5ba3f43e A |
619 | { |
620 | .uart_init = dockchannel_uart_init, | |
621 | .uart_set_baud_rate = NULL, | |
622 | .tr0 = dockchannel_uart_tr0, | |
623 | .td0 = dockchannel_uart_td0, | |
624 | .rr0 = dockchannel_uart_rr0, | |
625 | .rd0 = dockchannel_uart_rd0 | |
626 | }; | |
627 | ||
628 | #endif /* DOCKCHANNEL_UART */ | |
629 | ||
d9a64523 | 630 | /****************************************************************************/ |
0a7de745 | 631 | #ifdef PI3_UART |
c3c9b80d A |
632 | static vm_offset_t pi3_gpio_base_vaddr = 0; |
633 | static vm_offset_t pi3_aux_base_vaddr = 0; | |
0a7de745 A |
634 | static int |
635 | pi3_uart_tr0(void) | |
d9a64523 | 636 | { |
0a7de745 | 637 | return (int) BCM2837_GET32(BCM2837_AUX_MU_LSR_REG_V) & 0x20; |
d9a64523 A |
638 | } |
639 | ||
0a7de745 A |
640 | static void |
641 | pi3_uart_td0(int c) | |
d9a64523 | 642 | { |
0a7de745 | 643 | BCM2837_PUT32(BCM2837_AUX_MU_IO_REG_V, (uint32_t) c); |
d9a64523 A |
644 | } |
645 | ||
0a7de745 A |
646 | static int |
647 | pi3_uart_rr0(void) | |
648 | { | |
649 | return (int) BCM2837_GET32(BCM2837_AUX_MU_LSR_REG_V) & 0x01; | |
d9a64523 A |
650 | } |
651 | ||
0a7de745 A |
652 | static int |
653 | pi3_uart_rd0(void) | |
d9a64523 | 654 | { |
0a7de745 | 655 | return (int) BCM2837_GET32(BCM2837_AUX_MU_IO_REG_V) & 0xff; |
d9a64523 A |
656 | } |
657 | ||
0a7de745 A |
658 | static void |
659 | pi3_uart_init(void) | |
d9a64523 A |
660 | { |
661 | // Scratch variable | |
662 | uint32_t i; | |
663 | ||
664 | // Reset mini uart registers | |
665 | BCM2837_PUT32(BCM2837_AUX_ENABLES_V, 1); | |
666 | BCM2837_PUT32(BCM2837_AUX_MU_CNTL_REG_V, 0); | |
667 | BCM2837_PUT32(BCM2837_AUX_MU_LCR_REG_V, 3); | |
668 | BCM2837_PUT32(BCM2837_AUX_MU_MCR_REG_V, 0); | |
669 | BCM2837_PUT32(BCM2837_AUX_MU_IER_REG_V, 0); | |
670 | BCM2837_PUT32(BCM2837_AUX_MU_IIR_REG_V, 0xC6); | |
671 | BCM2837_PUT32(BCM2837_AUX_MU_BAUD_REG_V, 270); | |
672 | ||
f427ee49 | 673 | i = (uint32_t)BCM2837_FSEL_REG(14); |
d9a64523 A |
674 | // Configure GPIOs 14 & 15 for alternate function 5 |
675 | i &= ~(BCM2837_FSEL_MASK(14)); | |
676 | i |= (BCM2837_FSEL_ALT5 << BCM2837_FSEL_OFFS(14)); | |
677 | i &= ~(BCM2837_FSEL_MASK(15)); | |
678 | i |= (BCM2837_FSEL_ALT5 << BCM2837_FSEL_OFFS(15)); | |
679 | ||
680 | BCM2837_PUT32(BCM2837_FSEL_REG(14), i); | |
681 | ||
682 | BCM2837_PUT32(BCM2837_GPPUD_V, 0); | |
683 | ||
684 | // Barrier before AP spinning for 150 cycles | |
685 | __builtin_arm_isb(ISB_SY); | |
686 | ||
0a7de745 A |
687 | for (i = 0; i < 150; i++) { |
688 | asm volatile ("add x0, x0, xzr"); | |
d9a64523 | 689 | } |
5ba3f43e | 690 | |
d9a64523 A |
691 | __builtin_arm_isb(ISB_SY); |
692 | ||
0a7de745 | 693 | BCM2837_PUT32(BCM2837_GPPUDCLK0_V, (1 << 14) | (1 << 15)); |
d9a64523 A |
694 | |
695 | __builtin_arm_isb(ISB_SY); | |
696 | ||
0a7de745 A |
697 | for (i = 0; i < 150; i++) { |
698 | asm volatile ("add x0, x0, xzr"); | |
d9a64523 A |
699 | } |
700 | ||
701 | __builtin_arm_isb(ISB_SY); | |
702 | ||
703 | BCM2837_PUT32(BCM2837_GPPUDCLK0_V, 0); | |
704 | ||
705 | BCM2837_PUT32(BCM2837_AUX_MU_CNTL_REG_V, 3); | |
706 | } | |
707 | ||
cb323159 | 708 | SECURITY_READ_ONLY_LATE(static struct pe_serial_functions) pi3_uart_serial_functions = |
d9a64523 A |
709 | { |
710 | .uart_init = pi3_uart_init, | |
711 | .uart_set_baud_rate = NULL, | |
712 | .tr0 = pi3_uart_tr0, | |
713 | .td0 = pi3_uart_td0, | |
714 | .rr0 = pi3_uart_rr0, | |
715 | .rd0 = pi3_uart_rd0 | |
716 | }; | |
717 | ||
718 | #endif /* PI3_UART */ | |
c3c9b80d A |
719 | |
720 | /*****************************************************************************/ | |
721 | ||
722 | ||
d9a64523 | 723 | /*****************************************************************************/ |
cb323159 A |
724 | |
725 | static void | |
726 | register_serial_functions(struct pe_serial_functions *fns) | |
727 | { | |
728 | fns->next = gPESF; | |
729 | gPESF = fns; | |
730 | } | |
731 | ||
5ba3f43e A |
732 | int |
733 | serial_init(void) | |
734 | { | |
0a7de745 | 735 | DTEntry entryP = NULL; |
cb323159 | 736 | uint32_t prop_size; |
0a7de745 | 737 | vm_offset_t soc_base; |
f427ee49 A |
738 | uintptr_t const *reg_prop; |
739 | uint32_t const *prop_value __unused = NULL; | |
740 | char const *serial_compat __unused = 0; | |
cb323159 | 741 | uint32_t dccmode; |
5ba3f43e | 742 | |
cb323159 A |
743 | struct pe_serial_functions *fns = gPESF; |
744 | ||
745 | if (uart_initted) { | |
746 | while (fns != NULL) { | |
747 | fns->uart_init(); | |
748 | fns = fns->next; | |
749 | } | |
5ba3f43e A |
750 | kprintf("reinit serial\n"); |
751 | return 1; | |
752 | } | |
d9a64523 | 753 | |
5ba3f43e | 754 | dccmode = 0; |
0a7de745 | 755 | if (PE_parse_boot_argn("dcc", &dccmode, sizeof(dccmode))) { |
cb323159 | 756 | register_serial_functions(&dcc_serial_functions); |
5ba3f43e A |
757 | } |
758 | #ifdef SHMCON | |
cb323159 | 759 | uint32_t jconmode = 0; |
5ba3f43e | 760 | if (PE_parse_boot_argn("jcon", &jconmode, sizeof jconmode)) { |
cb323159 | 761 | register_serial_functions(&shmcon_serial_functions); |
5ba3f43e A |
762 | } |
763 | #endif /* SHMCON */ | |
764 | ||
765 | soc_base = pe_arm_get_soc_base_phys(); | |
766 | ||
0a7de745 | 767 | if (soc_base == 0) { |
5ba3f43e | 768 | return 0; |
0a7de745 | 769 | } |
5ba3f43e | 770 | |
cb323159 | 771 | #ifdef PI3_UART |
f427ee49 A |
772 | if (SecureDTFindEntry("name", "gpio", &entryP) == kSuccess) { |
773 | SecureDTGetProperty(entryP, "reg", (void const **)®_prop, &prop_size); | |
cb323159 A |
774 | pi3_gpio_base_vaddr = ml_io_map(soc_base + *reg_prop, *(reg_prop + 1)); |
775 | } | |
f427ee49 A |
776 | if (SecureDTFindEntry("name", "aux", &entryP) == kSuccess) { |
777 | SecureDTGetProperty(entryP, "reg", (void const **)®_prop, &prop_size); | |
cb323159 A |
778 | pi3_aux_base_vaddr = ml_io_map(soc_base + *reg_prop, *(reg_prop + 1)); |
779 | } | |
780 | if ((pi3_gpio_base_vaddr != 0) && (pi3_aux_base_vaddr != 0)) { | |
781 | register_serial_functions(&pi3_uart_serial_functions); | |
782 | } | |
783 | #endif /* PI3_UART */ | |
784 | ||
c3c9b80d | 785 | |
5ba3f43e | 786 | #ifdef DOCKCHANNEL_UART |
cb323159 | 787 | uint32_t no_dockchannel_uart = 0; |
f427ee49 A |
788 | if (SecureDTFindEntry("name", "dockchannel-uart", &entryP) == kSuccess) { |
789 | SecureDTGetProperty(entryP, "reg", (void const **)®_prop, &prop_size); | |
cb323159 A |
790 | // Should be two reg entries |
791 | if (prop_size / sizeof(uintptr_t) != 4) { | |
792 | panic("Malformed dockchannel-uart property"); | |
793 | } | |
794 | dockchannel_uart_base = ml_io_map(soc_base + *reg_prop, *(reg_prop + 1)); | |
795 | dock_agent_base = ml_io_map(soc_base + *(reg_prop + 2), *(reg_prop + 3)); | |
796 | PE_parse_boot_argn("no-dockfifo-uart", &no_dockchannel_uart, sizeof(no_dockchannel_uart)); | |
797 | // Keep the old name for boot-arg | |
798 | if (no_dockchannel_uart == 0) { | |
799 | register_serial_functions(&dockchannel_uart_serial_functions); | |
f427ee49 | 800 | SecureDTGetProperty(entryP, "max-aop-clk", (void const **)&prop_value, &prop_size); |
5ba3f43e | 801 | max_dockchannel_drain_period = (uint32_t)((prop_value)? (*prop_value * 0.03) : DOCKCHANNEL_DRAIN_PERIOD); |
f427ee49 A |
802 | prop_value = NULL; |
803 | SecureDTGetProperty(entryP, "enable-sw-drain", (void const **)&prop_value, &prop_size); | |
5ba3f43e | 804 | use_sw_drain = (prop_value)? *prop_value : 0; |
f427ee49 A |
805 | prop_value = NULL; |
806 | SecureDTGetProperty(entryP, "dock-wstat-mask", (void const **)&prop_value, &prop_size); | |
807 | dock_wstat_mask = (prop_value)? *prop_value : 0x1ff; | |
cb323159 A |
808 | } else { |
809 | dockchannel_uart_clear_intr(); | |
5ba3f43e A |
810 | } |
811 | // If no dockchannel-uart is found in the device tree, fall back | |
812 | // to looking for the traditional UART serial console. | |
813 | } | |
cb323159 | 814 | |
5ba3f43e A |
815 | #endif /* DOCKCHANNEL_UART */ |
816 | ||
817 | /* | |
818 | * The boot serial port should have a property named "boot-console". | |
819 | * If we don't find it there, look for "uart0" and "uart1". | |
820 | */ | |
821 | ||
f427ee49 A |
822 | if (SecureDTFindEntry("boot-console", NULL, &entryP) == kSuccess) { |
823 | SecureDTGetProperty(entryP, "reg", (void const **)®_prop, &prop_size); | |
5ba3f43e | 824 | uart_base = ml_io_map(soc_base + *reg_prop, *(reg_prop + 1)); |
0a7de745 | 825 | if (serial_compat == 0) { |
f427ee49 | 826 | SecureDTGetProperty(entryP, "compatible", (void const **)&serial_compat, &prop_size); |
0a7de745 | 827 | } |
f427ee49 A |
828 | } else if (SecureDTFindEntry("name", "uart0", &entryP) == kSuccess) { |
829 | SecureDTGetProperty(entryP, "reg", (void const **)®_prop, &prop_size); | |
5ba3f43e | 830 | uart_base = ml_io_map(soc_base + *reg_prop, *(reg_prop + 1)); |
0a7de745 | 831 | if (serial_compat == 0) { |
f427ee49 | 832 | SecureDTGetProperty(entryP, "compatible", (void const **)&serial_compat, &prop_size); |
0a7de745 | 833 | } |
f427ee49 A |
834 | } else if (SecureDTFindEntry("name", "uart1", &entryP) == kSuccess) { |
835 | SecureDTGetProperty(entryP, "reg", (void const **)®_prop, &prop_size); | |
5ba3f43e | 836 | uart_base = ml_io_map(soc_base + *reg_prop, *(reg_prop + 1)); |
0a7de745 | 837 | if (serial_compat == 0) { |
f427ee49 | 838 | SecureDTGetProperty(entryP, "compatible", (void const **)&serial_compat, &prop_size); |
0a7de745 | 839 | } |
5ba3f43e | 840 | } |
0a7de745 | 841 | #ifdef S3CUART |
5ba3f43e | 842 | if (NULL != entryP) { |
f427ee49 | 843 | SecureDTGetProperty(entryP, "pclk", (void const **)&prop_value, &prop_size); |
0a7de745 A |
844 | if (prop_value) { |
845 | dt_pclk = *prop_value; | |
846 | } | |
5ba3f43e A |
847 | |
848 | prop_value = NULL; | |
f427ee49 | 849 | SecureDTGetProperty(entryP, "sampling", (void const **)&prop_value, &prop_size); |
0a7de745 A |
850 | if (prop_value) { |
851 | dt_sampling = *prop_value; | |
852 | } | |
5ba3f43e A |
853 | |
854 | prop_value = NULL; | |
f427ee49 | 855 | SecureDTGetProperty(entryP, "ubrdiv", (void const **)&prop_value, &prop_size); |
0a7de745 A |
856 | if (prop_value) { |
857 | dt_ubrdiv = *prop_value; | |
858 | } | |
5ba3f43e | 859 | } |
f427ee49 A |
860 | |
861 | if (serial_compat) { | |
862 | if (!strcmp(serial_compat, "uart,16550")) { | |
863 | register_serial_functions(&ln2410_serial_functions); | |
864 | } else if (!strcmp(serial_compat, "uart-16550")) { | |
865 | register_serial_functions(&ln2410_serial_functions); | |
866 | } else if (!strcmp(serial_compat, "uart,s5i3000")) { | |
867 | register_serial_functions(&ln2410_serial_functions); | |
868 | } else if (!strcmp(serial_compat, "uart-1,samsung")) { | |
869 | register_serial_functions(&ln2410_serial_functions); | |
870 | } | |
0a7de745 | 871 | } |
cb323159 A |
872 | #endif /* S3CUART */ |
873 | ||
874 | if (gPESF == NULL) { | |
5ba3f43e | 875 | return 0; |
0a7de745 | 876 | } |
5ba3f43e | 877 | |
cb323159 A |
878 | fns = gPESF; |
879 | while (fns != NULL) { | |
880 | fns->uart_init(); | |
881 | fns = fns->next; | |
882 | } | |
5ba3f43e | 883 | |
f427ee49 A |
884 | #if HIBERNATION |
885 | /* hibernation needs to know the UART register addresses since it can't directly use this serial driver */ | |
886 | if (dockchannel_uart_base) { | |
887 | gHibernateGlobals.dockChannelRegBase = ml_vtophys(dockchannel_uart_base); | |
888 | gHibernateGlobals.dockChannelWstatMask = dock_wstat_mask; | |
889 | } | |
890 | if (uart_base) { | |
891 | gHibernateGlobals.hibUartRegBase = ml_vtophys(uart_base); | |
892 | } | |
893 | #endif /* HIBERNATION */ | |
894 | ||
5ba3f43e A |
895 | uart_initted = 1; |
896 | ||
897 | return 1; | |
898 | } | |
899 | ||
900 | void | |
901 | uart_putc(char c) | |
902 | { | |
cb323159 A |
903 | struct pe_serial_functions *fns = gPESF; |
904 | while (fns != NULL) { | |
905 | while (!fns->tr0()) { | |
f427ee49 A |
906 | #if __arm64__ /* on arm64, we have a WFE timeout, so no need to hot-poll here */ |
907 | __builtin_arm_wfe() | |
908 | #endif | |
0a7de745 A |
909 | ; /* Wait until THR is empty. */ |
910 | } | |
cb323159 A |
911 | fns->td0(c); |
912 | fns = fns->next; | |
5ba3f43e A |
913 | } |
914 | } | |
915 | ||
916 | int | |
917 | uart_getc(void) | |
0a7de745 | 918 | { /* returns -1 if no data available */ |
cb323159 A |
919 | struct pe_serial_functions *fns = gPESF; |
920 | while (fns != NULL) { | |
921 | if (fns->rr0()) { | |
922 | return fns->rd0(); | |
0a7de745 | 923 | } |
cb323159 | 924 | fns = fns->next; |
5ba3f43e A |
925 | } |
926 | return -1; | |
927 | } |