#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/atomic.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/fdt.h>
#define QCSMEM_ITEM_FIXED 8
#define QCSMEM_ITEM_COUNT 512
#define QCSMEM_HOST_COUNT 15
struct qcsmem_proc_comm {
uint32_t command;
uint32_t status;
uint32_t params[2];
};
struct qcsmem_global_entry {
uint32_t allocated;
uint32_t offset;
uint32_t size;
uint32_t aux_base;
#define QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK 0xfffffffc
};
struct qcsmem_header {
struct qcsmem_proc_comm proc_comm[4];
uint32_t version[32];
#define QCSMEM_HEADER_VERSION_MASTER_SBL_IDX 7
#define QCSMEM_HEADER_VERSION_GLOBAL_HEAP 11
#define QCSMEM_HEADER_VERSION_GLOBAL_PART 12
uint32_t initialized;
uint32_t free_offset;
uint32_t available;
uint32_t reserved;
struct qcsmem_global_entry toc[QCSMEM_ITEM_COUNT];
};
struct qcsmem_ptable_entry {
uint32_t offset;
uint32_t size;
uint32_t flags;
uint16_t host[2];
#define QCSMEM_LOCAL_HOST 0
#define QCSMEM_GLOBAL_HOST 0xfffe
uint32_t cacheline;
uint32_t reserved[7];
};
struct qcsmem_ptable {
uint32_t magic;
#define QCSMEM_PTABLE_MAGIC 0x434f5424
uint32_t version;
#define QCSMEM_PTABLE_VERSION 1
uint32_t num_entries;
uint32_t reserved[5];
struct qcsmem_ptable_entry entry[];
};
struct qcsmem_partition_header {
uint32_t magic;
#define QCSMEM_PART_HDR_MAGIC 0x54525024
uint16_t host[2];
uint32_t size;
uint32_t offset_free_uncached;
uint32_t offset_free_cached;
uint32_t reserved[3];
};
struct qcsmem_partition {
struct qcsmem_partition_header *phdr;
size_t cacheline;
size_t size;
};
struct qcsmem_private_entry {
uint16_t canary;
#define QCSMEM_PRIV_ENTRY_CANARY 0xa5a5
uint16_t item;
uint32_t size;
uint16_t padding_data;
uint16_t padding_hdr;
uint32_t reserved;
};
struct qcsmem_info {
uint32_t magic;
#define QCSMEM_INFO_MAGIC 0x49494953
uint32_t size;
uint32_t base_addr;
uint32_t reserved;
uint32_t num_items;
};
struct qcsmem_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
int sc_node;
bus_addr_t sc_aux_base;
bus_size_t sc_aux_size;
int sc_item_count;
struct qcsmem_partition sc_global_partition;
struct qcsmem_partition sc_partitions[QCSMEM_HOST_COUNT];
};
struct qcsmem_softc *qcsmem_sc;
int qcsmem_match(struct device *, void *, void *);
void qcsmem_attach(struct device *, struct device *, void *);
const struct cfattach qcsmem_ca = {
sizeof (struct qcsmem_softc), qcsmem_match, qcsmem_attach
};
struct cfdriver qcsmem_cd = {
NULL, "qcsmem", DV_DULL
};
int
qcsmem_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "qcom,smem");
}
void
qcsmem_attach(struct device *parent, struct device *self, void *aux)
{
struct qcsmem_softc *sc = (struct qcsmem_softc *)self;
struct fdt_attach_args *faa = aux;
struct qcsmem_header *header;
struct qcsmem_ptable *ptable;
struct qcsmem_ptable_entry *pte;
struct qcsmem_info *info;
struct qcsmem_partition *part;
struct qcsmem_partition_header *phdr;
uint32_t version;
uint32_t memreg;
uint32_t reg[4];
int i, node;
memreg = OF_getpropint(faa->fa_node, "memory-region", 0);
if (memreg) {
node = OF_getnodebyphandle(memreg);
if (node == 0) {
printf(": can't find memory region\n");
return;
}
if (OF_getpropintarray(node, "reg", reg,
sizeof(reg)) != sizeof(reg)) {
printf(": no registers\n");
return;
}
sc->sc_aux_base = (bus_addr_t)reg[0] << 32 | reg[1];
sc->sc_aux_size = (bus_size_t)reg[2] << 32 | reg[3];
} else {
if (faa->fa_nreg < 1) {
printf(": no registers\n");
return;
}
sc->sc_aux_base = faa->fa_reg[0].addr;
sc->sc_aux_size = faa->fa_reg[0].size;
}
sc->sc_node = faa->fa_node;
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, sc->sc_aux_base, sc->sc_aux_size,
BUS_SPACE_MAP_LINEAR, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
ptable = bus_space_vaddr(sc->sc_iot, sc->sc_ioh) +
sc->sc_aux_size - PAGE_SIZE;
if (ptable->magic != QCSMEM_PTABLE_MAGIC ||
ptable->version != QCSMEM_PTABLE_VERSION) {
printf(": unsupported ptable 0x%x/0x%x\n",
ptable->magic, ptable->version);
bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_aux_size);
return;
}
header = bus_space_vaddr(sc->sc_iot, sc->sc_ioh);
version = header->version[QCSMEM_HEADER_VERSION_MASTER_SBL_IDX] >> 16;
if (version != QCSMEM_HEADER_VERSION_GLOBAL_PART) {
printf(": unsupported header 0x%x\n", version);
return;
}
for (i = 0; i < ptable->num_entries; i++) {
pte = &ptable->entry[i];
if (!pte->offset || !pte->size)
continue;
if (pte->host[0] == QCSMEM_GLOBAL_HOST &&
pte->host[1] == QCSMEM_GLOBAL_HOST)
part = &sc->sc_global_partition;
else if (pte->host[0] == QCSMEM_LOCAL_HOST &&
pte->host[1] < QCSMEM_HOST_COUNT)
part = &sc->sc_partitions[pte->host[1]];
else if (pte->host[1] == QCSMEM_LOCAL_HOST &&
pte->host[0] < QCSMEM_HOST_COUNT)
part = &sc->sc_partitions[pte->host[0]];
else
continue;
if (part->phdr != NULL)
continue;
phdr = bus_space_vaddr(sc->sc_iot, sc->sc_ioh) +
pte->offset;
if (phdr->magic != QCSMEM_PART_HDR_MAGIC) {
printf(": unsupported partition 0x%x\n",
phdr->magic);
return;
}
if (pte->host[0] != phdr->host[0] ||
pte->host[1] != phdr->host[1]) {
printf(": bad hosts 0x%x/0x%x+0x%x/0x%x\n",
pte->host[0], phdr->host[0],
pte->host[1], phdr->host[1]);
return;
}
if (pte->size != phdr->size) {
printf(": bad size 0x%x/0x%x\n",
pte->size, phdr->size);
return;
}
if (phdr->offset_free_uncached > phdr->size) {
printf(": bad size 0x%x > 0x%x\n",
phdr->offset_free_uncached, phdr->size);
return;
}
part->phdr = phdr;
part->size = pte->size;
part->cacheline = pte->cacheline;
}
if (sc->sc_global_partition.phdr == NULL) {
printf(": could not find global partition\n");
return;
}
sc->sc_item_count = QCSMEM_ITEM_COUNT;
info = (struct qcsmem_info *)&ptable->entry[ptable->num_entries];
if (info->magic == QCSMEM_INFO_MAGIC)
sc->sc_item_count = info->num_items;
printf("\n");
qcsmem_sc = sc;
}
int
qcsmem_alloc_private(struct qcsmem_softc *sc, struct qcsmem_partition *part,
int item, int size)
{
struct qcsmem_private_entry *entry, *last;
struct qcsmem_partition_header *phdr = part->phdr;
entry = (void *)&phdr[1];
last = (void *)phdr + phdr->offset_free_uncached;
if ((void *)last > (void *)phdr + part->size)
return EINVAL;
while (entry < last) {
if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
printf("%s: invalid canary\n", sc->sc_dev.dv_xname);
return EINVAL;
}
if (entry->item == item)
return 0;
entry = (void *)&entry[1] + entry->padding_hdr +
entry->size;
}
if ((void *)entry > (void *)phdr + part->size)
return EINVAL;
if ((void *)&entry[1] + roundup(size, 8) >
(void *)phdr + phdr->offset_free_cached)
return EINVAL;
entry->canary = QCSMEM_PRIV_ENTRY_CANARY;
entry->item = item;
entry->size = roundup(size, 8);
entry->padding_data = entry->size - size;
entry->padding_hdr = 0;
membar_producer();
phdr->offset_free_uncached += sizeof(*entry) + entry->size;
return 0;
}
int
qcsmem_alloc_global(struct qcsmem_softc *sc, int item, int size)
{
struct qcsmem_header *header;
struct qcsmem_global_entry *entry;
header = bus_space_vaddr(sc->sc_iot, sc->sc_ioh);
entry = &header->toc[item];
if (entry->allocated)
return 0;
size = roundup(size, 8);
if (size > header->available)
return EINVAL;
entry->offset = header->free_offset;
entry->size = size;
membar_producer();
entry->allocated = 1;
header->free_offset += size;
header->available -= size;
return 0;
}
int
qcsmem_alloc(int host, int item, int size)
{
struct qcsmem_softc *sc = qcsmem_sc;
struct qcsmem_partition *part;
int ret;
if (sc == NULL)
return ENXIO;
if (item < QCSMEM_ITEM_FIXED)
return EPERM;
if (item >= sc->sc_item_count)
return ENXIO;
ret = hwlock_lock_idx_timeout(sc->sc_node, 0, 1000);
if (ret)
return ret;
if (host < QCSMEM_HOST_COUNT &&
sc->sc_partitions[host].phdr != NULL) {
part = &sc->sc_partitions[host];
ret = qcsmem_alloc_private(sc, part, item, size);
} else if (sc->sc_global_partition.phdr != NULL) {
part = &sc->sc_global_partition;
ret = qcsmem_alloc_private(sc, part, item, size);
} else {
ret = qcsmem_alloc_global(sc, item, size);
}
hwlock_unlock_idx(sc->sc_node, 0);
return ret;
}
void *
qcsmem_get_private(struct qcsmem_softc *sc, struct qcsmem_partition *part,
int item, int *size)
{
struct qcsmem_private_entry *entry, *last;
struct qcsmem_partition_header *phdr = part->phdr;
entry = (void *)&phdr[1];
last = (void *)phdr + phdr->offset_free_uncached;
while (entry < last) {
if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
printf("%s: invalid canary\n", sc->sc_dev.dv_xname);
return NULL;
}
if (entry->item == item) {
if (size != NULL) {
if (entry->size > part->size ||
entry->padding_data > entry->size)
return NULL;
*size = entry->size - entry->padding_data;
}
return (void *)&entry[1] + entry->padding_hdr;
}
entry = (void *)&entry[1] + entry->padding_hdr +
entry->size;
}
if ((void *)entry > (void *)phdr + part->size)
return NULL;
entry = (void *)phdr + phdr->size -
roundup(sizeof(*entry), part->cacheline);
last = (void *)phdr + phdr->offset_free_cached;
if ((void *)entry < (void *)phdr ||
(void *)last > (void *)phdr + part->size)
return NULL;
while (entry > last) {
if (entry->canary != QCSMEM_PRIV_ENTRY_CANARY) {
printf("%s: invalid canary\n", sc->sc_dev.dv_xname);
return NULL;
}
if (entry->item == item) {
if (size != NULL) {
if (entry->size > part->size ||
entry->padding_data > entry->size)
return NULL;
*size = entry->size - entry->padding_data;
}
return (void *)entry - entry->size;
}
entry = (void *)entry - entry->size -
roundup(sizeof(*entry), part->cacheline);
}
if ((void *)entry < (void *)phdr)
return NULL;
return NULL;
}
void *
qcsmem_get_global(struct qcsmem_softc *sc, int item, int *size)
{
struct qcsmem_header *header;
struct qcsmem_global_entry *entry;
uint32_t aux_base;
header = bus_space_vaddr(sc->sc_iot, sc->sc_ioh);
entry = &header->toc[item];
if (!entry->allocated)
return NULL;
aux_base = entry->aux_base & QCSMEM_GLOBAL_ENTRY_AUX_BASE_MASK;
if (aux_base != 0 && aux_base != sc->sc_aux_base)
return NULL;
if (entry->size + entry->offset > sc->sc_aux_size)
return NULL;
if (size != NULL)
*size = entry->size;
return bus_space_vaddr(sc->sc_iot, sc->sc_ioh) + entry->offset;
}
void *
qcsmem_get(int host, int item, int *size)
{
struct qcsmem_softc *sc = qcsmem_sc;
struct qcsmem_partition *part;
void *p = NULL;
int ret;
if (sc == NULL)
return NULL;
if (item >= sc->sc_item_count)
return NULL;
ret = hwlock_lock_idx_timeout(sc->sc_node, 0, 1000);
if (ret)
return NULL;
if (host < QCSMEM_HOST_COUNT &&
sc->sc_partitions[host].phdr != NULL) {
part = &sc->sc_partitions[host];
p = qcsmem_get_private(sc, part, item, size);
} else if (sc->sc_global_partition.phdr != NULL) {
part = &sc->sc_global_partition;
p = qcsmem_get_private(sc, part, item, size);
} else {
p = qcsmem_get_global(sc, item, size);
}
hwlock_unlock_idx(sc->sc_node, 0);
return p;
}