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