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