1 /* $NetBSD: qcomsmem.c,v 1.1 2024/12/30 12:31:10 jmcneill Exp $ */
2 /* $OpenBSD: qcsmem.c,v 1.1 2023/05/19 21:13:49 patrick Exp $ */
3 /*
4 * Copyright (c) 2023 Patrick Wildt <patrick@blueri.se>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/param.h>
20 #include <sys/systm.h>
21 #include <sys/device.h>
22 #include <sys/kmem.h>
23
24 #include <dev/acpi/acpivar.h>
25 #include <dev/acpi/qcomsmem.h>
26
27 #define QCSMEM_ITEM_FIXED 8
28 #define QCSMEM_ITEM_COUNT 512
29 #define QCSMEM_HOST_COUNT 15
30
31 struct qcsmem_proc_comm {
32 uint32_t command;
33 uint32_t status;
34 uint32_t params[2];
35 };
36
37 struct qcsmem_global_entry {
38 uint32_t allocated;
39 uint32_t offset;
40 uint32_t size;
41 uint32_t aux_base;
42 #define QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK 0xfffffffc
43 };
44
45 struct qcsmem_header {
46 struct qcsmem_proc_comm proc_comm[4];
47 uint32_t version[32];
48 #define QCSMEM_HEADER_VERSION_MASTER_SBL_IDX 7
49 #define QCSMEM_HEADER_VERSION_GLOBAL_HEAP 11
50 #define QCSMEM_HEADER_VERSION_GLOBAL_PART 12
51 uint32_t initialized;
52 uint32_t free_offset;
53 uint32_t available;
54 uint32_t reserved;
55 struct qcsmem_global_entry toc[QCSMEM_ITEM_COUNT];
56 };
57
58 struct qcsmem_ptable_entry {
59 uint32_t offset;
60 uint32_t size;
61 uint32_t flags;
62 uint16_t host[2];
63 #define QCSMEM_LOCAL_HOST 0
64 #define QCSMEM_GLOBAL_HOST 0xfffe
65 uint32_t cacheline;
66 uint32_t reserved[7];
67 };
68
69 struct qcsmem_ptable {
70 uint32_t magic;
71 #define QCSMEM_PTABLE_MAGIC 0x434f5424
72 uint32_t version;
73 #define QCSMEM_PTABLE_VERSION 1
74 uint32_t num_entries;
75 uint32_t reserved[5];
76 struct qcsmem_ptable_entry entry[];
77 };
78
79 struct qcsmem_partition_header {
80 uint32_t magic;
81 #define QCSMEM_PART_HDR_MAGIC 0x54525024
82 uint16_t host[2];
83 uint32_t size;
84 uint32_t offset_free_uncached;
85 uint32_t offset_free_cached;
86 uint32_t reserved[3];
87 };
88
89 struct qcsmem_partition {
90 struct qcsmem_partition_header *phdr;
91 size_t cacheline;
92 size_t size;
93 };
94
95 struct qcsmem_private_entry {
96 uint16_t canary;
97 #define QCSMEM_PRIV_ENTRY_CANARY 0xa5a5
98 uint16_t item;
99 uint32_t size;
100 uint16_t padding_data;
101 uint16_t padding_hdr;
102 uint32_t reserved;
103 };
104
105 struct qcsmem_info {
106 uint32_t magic;
107 #define QCSMEM_INFO_MAGIC 0x49494953
108 uint32_t size;
109 uint32_t base_addr;
110 uint32_t reserved;
111 uint32_t num_items;
112 };
113
114 struct qcsmem_softc {
115 device_t sc_dev;
116 bus_space_tag_t sc_iot;
117 void *sc_smem;
118 bus_space_handle_t sc_mtx_ioh;
119
120 bus_addr_t sc_aux_base;
121 bus_size_t sc_aux_size;
122
123 int sc_item_count;
124 struct qcsmem_partition sc_global_partition;
125 struct qcsmem_partition sc_partitions[QCSMEM_HOST_COUNT];
126 };
127
128 #define QCMTX_OFF(idx) ((idx) * 0x1000)
129 #define QCMTX_NUM_LOCKS 32
130 #define QCMTX_APPS_PROC_ID 1
131
132 #define MTXREAD4(sc, reg) \
133 bus_space_read_4((sc)->sc_iot, (sc)->sc_mtx_ioh, (reg))
134 #define MTXWRITE4(sc, reg, val) \
135 bus_space_write_4((sc)->sc_iot, (sc)->sc_mtx_ioh, (reg), (val))
136
137 struct qcsmem_softc *qcsmem_sc;
138
139 #define QCSMEM_X1E_BASE 0xffe00000
140 #define QCSMEM_X1E_SIZE 0x200000
141
142 #define QCMTX_X1E_BASE 0x01f40000
143 #define QCMTX_X1E_SIZE 0x20000
144
145 #define QCSMEM_X1E_LOCK_IDX 3
146
147 static const struct device_compatible_entry compat_data[] = {
148 { .compat = "QCOM0C84" },
149 DEVICE_COMPAT_EOL
150 };
151
152 static int qcsmem_match(device_t, cfdata_t, void *);
153 static void qcsmem_attach(device_t, device_t, void *);
154 static int qcmtx_lock(struct qcsmem_softc *, u_int, u_int);
155 static void qcmtx_unlock(struct qcsmem_softc *, u_int);
156
157 CFATTACH_DECL_NEW(qcomsmem, sizeof(struct qcsmem_softc),
158 qcsmem_match, qcsmem_attach, NULL, NULL);
159
160 static int
qcsmem_match(device_t parent,cfdata_t match,void * aux)161 qcsmem_match(device_t parent, cfdata_t match, void *aux)
162 {
163 struct acpi_attach_args *aa = aux;
164
165 return acpi_compatible_match(aa, compat_data);
166 }
167
168 static void
qcsmem_attach(device_t parent,device_t self,void * aux)169 qcsmem_attach(device_t parent, device_t self, void *aux)
170 {
171 struct qcsmem_softc *sc = device_private(self);
172 struct acpi_attach_args *aa = aux;
173 struct qcsmem_header *header;
174 struct qcsmem_ptable *ptable;
175 struct qcsmem_ptable_entry *pte;
176 struct qcsmem_info *info;
177 struct qcsmem_partition *part;
178 struct qcsmem_partition_header *phdr;
179 uintptr_t smem_va;
180 uint32_t hdr_version;
181 int i;
182
183 sc->sc_dev = self;
184 sc->sc_iot = aa->aa_memt;
185 sc->sc_smem = AcpiOsMapMemory(QCSMEM_X1E_BASE, QCSMEM_X1E_SIZE);
186 KASSERT(sc->sc_smem != NULL);
187
188 sc->sc_aux_base = QCSMEM_X1E_BASE;
189 sc->sc_aux_size = QCSMEM_X1E_SIZE;
190
191 if (bus_space_map(sc->sc_iot, QCMTX_X1E_BASE,
192 QCMTX_X1E_SIZE, 0, &sc->sc_mtx_ioh)) {
193 aprint_error(": can't map mutex registers\n");
194 return;
195 }
196
197 smem_va = (uintptr_t)sc->sc_smem;
198
199 ptable = (void *)(smem_va + sc->sc_aux_size - PAGE_SIZE);
200 if (ptable->magic != QCSMEM_PTABLE_MAGIC ||
201 ptable->version != QCSMEM_PTABLE_VERSION) {
202 aprint_error(": unsupported ptable 0x%x/0x%x\n",
203 ptable->magic, ptable->version);
204 return;
205 }
206
207 header = (void *)smem_va;
208 hdr_version = header->version[QCSMEM_HEADER_VERSION_MASTER_SBL_IDX] >> 16;
209 if (hdr_version != QCSMEM_HEADER_VERSION_GLOBAL_PART) {
210 aprint_error(": unsupported header 0x%x\n", hdr_version);
211 return;
212 }
213
214 for (i = 0; i < ptable->num_entries; i++) {
215 pte = &ptable->entry[i];
216 if (!pte->offset || !pte->size)
217 continue;
218 if (pte->host[0] == QCSMEM_GLOBAL_HOST &&
219 pte->host[1] == QCSMEM_GLOBAL_HOST)
220 part = &sc->sc_global_partition;
221 else if (pte->host[0] == QCSMEM_LOCAL_HOST &&
222 pte->host[1] < QCSMEM_HOST_COUNT)
223 part = &sc->sc_partitions[pte->host[1]];
224 else if (pte->host[1] == QCSMEM_LOCAL_HOST &&
225 pte->host[0] < QCSMEM_HOST_COUNT)
226 part = &sc->sc_partitions[pte->host[0]];
227 else
228 continue;
229 if (part->phdr != NULL)
230 continue;
231 phdr = (void *)(smem_va + pte->offset);
232 if (phdr->magic != QCSMEM_PART_HDR_MAGIC) {
233 aprint_error(": unsupported partition 0x%x\n",
234 phdr->magic);
235 return;
236 }
237 if (pte->host[0] != phdr->host[0] ||
238 pte->host[1] != phdr->host[1]) {
239 aprint_error(": bad hosts 0x%x/0x%x+0x%x/0x%x\n",
240 pte->host[0], phdr->host[0],
241 pte->host[1], phdr->host[1]);
242 return;
243 }
244 if (pte->size != phdr->size) {
245 aprint_error(": bad size 0x%x/0x%x\n",
246 pte->size, phdr->size);
247 return;
248 }
249 if (phdr->offset_free_uncached > phdr->size) {
250 aprint_error(": bad size 0x%x > 0x%x\n",
251 phdr->offset_free_uncached, phdr->size);
252 return;
253 }
254 part->phdr = phdr;
255 part->size = pte->size;
256 part->cacheline = pte->cacheline;
257 }
258 if (sc->sc_global_partition.phdr == NULL) {
259 aprint_error(": could not find global partition\n");
260 return;
261 }
262
263 sc->sc_item_count = QCSMEM_ITEM_COUNT;
264 info = (struct qcsmem_info *)&ptable->entry[ptable->num_entries];
265 if (info->magic == QCSMEM_INFO_MAGIC)
266 sc->sc_item_count = info->num_items;
267
268 aprint_naive("\n");
269 aprint_normal("\n");
270
271 qcsmem_sc = sc;
272 }
273
274 static int
qcsmem_alloc_private(struct qcsmem_softc * sc,struct qcsmem_partition * part,int item,int size)275 qcsmem_alloc_private(struct qcsmem_softc *sc, struct qcsmem_partition *part,
276 int item, int size)
277 {
278 struct qcsmem_private_entry *entry, *last;
279 struct qcsmem_partition_header *phdr = part->phdr;
280 uintptr_t phdr_va = (uintptr_t)phdr;
281
282 entry = (void *)&phdr[1];
283 last = (void *)(phdr_va + phdr->offset_free_uncached);
284
285 if ((void *)last > (void *)(phdr_va + part->size))
286 return EINVAL;
287
288 while (entry < last) {
289 if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
290 device_printf(sc->sc_dev, "invalid canary\n");
291 return EINVAL;
292 }
293
294 if (entry->item == item)
295 return 0;
296
297 entry = (void *)((uintptr_t)&entry[1] + entry->padding_hdr +
298 entry->size);
299 }
300
301 if ((void *)entry > (void *)(phdr_va + part->size))
302 return EINVAL;
303
304 if ((uintptr_t)&entry[1] + roundup(size, 8) >
305 phdr_va + phdr->offset_free_cached)
306 return EINVAL;
307
308 entry->canary = QCSMEM_PRIV_ENTRY_CANARY;
309 entry->item = item;
310 entry->size = roundup(size, 8);
311 entry->padding_data = entry->size - size;
312 entry->padding_hdr = 0;
313 membar_producer();
314
315 phdr->offset_free_uncached += sizeof(*entry) + entry->size;
316
317 return 0;
318 }
319
320 static int
qcsmem_alloc_global(struct qcsmem_softc * sc,int item,int size)321 qcsmem_alloc_global(struct qcsmem_softc *sc, int item, int size)
322 {
323 struct qcsmem_header *header;
324 struct qcsmem_global_entry *entry;
325
326 header = (void *)sc->sc_smem;
327 entry = &header->toc[item];
328 if (entry->allocated)
329 return 0;
330
331 size = roundup(size, 8);
332 if (size > header->available)
333 return EINVAL;
334
335 entry->offset = header->free_offset;
336 entry->size = size;
337 membar_producer();
338 entry->allocated = 1;
339
340 header->free_offset += size;
341 header->available -= size;
342
343 return 0;
344 }
345
346 int
qcsmem_alloc(int host,int item,int size)347 qcsmem_alloc(int host, int item, int size)
348 {
349 struct qcsmem_softc *sc = qcsmem_sc;
350 struct qcsmem_partition *part;
351 int ret;
352
353 if (sc == NULL)
354 return ENXIO;
355
356 if (item < QCSMEM_ITEM_FIXED)
357 return EPERM;
358
359 if (item >= sc->sc_item_count)
360 return ENXIO;
361
362 ret = qcmtx_lock(sc, QCSMEM_X1E_LOCK_IDX, 1000);
363 if (ret)
364 return ret;
365
366 if (host < QCSMEM_HOST_COUNT &&
367 sc->sc_partitions[host].phdr != NULL) {
368 part = &sc->sc_partitions[host];
369 ret = qcsmem_alloc_private(sc, part, item, size);
370 } else if (sc->sc_global_partition.phdr != NULL) {
371 part = &sc->sc_global_partition;
372 ret = qcsmem_alloc_private(sc, part, item, size);
373 } else {
374 ret = qcsmem_alloc_global(sc, item, size);
375 }
376
377 qcmtx_unlock(sc, QCSMEM_X1E_LOCK_IDX);
378
379 return ret;
380 }
381
382 static void *
qcsmem_get_private(struct qcsmem_softc * sc,struct qcsmem_partition * part,int item,int * size)383 qcsmem_get_private(struct qcsmem_softc *sc, struct qcsmem_partition *part,
384 int item, int *size)
385 {
386 struct qcsmem_private_entry *entry, *last;
387 struct qcsmem_partition_header *phdr = part->phdr;
388 uintptr_t phdr_va = (uintptr_t)phdr;
389
390 entry = (void *)&phdr[1];
391 last = (void *)(phdr_va + phdr->offset_free_uncached);
392
393 while (entry < last) {
394 if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
395 device_printf(sc->sc_dev, "invalid canary\n");
396 return NULL;
397 }
398
399 if (entry->item == item) {
400 if (size != NULL) {
401 if (entry->size > part->size ||
402 entry->padding_data > entry->size)
403 return NULL;
404 *size = entry->size - entry->padding_data;
405 }
406
407 return (void *)((uintptr_t)&entry[1] + entry->padding_hdr);
408 }
409
410 entry = (void *)((uintptr_t)&entry[1] + entry->padding_hdr +
411 entry->size);
412 }
413
414 if ((uintptr_t)entry > phdr_va + part->size)
415 return NULL;
416
417 entry = (void *)(phdr_va + phdr->size -
418 roundup(sizeof(*entry), part->cacheline));
419 last = (void *)(phdr_va + phdr->offset_free_cached);
420
421 if ((uintptr_t)entry < phdr_va ||
422 (uintptr_t)last > phdr_va + part->size)
423 return NULL;
424
425 while (entry > last) {
426 if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
427 device_printf(sc->sc_dev, "invalid canary\n");
428 return NULL;
429 }
430
431 if (entry->item == item) {
432 if (size != NULL) {
433 if (entry->size > part->size ||
434 entry->padding_data > entry->size)
435 return NULL;
436 *size = entry->size - entry->padding_data;
437 }
438
439 return (void *)((uintptr_t)entry - entry->size);
440 }
441
442 entry = (void *)((uintptr_t)entry - entry->size -
443 roundup(sizeof(*entry), part->cacheline));
444 }
445
446 if ((uintptr_t)entry < phdr_va)
447 return NULL;
448
449 return NULL;
450 }
451
452 static void *
qcsmem_get_global(struct qcsmem_softc * sc,int item,int * size)453 qcsmem_get_global(struct qcsmem_softc *sc, int item, int *size)
454 {
455 struct qcsmem_header *header;
456 struct qcsmem_global_entry *entry;
457 uint32_t aux_base;
458
459 header = (void *)sc->sc_smem;
460 entry = &header->toc[item];
461 if (!entry->allocated)
462 return NULL;
463
464 aux_base = entry->aux_base & QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK;
465 if (aux_base != 0 && aux_base != sc->sc_aux_base)
466 return NULL;
467
468 if (entry->size + entry->offset > sc->sc_aux_size)
469 return NULL;
470
471 if (size != NULL)
472 *size = entry->size;
473
474 return (void *)((uintptr_t)sc->sc_smem +
475 entry->offset);
476 }
477
478 void *
qcsmem_get(int host,int item,int * size)479 qcsmem_get(int host, int item, int *size)
480 {
481 struct qcsmem_softc *sc = qcsmem_sc;
482 struct qcsmem_partition *part;
483 void *p = NULL;
484 int ret;
485
486 if (sc == NULL)
487 return NULL;
488
489 if (item >= sc->sc_item_count)
490 return NULL;
491
492 ret = qcmtx_lock(sc, QCSMEM_X1E_LOCK_IDX, 1000);
493 if (ret)
494 return NULL;
495
496 if (host >= 0 &&
497 host < QCSMEM_HOST_COUNT &&
498 sc->sc_partitions[host].phdr != NULL) {
499 part = &sc->sc_partitions[host];
500 p = qcsmem_get_private(sc, part, item, size);
501 } else if (sc->sc_global_partition.phdr != NULL) {
502 part = &sc->sc_global_partition;
503 p = qcsmem_get_private(sc, part, item, size);
504 } else {
505 p = qcsmem_get_global(sc, item, size);
506 }
507
508 qcmtx_unlock(sc, QCSMEM_X1E_LOCK_IDX);
509 return p;
510 }
511
512 void
qcsmem_memset(void * ptr,uint8_t val,size_t len)513 qcsmem_memset(void *ptr, uint8_t val, size_t len)
514 {
515 if (len % 8 == 0 && val == 0) {
516 volatile uint64_t *p = ptr;
517 size_t n;
518
519 for (n = 0; n < len; n += 8) {
520 p[n] = val;
521 }
522 } else {
523 volatile uint8_t *p = ptr;
524 size_t n;
525
526 for (n = 0; n < len; n++) {
527 p[n] = val;
528 }
529 }
530 }
531
532 static int
qcmtx_dolockunlock(struct qcsmem_softc * sc,u_int idx,int lock)533 qcmtx_dolockunlock(struct qcsmem_softc *sc, u_int idx, int lock)
534 {
535 if (idx >= QCMTX_NUM_LOCKS)
536 return ENXIO;
537
538 if (lock) {
539 MTXWRITE4(sc, QCMTX_OFF(idx), QCMTX_APPS_PROC_ID);
540 if (MTXREAD4(sc, QCMTX_OFF(idx)) !=
541 QCMTX_APPS_PROC_ID)
542 return EAGAIN;
543 KASSERT(MTXREAD4(sc, QCMTX_OFF(idx)) == QCMTX_APPS_PROC_ID);
544 } else {
545 KASSERT(MTXREAD4(sc, QCMTX_OFF(idx)) == QCMTX_APPS_PROC_ID);
546 MTXWRITE4(sc, QCMTX_OFF(idx), 0);
547 }
548
549 return 0;
550 }
551
552 static int
qcmtx_lock(struct qcsmem_softc * sc,u_int idx,u_int timeout_ms)553 qcmtx_lock(struct qcsmem_softc *sc, u_int idx, u_int timeout_ms)
554 {
555 int rv = EINVAL;
556 u_int n;
557
558 for (n = 0; n < timeout_ms; n++) {
559 rv = qcmtx_dolockunlock(sc, idx, 1);
560 if (rv != EAGAIN) {
561 break;
562 }
563 delay(1000);
564 }
565
566 return rv;
567 }
568
569 static void
qcmtx_unlock(struct qcsmem_softc * sc,u_int idx)570 qcmtx_unlock(struct qcsmem_softc *sc, u_int idx)
571 {
572 qcmtx_dolockunlock(sc, idx, 0);
573 }
574