#include "opt_m68k_arch.h"
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: bus.c,v 1.71 2025/11/29 19:34:20 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/vmem_impl.h>
#include <uvm/uvm.h>
#include <machine/cpu.h>
#include <m68k/cacheops.h>
#define _ATARI_BUS_DMA_PRIVATE
#include <sys/bus.h>
#define IOMEM_BTAG_COUNT VMEM_EST_BTCOUNT(1, 16)
static struct vmem iomem_arena_store;
static struct vmem_btag iomem_btag_store[IOMEM_BTAG_COUNT];
static vmem_t *iomem_arena;
static int _bus_dmamap_load_buffer(bus_dma_tag_t tag, bus_dmamap_t,
void *, bus_size_t, struct vmspace *, int, paddr_t *,
int *, int);
static int bus_mem_add_mapping(bus_space_tag_t t, bus_addr_t bpa,
bus_size_t size, int flags, bus_space_handle_t *bsph);
extern paddr_t avail_end;
static pt_entry_t *bootm_ptep;
static vaddr_t bootm_start;
static vaddr_t bootm_end;
#define BOOTM_BTAG_COUNT VMEM_EST_BTCOUNT(1, 32)
static struct vmem bootm_arena_store;
static struct vmem_btag bootm_btag_store[BOOTM_BTAG_COUNT];
static vmem_t * bootm_arena;
static vaddr_t bootm_alloc(paddr_t pa, u_long size, int flags);
static int bootm_free(vaddr_t va, u_long size);
void
bootm_init(vaddr_t va, void *ptep, vsize_t size)
{
bootm_start = va;
bootm_end = va + size - 1;
bootm_ptep = (pt_entry_t *)ptep;
bootm_arena = vmem_init(&bootm_arena_store,
"bootmem",
0,
0,
PAGE_SIZE,
NULL,
NULL,
NULL,
0,
VM_NOSLEEP | VM_PRIVTAGS,
IPL_NONE);
vmem_add_bts(bootm_arena, bootm_btag_store, BOOTM_BTAG_COUNT);
vmem_add(bootm_arena, va, size, VM_NOSLEEP);
}
vaddr_t
bootm_alloc(paddr_t pa, u_long size, int flags)
{
pt_entry_t *pg, *epg;
pt_entry_t pg_proto;
vmem_addr_t rva;
vaddr_t va;
if (vmem_alloc(bootm_arena, size, VM_BESTFIT | VM_NOSLEEP, &rva) != 0) {
printf("bootm_alloc fails! Not enough fixed boundary tags?\n");
printf("Requested extent: pa=%lx, size=%lx\n",
(u_long)pa, size);
return 0;
}
pg = &bootm_ptep[btoc(rva - bootm_start)];
epg = &pg[btoc(size)];
va = rva;
pg_proto = pa | PG_RW | PG_V;
if ((flags & BUS_SPACE_MAP_CACHEABLE) == 0)
pg_proto |= PG_CI;
while (pg < epg) {
*pg++ = pg_proto;
pg_proto += PAGE_SIZE;
#if defined(M68040) || defined(M68060)
if (mmutype == MMU_68040) {
DCFP(pa);
pa += PAGE_SIZE;
}
#endif
TBIS(va);
va += PAGE_SIZE;
}
return rva;
}
int
bootm_free(vaddr_t va, u_long size)
{
if ((va < bootm_start) || ((va + size - 1) > bootm_end))
return 0;
vmem_free(bootm_arena, va, size);
return 1;
}
void
atari_bus_space_arena_init(paddr_t startpa, paddr_t endpa)
{
vmem_size_t size;
iomem_arena = vmem_init(&iomem_arena_store,
"iomem",
0,
0,
1,
NULL,
NULL,
NULL,
0,
VM_NOSLEEP | VM_PRIVTAGS,
IPL_NONE);
vmem_add_bts(iomem_arena, iomem_btag_store, IOMEM_BTAG_COUNT);
size = (vmem_size_t)(endpa - startpa) + 1;
if (size == 0) {
size--;
}
vmem_add(iomem_arena, startpa, size, VM_NOSLEEP);
}
int
atari_bus_space_alloc_physmem(paddr_t startpa, paddr_t endpa)
{
return vmem_xalloc_addr(iomem_arena, startpa, endpa - startpa,
VM_NOSLEEP);
}
int
bus_space_map(bus_space_tag_t t, bus_addr_t bpa, bus_size_t size, int flags,
bus_space_handle_t *mhp)
{
int error;
error = vmem_xalloc_addr(iomem_arena, bpa + t->base, size,
VM_NOSLEEP);
if (error != 0)
return error;
error = bus_mem_add_mapping(t, bpa, size, flags, mhp);
if (error != 0) {
vmem_xfree(iomem_arena, bpa + t->base, size);
}
return error;
}
int
bus_space_alloc(bus_space_tag_t t, bus_addr_t rstart, bus_addr_t rend,
bus_size_t size, bus_size_t alignment, bus_size_t boundary, int flags,
bus_addr_t *bpap, bus_space_handle_t *bshp)
{
vmem_addr_t bpa;
int error;
error = vmem_xalloc(iomem_arena, size,
alignment,
0,
boundary,
rstart + t->base,
rend + t->base,
VM_BESTFIT | VM_NOSLEEP,
&bpa);
if (error != 0)
return error;
error = bus_mem_add_mapping(t, bpa, size, flags, bshp);
if (error != 0) {
vmem_xfree(iomem_arena, bpa, size);
}
*bpap = bpa;
return error;
}
static int
bus_mem_add_mapping(bus_space_tag_t t, bus_addr_t bpa, bus_size_t size,
int flags, bus_space_handle_t *bshp)
{
vaddr_t va;
paddr_t pa, endpa;
pa = m68k_trunc_page(bpa + t->base);
endpa = m68k_round_page((bpa + t->base + size) - 1);
#ifdef DIAGNOSTIC
if (endpa <= pa)
panic("%s: overflow", __func__);
#endif
if (kernel_map == NULL) {
va = bootm_alloc(pa, endpa - pa, flags);
if (va == 0)
return ENOMEM;
*bshp = va + (bpa & PGOFSET);
return 0;
}
va = uvm_km_alloc(kernel_map, endpa - pa, 0,
UVM_KMF_VAONLY | UVM_KMF_NOWAIT);
if (va == 0)
return ENOMEM;
*bshp = va + (bpa & PGOFSET);
for (; pa < endpa; pa += PAGE_SIZE, va += PAGE_SIZE) {
pt_entry_t *ptep, npte;
pmap_enter(pmap_kernel(), (vaddr_t)va, pa,
VM_PROT_READ|VM_PROT_WRITE, VM_PROT_READ|VM_PROT_WRITE);
ptep = kvtopte(va);
npte = *ptep & ~PG_CMASK;
if ((flags & BUS_SPACE_MAP_CACHEABLE) == 0)
npte |= PG_CI;
else if (mmutype == MMU_68040)
npte |= PG_CCB;
*ptep = npte;
}
pmap_update(pmap_kernel());
TBIAS();
return 0;
}
void
bus_space_unmap(bus_space_tag_t t, bus_space_handle_t bsh, bus_size_t size)
{
vaddr_t va, endva;
paddr_t bpa;
va = m68k_trunc_page(bsh);
endva = m68k_round_page(((char *)bsh + size) - 1);
#ifdef DIAGNOSTIC
if (endva < va)
panic("%s: overflow", __func__);
#endif
(void)pmap_extract(pmap_kernel(), va, &bpa);
bpa += ((paddr_t)bsh & PGOFSET);
if (!bootm_free(va, endva - va)) {
pmap_remove(pmap_kernel(), va, endva);
pmap_update(pmap_kernel());
uvm_km_free(kernel_map, va, endva - va, UVM_KMF_VAONLY);
}
vmem_xfree(iomem_arena, bpa, size);
}
int
bus_space_subregion(bus_space_tag_t t, bus_space_handle_t memh,
bus_size_t off, bus_size_t sz, bus_space_handle_t *mhp)
{
*mhp = memh + off;
return 0;
}
paddr_t
bus_space_mmap(bus_space_tag_t t, bus_addr_t addr, off_t off, int prot,
int flags)
{
return m68k_btop(addr + off);
}
static size_t
_bus_dmamap_mapsize(int const nsegments)
{
KASSERT(nsegments > 0);
return sizeof(struct atari_bus_dmamap) +
(sizeof(bus_dma_segment_t) * (nsegments - 1));
}
int
_bus_dmamap_create(bus_dma_tag_t t, bus_size_t size, int nsegments,
bus_size_t maxsegsz, bus_size_t boundary, int flags, bus_dmamap_t *dmamp)
{
struct atari_bus_dmamap *map;
void *mapstore;
if ((mapstore = kmem_zalloc(_bus_dmamap_mapsize(nsegments),
(flags & BUS_DMA_NOWAIT) != 0 ? KM_NOSLEEP : KM_SLEEP)) == NULL)
return ENOMEM;
map = (struct atari_bus_dmamap *)mapstore;
map->_dm_size = size;
map->_dm_segcnt = nsegments;
map->_dm_maxmaxsegsz = maxsegsz;
map->_dm_boundary = boundary;
map->_dm_flags = flags & ~(BUS_DMA_WAITOK|BUS_DMA_NOWAIT);
map->dm_maxsegsz = maxsegsz;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
*dmamp = map;
return 0;
}
void
_bus_dmamap_destroy(bus_dma_tag_t t, bus_dmamap_t map)
{
kmem_free(map, _bus_dmamap_mapsize(map->_dm_segcnt));
}
int
_bus_dmamap_load(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
bus_size_t buflen, struct proc *p, int flags)
{
paddr_t lastaddr;
int seg, error;
struct vmspace *vm;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
KASSERT(map->dm_maxsegsz <= map->_dm_maxmaxsegsz);
if (buflen > map->_dm_size)
return EINVAL;
if (p != NULL) {
vm = p->p_vmspace;
} else {
vm = vmspace_kernel();
}
seg = 0;
error = _bus_dmamap_load_buffer(t, map, buf, buflen, vm, flags,
&lastaddr, &seg, 1);
if (error == 0) {
map->dm_mapsize = buflen;
map->dm_nsegs = seg + 1;
}
return error;
}
int
_bus_dmamap_load_mbuf(bus_dma_tag_t t, bus_dmamap_t map, struct mbuf *m0,
int flags)
{
paddr_t lastaddr;
int seg, error, first;
struct mbuf *m;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
KASSERT(map->dm_maxsegsz <= map->_dm_maxmaxsegsz);
#ifdef DIAGNOSTIC
if ((m0->m_flags & M_PKTHDR) == 0)
panic("%s: no packet header", __func__);
#endif
if (m0->m_pkthdr.len > map->_dm_size)
return EINVAL;
first = 1;
seg = 0;
error = 0;
for (m = m0; m != NULL && error == 0; m = m->m_next) {
if (m->m_len == 0)
continue;
error = _bus_dmamap_load_buffer(t, map, m->m_data, m->m_len,
vmspace_kernel(), flags, &lastaddr, &seg, first);
first = 0;
}
if (error == 0) {
map->dm_mapsize = m0->m_pkthdr.len;
map->dm_nsegs = seg + 1;
}
return error;
}
int
_bus_dmamap_load_uio(bus_dma_tag_t t, bus_dmamap_t map, struct uio *uio,
int flags)
{
paddr_t lastaddr;
int seg, i, error, first;
bus_size_t minlen, resid;
struct iovec *iov;
void *addr;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
KASSERT(map->dm_maxsegsz <= map->_dm_maxmaxsegsz);
resid = uio->uio_resid;
iov = uio->uio_iov;
first = 1;
seg = 0;
error = 0;
for (i = 0; i < uio->uio_iovcnt && resid != 0 && error == 0; i++) {
minlen = resid < iov[i].iov_len ? resid : iov[i].iov_len;
addr = (void *)iov[i].iov_base;
error = _bus_dmamap_load_buffer(t, map, addr, minlen,
uio->uio_vmspace, flags, &lastaddr, &seg, first);
first = 0;
resid -= minlen;
}
if (error == 0) {
map->dm_mapsize = uio->uio_resid;
map->dm_nsegs = seg + 1;
}
return error;
}
int
_bus_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)
{
panic("%s: not implemented", __func__);
}
void
_bus_dmamap_unload(bus_dma_tag_t t, bus_dmamap_t map)
{
map->dm_maxsegsz = map->_dm_maxmaxsegsz;
map->dm_mapsize = 0;
map->dm_nsegs = 0;
}
void
_bus_dmamap_sync(bus_dma_tag_t t, bus_dmamap_t map, bus_addr_t offset,
bus_size_t len, int ops)
{
#if defined(M68040) || defined(M68060)
bus_addr_t p, e, ps, pe;
bus_size_t seglen;
bus_dma_segment_t *seg;
int i;
#endif
#if defined(M68020) || defined(M68030)
#if defined(M68040) || defined(M68060)
if (cputype == CPU_68020 || cputype == CPU_68030)
#endif
return;
#endif
#if defined(M68040) || defined(M68060)
if ((map->_dm_flags & BUS_DMA_COHERENT) != 0)
return;
if ((ops & (BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE)) == 0)
return;
for (i = 0; i < map->dm_nsegs && len != 0; i++) {
seg = &map->dm_segs[i];
if (seg->ds_len <= offset) {
offset -= seg->ds_len;
continue;
}
seglen = seg->ds_len - offset;
if (seglen > len)
seglen = len;
ps = seg->ds_addr + offset;
pe = ps + seglen;
if ((ops & BUS_DMASYNC_PREWRITE) != 0) {
p = ps & ~CACHELINE_MASK;
e = (pe + CACHELINE_MASK) & ~CACHELINE_MASK;
while ((p < e) && (p & (CACHELINE_SIZE * 8 - 1)) != 0) {
DCFL(p);
p += CACHELINE_SIZE;
}
while ((p < e) && (p & PAGE_MASK) != 0) {
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
}
while (p + PAGE_SIZE <= e) {
DCFP(p);
p += PAGE_SIZE;
}
while (p + CACHELINE_SIZE * 8 <= e) {
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
DCFL(p);
p += CACHELINE_SIZE;
}
while (p < e) {
DCFL(p);
p += CACHELINE_SIZE;
}
}
else if ((ops & BUS_DMASYNC_PREREAD) != 0) {
if ((ps & CACHELINE_MASK) != 0) {
DCFL(ps & ~CACHELINE_MASK);
}
p = (ps + CACHELINE_MASK) & ~CACHELINE_MASK;
e = pe & ~CACHELINE_MASK;
while ((p < e) && (p & (CACHELINE_SIZE * 8 - 1)) != 0) {
DCPL(p);
p += CACHELINE_SIZE;
}
while ((p < e) && (p & PAGE_MASK) != 0) {
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
}
while (p + PAGE_SIZE <= e) {
DCPP(p);
p += PAGE_SIZE;
}
while (p + CACHELINE_SIZE * 8 <= e) {
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
DCPL(p);
p += CACHELINE_SIZE;
}
while (p < e) {
DCPL(p);
p += CACHELINE_SIZE;
}
if (p < pe) {
DCFL(p);
}
}
offset = 0;
len -= seglen;
}
#endif
}
int
bus_dmamem_alloc(bus_dma_tag_t t, bus_size_t size, bus_size_t alignment,
bus_size_t boundary, bus_dma_segment_t *segs, int nsegs, int *rsegs,
int flags)
{
return bus_dmamem_alloc_range(t, size, alignment, boundary,
segs, nsegs, rsegs, flags, 0, trunc_page(avail_end));
}
void
bus_dmamem_free(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs)
{
struct vm_page *m;
bus_addr_t addr, offset;
struct pglist mlist;
int curseg;
offset = t->_displacement;
TAILQ_INIT(&mlist);
for (curseg = 0; curseg < nsegs; curseg++) {
for (addr = segs[curseg].ds_addr;
addr < (segs[curseg].ds_addr + segs[curseg].ds_len);
addr += PAGE_SIZE) {
m = PHYS_TO_VM_PAGE(addr - offset);
TAILQ_INSERT_TAIL(&mlist, m, pageq.queue);
}
}
uvm_pglistfree(&mlist);
}
int
bus_dmamem_map(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs,
size_t size, void **kvap, int flags)
{
vaddr_t va;
bus_addr_t addr, offset;
int curseg;
const uvm_flag_t kmflags =
(flags & BUS_DMA_NOWAIT) != 0 ? UVM_KMF_NOWAIT : 0;
offset = t->_displacement;
size = round_page(size);
va = uvm_km_alloc(kernel_map, size, 0, UVM_KMF_VAONLY | kmflags);
if (va == 0)
return ENOMEM;
*kvap = (void *)va;
for (curseg = 0; curseg < nsegs; curseg++) {
for (addr = segs[curseg].ds_addr;
addr < (segs[curseg].ds_addr + segs[curseg].ds_len);
addr += PAGE_SIZE, va += PAGE_SIZE, size -= PAGE_SIZE) {
if (size == 0)
panic("%s: size botch", __func__);
pmap_enter(pmap_kernel(), va, addr - offset,
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE);
}
}
pmap_update(pmap_kernel());
return 0;
}
void
bus_dmamem_unmap(bus_dma_tag_t t, void *kva, size_t size)
{
#ifdef DIAGNOSTIC
if ((vaddr_t)kva & PGOFSET)
panic("%s", __func__);
#endif
size = round_page(size);
pmap_remove(pmap_kernel(), (vaddr_t)kva, (vaddr_t)kva + size);
pmap_update(pmap_kernel());
uvm_km_free(kernel_map, (vaddr_t)kva, size, UVM_KMF_VAONLY);
}
paddr_t
bus_dmamem_mmap(bus_dma_tag_t t, bus_dma_segment_t *segs, int nsegs, off_t off,
int prot, int flags)
{
int i, offset;
offset = t->_displacement;
for (i = 0; i < nsegs; i++) {
#ifdef DIAGNOSTIC
if ((off & PGOFSET) != 0)
panic("%s: offset unaligned", __func__);
if ((segs[i].ds_addr & PGOFSET) != 0)
panic("%s: segment unaligned", __func__);
if ((segs[i].ds_len & PGOFSET) != 0)
panic("%s: segment size not multiple of page size",
__func__);
#endif
if (off >= segs[i].ds_len) {
off -= segs[i].ds_len;
continue;
}
return m68k_btop((char *)segs[i].ds_addr - offset + off);
}
return -1;
}
static int
_bus_dmamap_load_buffer(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
bus_size_t buflen, struct vmspace *vm, int flags, paddr_t *lastaddrp,
int *segp, int first)
{
bus_size_t sgsize;
bus_addr_t curaddr, lastaddr, offset, baddr, bmask;
vaddr_t vaddr = (vaddr_t)buf;
int seg;
pmap_t pmap;
offset = t->_displacement;
pmap = vm_map_pmap(&vm->vm_map);
lastaddr = *lastaddrp;
bmask = ~(map->_dm_boundary - 1);
for (seg = *segp; buflen > 0 ; ) {
(void)pmap_extract(pmap, vaddr, &curaddr);
sgsize = PAGE_SIZE - ((vaddr_t)vaddr & PGOFSET);
if (buflen < sgsize)
sgsize = buflen;
if (map->_dm_boundary > 0) {
baddr = (curaddr + map->_dm_boundary) & bmask;
if (sgsize > (baddr - curaddr))
sgsize = (baddr - curaddr);
}
if (first) {
map->dm_segs[seg].ds_addr = curaddr + offset;
map->dm_segs[seg].ds_len = sgsize;
first = 0;
} else {
if (curaddr == lastaddr &&
(map->dm_segs[seg].ds_len + sgsize) <=
map->dm_maxsegsz &&
(map->_dm_boundary == 0 ||
(map->dm_segs[seg].ds_addr & bmask) ==
(curaddr & bmask)))
map->dm_segs[seg].ds_len += sgsize;
else {
if (++seg >= map->_dm_segcnt)
break;
map->dm_segs[seg].ds_addr = curaddr + offset;
map->dm_segs[seg].ds_len = sgsize;
}
}
lastaddr = curaddr + sgsize;
vaddr += sgsize;
buflen -= sgsize;
}
*segp = seg;
*lastaddrp = lastaddr;
if (buflen != 0)
return EFBIG;
return 0;
}
int
bus_dmamem_alloc_range(bus_dma_tag_t t, bus_size_t size, bus_size_t alignment,
bus_size_t boundary, bus_dma_segment_t *segs, int nsegs, int *rsegs,
int flags, paddr_t low, paddr_t high)
{
paddr_t curaddr, lastaddr;
bus_addr_t offset;
struct vm_page *m;
struct pglist mlist;
int curseg, error;
offset = t->_displacement;
size = round_page(size);
error = uvm_pglistalloc(size, low, high, alignment, boundary,
&mlist, nsegs, (flags & BUS_DMA_NOWAIT) == 0);
if (error != 0)
return error;
m = TAILQ_FIRST(&mlist);
curseg = 0;
lastaddr = VM_PAGE_TO_PHYS(m);
segs[curseg].ds_addr = lastaddr + offset;
segs[curseg].ds_len = PAGE_SIZE;
m = TAILQ_NEXT(m, pageq.queue);
for (; m != NULL; m = TAILQ_NEXT(m, pageq.queue)) {
curaddr = VM_PAGE_TO_PHYS(m);
#ifdef DIAGNOSTIC
if (curaddr < low || curaddr >= high) {
printf("uvm_pglistalloc returned non-sensical"
" address 0x%lx\n", curaddr);
panic("%s", __func__);
}
#endif
if (curaddr == (lastaddr + PAGE_SIZE))
segs[curseg].ds_len += PAGE_SIZE;
else {
curseg++;
segs[curseg].ds_addr = curaddr + offset;
segs[curseg].ds_len = PAGE_SIZE;
}
lastaddr = curaddr;
}
*rsegs = curseg + 1;
return 0;
}