Home | History | Annotate | Line # | Download | only in sun3
      1 /*	$NetBSD: dvma.c,v 1.43 2023/12/20 05:18:00 thorpej Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1996 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Gordon W. Ross.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 __KERNEL_RCSID(0, "$NetBSD: dvma.c,v 1.43 2023/12/20 05:18:00 thorpej Exp $");
     34 
     35 #include <sys/param.h>
     36 #include <sys/systm.h>
     37 #include <sys/device.h>
     38 #include <sys/proc.h>
     39 #include <sys/vmem.h>
     40 #include <sys/buf.h>
     41 #include <sys/vnode.h>
     42 #include <sys/core.h>
     43 #include <sys/exec.h>
     44 
     45 #include <uvm/uvm.h> /* XXX: not _extern ... need uvm_map_create */
     46 
     47 #define _SUN68K_BUS_DMA_PRIVATE
     48 #include <machine/autoconf.h>
     49 #include <machine/bus.h>
     50 #include <machine/cpu.h>
     51 #include <machine/dvma.h>
     52 #include <machine/pmap.h>
     53 #include <machine/pte.h>
     54 
     55 #include <sun3/sun3/control.h>
     56 #include <sun3/sun3/machdep.h>
     57 
     58 /* DVMA is the last 1MB, but the PROM owns the last page. */
     59 #define DVMA_MAP_END	(DVMA_MAP_BASE + DVMA_MAP_AVAIL)
     60 
     61 /* Extent map used by dvma_mapin/dvma_mapout */
     62 vmem_t *dvma_arena;
     63 
     64 /* XXX: Might need to tune this... */
     65 vsize_t dvma_segmap_size = 6 * NBSG;
     66 
     67 /* Using phys_map to manage DVMA scratch-memory pages. */
     68 /* Note: Could use separate pagemap for obio if needed. */
     69 
     70 void
     71 dvma_init(void)
     72 {
     73 	vaddr_t segmap_addr;
     74 
     75 	/*
     76 	 * Create phys_map covering the entire DVMA space,
     77 	 * then allocate the segment pool from that.  The
     78 	 * remainder will be used as the DVMA page pool.
     79 	 *
     80 	 * Note that no INTRSAFE is needed here because the
     81 	 * dvma_extent manages things handled in interrupt
     82 	 * context.
     83 	 */
     84 	phys_map = kmem_alloc(sizeof(struct vm_map), KM_SLEEP);
     85 	uvm_map_setup(phys_map, DVMA_MAP_BASE, DVMA_MAP_END, 0);
     86 	phys_map->pmap = pmap_kernel();
     87 
     88 	/*
     89 	 * Reserve the DVMA space used for segment remapping.
     90 	 * The remainder of phys_map is used for DVMA scratch
     91 	 * memory pages (i.e. driver control blocks, etc.)
     92 	 */
     93 	segmap_addr = uvm_km_alloc(phys_map, dvma_segmap_size, 0,
     94 	    UVM_KMF_VAONLY | UVM_KMF_WAITVA);
     95 	if (segmap_addr != DVMA_MAP_BASE)
     96 		panic("dvma_init: unable to allocate DVMA segments");
     97 
     98 	/*
     99 	 * Create the vmem arena used for mapping whole segments
    100 	 * into DVMA space for the purpose of data transfer.
    101 	 */
    102 	dvma_arena = vmem_create("dvma", segmap_addr, dvma_segmap_size,
    103 				 PAGE_SIZE,		/* quantum */
    104 				 NULL,			/* importfn */
    105 				 NULL,			/* releasefn */
    106 				 NULL,			/* source */
    107 				 0,			/* qcache_max */
    108 				 VM_SLEEP,
    109 				 IPL_VM);
    110 }
    111 
    112 /*
    113  * Allocate actual memory pages in DVMA space.
    114  * (idea for implementation borrowed from Chris Torek.)
    115  */
    116 void *
    117 dvma_malloc(size_t bytes)
    118 {
    119 	void *new_mem;
    120 	vsize_t new_size;
    121 
    122 	if (bytes == 0)
    123 		return NULL;
    124 	new_size = m68k_round_page(bytes);
    125 	new_mem = (void *)uvm_km_alloc(phys_map, new_size, 0, UVM_KMF_WIRED);
    126 	if (new_mem == 0)
    127 		panic("dvma_malloc: no space in phys_map");
    128 	/* The pmap code always makes DVMA pages non-cached. */
    129 	return new_mem;
    130 }
    131 
    132 /*
    133  * Free pages from dvma_malloc()
    134  */
    135 void
    136 dvma_free(void *addr, size_t size)
    137 {
    138 	vsize_t sz = m68k_round_page(size);
    139 
    140 	uvm_km_free(phys_map, (vaddr_t)addr, sz, UVM_KMF_WIRED);
    141 }
    142 
    143 /*
    144  * Given a DVMA address, return the physical address that
    145  * would be used by some OTHER bus-master besides the CPU.
    146  * (Examples: on-board ie/le, VME xy board).
    147  */
    148 u_long
    149 dvma_kvtopa(void *kva, int bustype)
    150 {
    151 	u_long addr, mask;
    152 
    153 	addr = (u_long)kva;
    154 	if ((addr & DVMA_MAP_BASE) != DVMA_MAP_BASE)
    155 		panic("dvma_kvtopa: bad dmva addr=0x%lx", addr);
    156 
    157 	switch (bustype) {
    158 	case BUS_OBIO:
    159 	case BUS_OBMEM:
    160 		mask = DVMA_OBIO_SLAVE_MASK;
    161 		break;
    162 	default:	/* VME bus device. */
    163 		mask = DVMA_VME_SLAVE_MASK;
    164 		break;
    165 	}
    166 
    167 	return addr & mask;
    168 }
    169 
    170 /*
    171  * Given a range of kernel virtual space, remap all the
    172  * pages found there into the DVMA space (dup mappings).
    173  * This IS safe to call at interrupt time.
    174  * (Typically called at SPLBIO)
    175  */
    176 void *
    177 dvma_mapin(void *kva, int len, int canwait /* ignored */)
    178 {
    179 	vaddr_t seg_kva;
    180 	vsize_t seg_len, seg_off;
    181 	vaddr_t v, x;
    182 	int s, sme, error;
    183 	vmem_addr_t seg_dma;
    184 
    185 	/* Get seg-aligned address and length. */
    186 	seg_kva = (vaddr_t)kva;
    187 	seg_len = (vsize_t)len;
    188 	seg_off = seg_kva & SEGOFSET;
    189 	seg_kva -= seg_off;
    190 	seg_len = sun3_round_seg(seg_len + seg_off);
    191 
    192 	/* Allocate the DVMA segment(s) */
    193 
    194 	const vm_flag_t vmflags = VM_INSTANTFIT |
    195 	    (/* canwait ? VM_SLEEP : */ VM_NOSLEEP);
    196 
    197 	error = vmem_xalloc(dvma_arena, seg_len,
    198 			    NBSG,		/* alignment */
    199 			    0,			/* phase */
    200 			    0,			/* nocross */
    201 			    VMEM_ADDR_MIN,	/* minaddr */
    202 			    VMEM_ADDR_MAX,	/* maxaddr */
    203 			    vmflags,
    204 			    &seg_dma);
    205 	if (error)
    206 		return NULL;
    207 
    208 	KASSERT((seg_dma & SEGOFSET) == 0);
    209 
    210 	s = splvm();
    211 
    212 	/* Duplicate the mappings into DMA space. */
    213 	v = seg_kva;
    214 	x = seg_dma;
    215 	while (seg_len > 0) {
    216 		sme = get_segmap(v);
    217 		KASSERT(sme != SEGINV);
    218 #ifdef	HAVECACHE
    219 		/* flush write-back on old mappings */
    220 		if (cache_size)
    221 			cache_flush_segment(v);
    222 #endif
    223 		set_segmap_allctx(x, sme);
    224 		v += NBSG;
    225 		x += NBSG;
    226 		seg_len -= NBSG;
    227 	}
    228 	seg_dma += seg_off;
    229 
    230 	splx(s);
    231 	return (void *)seg_dma;
    232 }
    233 
    234 /*
    235  * Free some DVMA space allocated by the above.
    236  * This IS safe to call at interrupt time.
    237  * (Typically called at SPLBIO)
    238  */
    239 void
    240 dvma_mapout(void *dma, int len)
    241 {
    242 	vaddr_t seg_dma;
    243 	vsize_t seg_len, seg_off;
    244 	vaddr_t v, x;
    245 	int sme __diagused;
    246 	int s;
    247 
    248 	/* Get seg-aligned address and length. */
    249 	seg_dma = (vaddr_t)dma;
    250 	seg_len = (vsize_t)len;
    251 	seg_off = seg_dma & SEGOFSET;
    252 	seg_dma -= seg_off;
    253 	seg_len = sun3_round_seg(seg_len + seg_off);
    254 
    255 	s = splvm();
    256 
    257 	/* Flush cache and remove DVMA mappings. */
    258 	v = seg_dma;
    259 	x = v + seg_len;
    260 	while (v < x) {
    261 		sme = get_segmap(v);
    262 		KASSERT(sme != SEGINV);
    263 #ifdef	HAVECACHE
    264 		/* flush write-back on the DVMA mappings */
    265 		if (cache_size)
    266 			cache_flush_segment(v);
    267 #endif
    268 		set_segmap_allctx(v, SEGINV);
    269 		v += NBSG;
    270 	}
    271 
    272 	splx(s);
    273 
    274 	vmem_xfree(dvma_arena, seg_dma, seg_len);
    275 }
    276 
    277 int
    278 _bus_dmamap_load_raw(bus_dma_tag_t t, bus_dmamap_t map, bus_dma_segment_t *segs,
    279     int nsegs, bus_size_t size, int flags)
    280 {
    281 
    282 	panic("_bus_dmamap_load_raw(): not implemented yet.");
    283 }
    284 
    285 int
    286 _bus_dmamap_load(bus_dma_tag_t t, bus_dmamap_t map, void *buf,
    287     bus_size_t buflen, struct proc *p, int flags)
    288 {
    289 	vaddr_t kva;
    290 	vmem_addr_t dva;
    291 	vsize_t off, sgsize;
    292 	paddr_t pa;
    293 	pmap_t pmap;
    294 	int error, rv __diagused;
    295 
    296 	/*
    297 	 * Make sure that on error condition we return "no valid mappings".
    298 	 */
    299 	map->dm_nsegs = 0;
    300 	map->dm_mapsize = 0;
    301 
    302 	if (buflen > map->_dm_size)
    303 		return EINVAL;
    304 
    305 	kva = (vaddr_t)buf;
    306 	off = kva & PGOFSET;
    307 	sgsize = round_page(off + buflen);
    308 
    309 	/* Try to allocate DVMA space. */
    310 
    311 	const vm_flag_t vmflags = VM_INSTANTFIT |
    312 	    ((flags & BUS_DMA_NOWAIT) ? VM_NOSLEEP : VM_SLEEP);
    313 
    314 	error = vmem_xalloc(dvma_arena, sgsize,
    315 			    0,			/* alignment */
    316 			    0,			/* phase */
    317 			    0,			/* nocross */
    318 			    VMEM_ADDR_MIN,	/* minaddr */
    319 			    VMEM_ADDR_MAX,	/* maxaddr */
    320 			    vmflags,
    321 			    &dva);
    322 	if (error)
    323 		return ENOMEM;
    324 
    325 	/* Fill in the segment. */
    326 	map->dm_segs[0].ds_addr = dva + off;
    327 	map->dm_segs[0].ds_len = buflen;
    328 	map->dm_segs[0]._ds_va = dva;
    329 	map->dm_segs[0]._ds_sgsize = sgsize;
    330 
    331 	/*
    332 	 * Now map the DVMA addresses we allocated to point to the
    333 	 * pages of the caller's buffer.
    334 	 */
    335 	if (p != NULL)
    336 		pmap = p->p_vmspace->vm_map.pmap;
    337 	else
    338 		pmap = pmap_kernel();
    339 
    340 	while (sgsize > 0) {
    341 		rv = pmap_extract(pmap, kva, &pa);
    342 #ifdef DIAGNOSTIC
    343 		if (rv == false)
    344 			panic("%s: unmapped VA", __func__);
    345 #endif
    346 		pmap_enter(pmap_kernel(), dva, pa | PMAP_NC,
    347 		    VM_PROT_READ | VM_PROT_WRITE, PMAP_WIRED);
    348 		kva += PAGE_SIZE;
    349 		dva += PAGE_SIZE;
    350 		sgsize -= PAGE_SIZE;
    351 	}
    352 
    353 	map->dm_nsegs = 1;
    354 	map->dm_mapsize = map->dm_segs[0].ds_len;
    355 
    356 	return 0;
    357 }
    358 
    359 void
    360 _bus_dmamap_unload(bus_dma_tag_t t, bus_dmamap_t map)
    361 {
    362 	bus_dma_segment_t *segs;
    363 	vaddr_t dva;
    364 	vsize_t sgsize;
    365 
    366 #ifdef DIAGNOSTIC
    367 	if (map->dm_nsegs != 1)
    368 		panic("%s: invalid nsegs = %d", __func__, map->dm_nsegs);
    369 #endif
    370 
    371 	segs = map->dm_segs;
    372 	dva = segs[0]._ds_va & ~PGOFSET;
    373 	sgsize = segs[0]._ds_sgsize;
    374 
    375 	/* Unmap the DVMA addresses. */
    376 	pmap_remove(pmap_kernel(), dva, dva + sgsize);
    377 	pmap_update(pmap_kernel());
    378 
    379 	/* Free the DVMA addresses. */
    380 	vmem_xfree(dvma_arena, dva, sgsize);
    381 
    382 	/* Mark the mappings as invalid. */
    383 	map->dm_mapsize = 0;
    384 	map->dm_nsegs = 0;
    385 }
    386