501 lines
14 KiB
C
501 lines
14 KiB
C
/* $OpenBSD: simplebus.c,v 1.18 2023/09/22 01:10:43 jsg Exp $ */
|
|
/*
|
|
* Copyright (c) 2016 Patrick Wildt <patrick@blueri.se>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/device.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <machine/fdt.h>
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <dev/ofw/fdt.h>
|
|
#include <dev/ofw/ofw_misc.h>
|
|
|
|
#include <machine/fdt.h>
|
|
#include <machine/simplebusvar.h>
|
|
|
|
int simplebus_match(struct device *, void *, void *);
|
|
void simplebus_attach(struct device *, struct device *, void *);
|
|
|
|
void simplebus_attach_node(struct device *, int);
|
|
int simplebus_bs_map(bus_space_tag_t, bus_addr_t, bus_size_t, int,
|
|
bus_space_handle_t *);
|
|
paddr_t simplebus_bs_mmap(bus_space_tag_t, bus_addr_t, off_t, int, int);
|
|
int simplebus_dmamap_load_buffer(bus_dma_tag_t, bus_dmamap_t, void *,
|
|
bus_size_t, struct proc *, int, paddr_t *, int *, int);
|
|
int simplebus_dmamap_load_raw(bus_dma_tag_t, bus_dmamap_t,
|
|
bus_dma_segment_t *, int, bus_size_t, int);
|
|
|
|
const struct cfattach simplebus_ca = {
|
|
sizeof(struct simplebus_softc), simplebus_match, simplebus_attach
|
|
};
|
|
|
|
struct cfdriver simplebus_cd = {
|
|
NULL, "simplebus", DV_DULL
|
|
};
|
|
|
|
/*
|
|
* Simplebus is a generic bus with no special casings.
|
|
*/
|
|
int
|
|
simplebus_match(struct device *parent, void *cfdata, void *aux)
|
|
{
|
|
struct fdt_attach_args *fa = (struct fdt_attach_args *)aux;
|
|
|
|
if (fa->fa_node == 0)
|
|
return (0);
|
|
|
|
/* Qualcomm GENI can mostly be treated as simple-bus. */
|
|
if (OF_is_compatible(fa->fa_node, "qcom,geni-se-qup"))
|
|
return (1);
|
|
|
|
if (!OF_is_compatible(fa->fa_node, "simple-bus"))
|
|
return (0);
|
|
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
simplebus_attach(struct device *parent, struct device *self, void *aux)
|
|
{
|
|
struct simplebus_softc *sc = (struct simplebus_softc *)self;
|
|
struct fdt_attach_args *fa = (struct fdt_attach_args *)aux;
|
|
char name[32];
|
|
int node;
|
|
|
|
sc->sc_node = fa->fa_node;
|
|
sc->sc_iot = fa->fa_iot;
|
|
sc->sc_dmat = fa->fa_dmat;
|
|
sc->sc_acells = OF_getpropint(sc->sc_node, "#address-cells",
|
|
fa->fa_acells);
|
|
sc->sc_scells = OF_getpropint(sc->sc_node, "#size-cells",
|
|
fa->fa_scells);
|
|
sc->sc_pacells = fa->fa_acells;
|
|
sc->sc_pscells = fa->fa_scells;
|
|
|
|
if (OF_getprop(sc->sc_node, "name", name, sizeof(name)) > 0) {
|
|
name[sizeof(name) - 1] = 0;
|
|
printf(": \"%s\"", name);
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
memcpy(&sc->sc_bus, sc->sc_iot, sizeof(sc->sc_bus));
|
|
sc->sc_bus.bus_private = sc;
|
|
sc->sc_bus._space_map = simplebus_bs_map;
|
|
sc->sc_bus._space_mmap = simplebus_bs_mmap;
|
|
|
|
sc->sc_rangeslen = OF_getproplen(sc->sc_node, "ranges");
|
|
if (sc->sc_rangeslen > 0 &&
|
|
(sc->sc_rangeslen % sizeof(uint32_t)) == 0) {
|
|
sc->sc_ranges = malloc(sc->sc_rangeslen, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "ranges", sc->sc_ranges,
|
|
sc->sc_rangeslen);
|
|
}
|
|
|
|
memcpy(&sc->sc_dma, sc->sc_dmat, sizeof(sc->sc_dma));
|
|
sc->sc_dma._dmamap_load_buffer = simplebus_dmamap_load_buffer;
|
|
sc->sc_dma._dmamap_load_raw = simplebus_dmamap_load_raw;
|
|
sc->sc_dma._cookie = sc;
|
|
|
|
sc->sc_dmarangeslen = OF_getproplen(sc->sc_node, "dma-ranges");
|
|
if (sc->sc_dmarangeslen > 0 &&
|
|
(sc->sc_dmarangeslen % sizeof(uint32_t)) == 0) {
|
|
sc->sc_dmaranges = malloc(sc->sc_dmarangeslen,
|
|
M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(sc->sc_node, "dma-ranges",
|
|
sc->sc_dmaranges, sc->sc_dmarangeslen);
|
|
}
|
|
|
|
/*
|
|
* The device tree provided by the Raspberry Pi firmware lacks
|
|
* a "dma-ranges" option. So provide the information until
|
|
* that gets fixed.
|
|
*/
|
|
if (sc->sc_dmaranges == NULL) {
|
|
node = OF_parent(sc->sc_node);
|
|
if (OF_is_compatible(node, "brcm,bcm2709")) {
|
|
sc->sc_dmarangeslen = 3 * sizeof(uint32_t);
|
|
sc->sc_dmaranges = malloc(sc->sc_dmarangeslen,
|
|
M_TEMP, M_WAITOK);
|
|
sc->sc_dmaranges[0] = 0xc0000000;
|
|
sc->sc_dmaranges[1] = 0x00000000;
|
|
sc->sc_dmaranges[2] = 0x3f000000;
|
|
}
|
|
}
|
|
|
|
/* Scan the whole tree. */
|
|
for (sc->sc_early = 2; sc->sc_early >= 0; sc->sc_early--) {
|
|
for (node = OF_child(sc->sc_node); node; node = OF_peer(node))
|
|
simplebus_attach_node(self, node);
|
|
}
|
|
}
|
|
|
|
int
|
|
simplebus_submatch(struct device *self, void *match, void *aux)
|
|
{
|
|
struct simplebus_softc *sc = (struct simplebus_softc *)self;
|
|
struct cfdata *cf = match;
|
|
|
|
if (cf->cf_loc[0] == sc->sc_early)
|
|
return (*cf->cf_attach->ca_match)(self, match, aux);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
simplebus_print(void *aux, const char *pnp)
|
|
{
|
|
struct fdt_attach_args *fa = aux;
|
|
char name[32];
|
|
|
|
if (!pnp)
|
|
return (QUIET);
|
|
|
|
if (OF_getprop(fa->fa_node, "name", name, sizeof(name)) > 0) {
|
|
name[sizeof(name) - 1] = 0;
|
|
printf("\"%s\"", name);
|
|
} else
|
|
printf("node %u", fa->fa_node);
|
|
|
|
printf(" at %s", pnp);
|
|
|
|
return (UNCONF);
|
|
}
|
|
|
|
/*
|
|
* Look for a driver that wants to be attached to this node.
|
|
*/
|
|
void
|
|
simplebus_attach_node(struct device *self, int node)
|
|
{
|
|
struct simplebus_softc *sc = (struct simplebus_softc *)self;
|
|
struct fdt_attach_args fa;
|
|
char buf[32];
|
|
int i, len, line;
|
|
uint32_t *cell, *reg;
|
|
struct device *child;
|
|
|
|
if (OF_getproplen(node, "compatible") <= 0)
|
|
return;
|
|
|
|
if (OF_getprop(node, "status", buf, sizeof(buf)) > 0 &&
|
|
strcmp(buf, "disabled") == 0)
|
|
return;
|
|
|
|
/* Skip if already attached early. */
|
|
for (i = 0; i < nitems(sc->sc_early_nodes); i++) {
|
|
if (sc->sc_early_nodes[i] == node)
|
|
return;
|
|
if (sc->sc_early_nodes[i] == 0)
|
|
break;
|
|
}
|
|
|
|
memset(&fa, 0, sizeof(fa));
|
|
fa.fa_name = "";
|
|
fa.fa_node = node;
|
|
fa.fa_iot = &sc->sc_bus;
|
|
fa.fa_dmat = &sc->sc_dma;
|
|
fa.fa_acells = sc->sc_acells;
|
|
fa.fa_scells = sc->sc_scells;
|
|
|
|
len = OF_getproplen(node, "reg");
|
|
line = (sc->sc_acells + sc->sc_scells) * sizeof(uint32_t);
|
|
if (len > 0 && line > 0 && (len % line) == 0) {
|
|
reg = malloc(len, M_TEMP, M_WAITOK);
|
|
OF_getpropintarray(node, "reg", reg, len);
|
|
|
|
fa.fa_reg = malloc((len / line) * sizeof(struct fdt_reg),
|
|
M_DEVBUF, M_WAITOK | M_ZERO);
|
|
fa.fa_nreg = (len / line);
|
|
|
|
for (i = 0, cell = reg; i < len / line; i++) {
|
|
if (sc->sc_acells >= 1)
|
|
fa.fa_reg[i].addr = cell[0];
|
|
if (sc->sc_acells == 2) {
|
|
fa.fa_reg[i].addr <<= 32;
|
|
fa.fa_reg[i].addr |= cell[1];
|
|
}
|
|
cell += sc->sc_acells;
|
|
if (sc->sc_scells >= 1)
|
|
fa.fa_reg[i].size = cell[0];
|
|
if (sc->sc_scells == 2) {
|
|
fa.fa_reg[i].size <<= 32;
|
|
fa.fa_reg[i].size |= cell[1];
|
|
}
|
|
cell += sc->sc_scells;
|
|
}
|
|
|
|
free(reg, M_TEMP, len);
|
|
}
|
|
|
|
len = OF_getproplen(node, "interrupts");
|
|
if (len > 0 && (len % sizeof(uint32_t)) == 0) {
|
|
fa.fa_intr = malloc(len, M_DEVBUF, M_WAITOK);
|
|
fa.fa_nintr = len / sizeof(uint32_t);
|
|
|
|
OF_getpropintarray(node, "interrupts", fa.fa_intr, len);
|
|
}
|
|
|
|
if (OF_getproplen(node, "dma-coherent") >= 0) {
|
|
fa.fa_dmat = malloc(sizeof(sc->sc_dma),
|
|
M_DEVBUF, M_WAITOK | M_ZERO);
|
|
memcpy(fa.fa_dmat, &sc->sc_dma, sizeof(sc->sc_dma));
|
|
fa.fa_dmat->_flags |= BUS_DMA_COHERENT;
|
|
}
|
|
|
|
fa.fa_dmat = iommu_device_map(fa.fa_node, fa.fa_dmat);
|
|
|
|
child = config_found_sm(self, &fa, sc->sc_early ? NULL :
|
|
simplebus_print, simplebus_submatch);
|
|
|
|
/* Record nodes that we attach early. */
|
|
if (child && sc->sc_early) {
|
|
for (i = 0; i < nitems(sc->sc_early_nodes); i++) {
|
|
if (sc->sc_early_nodes[i] != 0)
|
|
continue;
|
|
sc->sc_early_nodes[i] = node;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(fa.fa_reg, M_DEVBUF, fa.fa_nreg * sizeof(struct fdt_reg));
|
|
free(fa.fa_intr, M_DEVBUF, fa.fa_nintr * sizeof(uint32_t));
|
|
}
|
|
|
|
/*
|
|
* Translate memory address if needed.
|
|
*/
|
|
int
|
|
simplebus_bs_map(bus_space_tag_t t, bus_addr_t bpa, bus_size_t size,
|
|
int flag, bus_space_handle_t *bshp)
|
|
{
|
|
struct simplebus_softc *sc = t->bus_private;
|
|
uint64_t addr, rfrom, rto, rsize;
|
|
uint32_t *range;
|
|
int parent, rlen, rone;
|
|
|
|
addr = bpa;
|
|
parent = OF_parent(sc->sc_node);
|
|
if (parent == 0)
|
|
return bus_space_map(sc->sc_iot, addr, size, flag, bshp);
|
|
|
|
if (sc->sc_rangeslen < 0)
|
|
return EINVAL;
|
|
if (sc->sc_rangeslen == 0)
|
|
return bus_space_map(sc->sc_iot, addr, size, flag, bshp);
|
|
|
|
rlen = sc->sc_rangeslen / sizeof(uint32_t);
|
|
rone = sc->sc_pacells + sc->sc_acells + sc->sc_scells;
|
|
|
|
/* For each range. */
|
|
for (range = sc->sc_ranges; rlen >= rone; rlen -= rone, range += rone) {
|
|
/* Extract from and size, so we can see if we fit. */
|
|
rfrom = range[0];
|
|
if (sc->sc_acells == 2)
|
|
rfrom = (rfrom << 32) + range[1];
|
|
rsize = range[sc->sc_acells + sc->sc_pacells];
|
|
if (sc->sc_scells == 2)
|
|
rsize = (rsize << 32) +
|
|
range[sc->sc_acells + sc->sc_pacells + 1];
|
|
|
|
/* Try next, if we're not in the range. */
|
|
if (addr < rfrom || (addr + size) > (rfrom + rsize))
|
|
continue;
|
|
|
|
/* All good, extract to address and translate. */
|
|
rto = range[sc->sc_acells];
|
|
if (sc->sc_pacells == 2)
|
|
rto = (rto << 32) + range[sc->sc_acells + 1];
|
|
|
|
addr -= rfrom;
|
|
addr += rto;
|
|
|
|
return bus_space_map(sc->sc_iot, addr, size, flag, bshp);
|
|
}
|
|
|
|
return ESRCH;
|
|
}
|
|
|
|
paddr_t
|
|
simplebus_bs_mmap(bus_space_tag_t t, bus_addr_t bpa, off_t off,
|
|
int prot, int flags)
|
|
{
|
|
struct simplebus_softc *sc = t->bus_private;
|
|
uint64_t addr, rfrom, rto, rsize;
|
|
uint32_t *range;
|
|
int parent, rlen, rone;
|
|
|
|
addr = bpa;
|
|
parent = OF_parent(sc->sc_node);
|
|
if (parent == 0)
|
|
return bus_space_mmap(sc->sc_iot, addr, off, prot, flags);
|
|
|
|
if (sc->sc_rangeslen < 0)
|
|
return EINVAL;
|
|
if (sc->sc_rangeslen == 0)
|
|
return bus_space_mmap(sc->sc_iot, addr, off, prot, flags);
|
|
|
|
rlen = sc->sc_rangeslen / sizeof(uint32_t);
|
|
rone = sc->sc_pacells + sc->sc_acells + sc->sc_scells;
|
|
|
|
/* For each range. */
|
|
for (range = sc->sc_ranges; rlen >= rone; rlen -= rone, range += rone) {
|
|
/* Extract from and size, so we can see if we fit. */
|
|
rfrom = range[0];
|
|
if (sc->sc_acells == 2)
|
|
rfrom = (rfrom << 32) + range[1];
|
|
rsize = range[sc->sc_acells + sc->sc_pacells];
|
|
if (sc->sc_scells == 2)
|
|
rsize = (rsize << 32) +
|
|
range[sc->sc_acells + sc->sc_pacells + 1];
|
|
|
|
/* Try next, if we're not in the range. */
|
|
if (addr < rfrom || addr >= (rfrom + rsize))
|
|
continue;
|
|
|
|
/* All good, extract to address and translate. */
|
|
rto = range[sc->sc_acells];
|
|
if (sc->sc_pacells == 2)
|
|
rto = (rto << 32) + range[sc->sc_acells + 1];
|
|
|
|
addr -= rfrom;
|
|
addr += rto;
|
|
|
|
return bus_space_mmap(sc->sc_iot, addr, off, prot, flags);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
simplebus_dmamap_load_buffer(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
|
|
bus_size_t buflen, struct proc *p, int flags, paddr_t *lastaddrp,
|
|
int *segp, int first)
|
|
{
|
|
struct simplebus_softc *sc = t->_cookie;
|
|
int rlen, rone, seg;
|
|
int firstseg = *segp;
|
|
int error;
|
|
|
|
error = sc->sc_dmat->_dmamap_load_buffer(sc->sc_dmat, map, buf, buflen,
|
|
p, flags, lastaddrp, segp, first);
|
|
if (error)
|
|
return error;
|
|
|
|
if (sc->sc_dmaranges == NULL)
|
|
return 0;
|
|
|
|
rlen = sc->sc_dmarangeslen / sizeof(uint32_t);
|
|
rone = sc->sc_pacells + sc->sc_acells + sc->sc_scells;
|
|
|
|
/* For each segment. */
|
|
for (seg = firstseg; seg <= *segp; seg++) {
|
|
uint64_t addr, size, rfrom, rto, rsize;
|
|
uint32_t *range;
|
|
|
|
addr = map->dm_segs[seg].ds_addr;
|
|
size = map->dm_segs[seg].ds_len;
|
|
|
|
/* For each range. */
|
|
for (range = sc->sc_dmaranges; rlen >= rone;
|
|
rlen -= rone, range += rone) {
|
|
/* Extract from and size, so we can see if we fit. */
|
|
rfrom = range[sc->sc_acells];
|
|
if (sc->sc_pacells == 2)
|
|
rfrom = (rfrom << 32) + range[sc->sc_acells + 1];
|
|
|
|
rsize = range[sc->sc_acells + sc->sc_pacells];
|
|
if (sc->sc_scells == 2)
|
|
rsize = (rsize << 32) +
|
|
range[sc->sc_acells + sc->sc_pacells + 1];
|
|
|
|
/* Try next, if we're not in the range. */
|
|
if (addr < rfrom || (addr + size) > (rfrom + rsize))
|
|
continue;
|
|
|
|
/* All good, extract to address and translate. */
|
|
rto = range[0];
|
|
if (sc->sc_acells == 2)
|
|
rto = (rto << 32) + range[1];
|
|
|
|
map->dm_segs[seg].ds_addr -= rfrom;
|
|
map->dm_segs[seg].ds_addr += rto;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
simplebus_dmamap_load_raw(bus_dma_tag_t t, bus_dmamap_t map,
|
|
bus_dma_segment_t *segs, int nsegs, bus_size_t size, int flags)
|
|
{
|
|
struct simplebus_softc *sc = t->_cookie;
|
|
int rlen, rone, seg;
|
|
int error;
|
|
|
|
error = sc->sc_dmat->_dmamap_load_raw(sc->sc_dmat, map,
|
|
segs, nsegs, size, flags);
|
|
if (error)
|
|
return error;
|
|
|
|
if (sc->sc_dmaranges == NULL)
|
|
return 0;
|
|
|
|
rlen = sc->sc_dmarangeslen / sizeof(uint32_t);
|
|
rone = sc->sc_pacells + sc->sc_acells + sc->sc_scells;
|
|
|
|
/* For each segment. */
|
|
for (seg = 0; seg < map->dm_nsegs; seg++) {
|
|
uint64_t addr, size, rfrom, rto, rsize;
|
|
uint32_t *range;
|
|
|
|
addr = map->dm_segs[seg].ds_addr;
|
|
size = map->dm_segs[seg].ds_len;
|
|
|
|
/* For each range. */
|
|
for (range = sc->sc_dmaranges; rlen >= rone;
|
|
rlen -= rone, range += rone) {
|
|
/* Extract from and size, so we can see if we fit. */
|
|
rfrom = range[sc->sc_acells];
|
|
if (sc->sc_pacells == 2)
|
|
rfrom = (rfrom << 32) + range[sc->sc_acells + 1];
|
|
|
|
rsize = range[sc->sc_acells + sc->sc_pacells];
|
|
if (sc->sc_scells == 2)
|
|
rsize = (rsize << 32) +
|
|
range[sc->sc_acells + sc->sc_pacells + 1];
|
|
|
|
/* Try next, if we're not in the range. */
|
|
if (addr < rfrom || (addr + size) > (rfrom + rsize))
|
|
continue;
|
|
|
|
/* All good, extract to address and translate. */
|
|
rto = range[0];
|
|
if (sc->sc_acells == 2)
|
|
rto = (rto << 32) + range[1];
|
|
|
|
map->dm_segs[seg].ds_addr -= rfrom;
|
|
map->dm_segs[seg].ds_addr += rto;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|