1/*- 2 * Copyright (c) 2012-2013 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * This software was developed by SRI International and the University of 6 * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) 7 * ("CTSRD"), as part of the DARPA CRASH research programme. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31#include <sys/cdefs.h> 32__FBSDID("$FreeBSD$"); 33 34#include <sys/param.h> 35#include <sys/bus.h> 36#include <sys/condvar.h> 37#include <sys/conf.h> 38#include <sys/kernel.h> 39#include <sys/lock.h> 40#include <sys/malloc.h> 41#include <sys/module.h> 42#include <sys/mutex.h> 43#include <sys/rman.h> 44#include <sys/stat.h> 45#include <sys/systm.h> 46#include <sys/uio.h> 47 48#include <machine/bus.h> 49#include <machine/resource.h> 50 51#include <vm/vm.h> 52 53#include <dev/altera/avgen/altera_avgen.h> 54 55/* 56 * Generic device driver for allowing read(), write(), and mmap() on 57 * memory-mapped, Avalon-attached devices. There is no actual dependence on 58 * Avalon, so conceivably this should just be soc_dev or similar, since many 59 * system-on-chip bus environments would work fine with the same code. 60 */ 61 62devclass_t altera_avgen_devclass; 63 64static d_mmap_t altera_avgen_mmap; 65static d_read_t altera_avgen_read; 66static d_write_t altera_avgen_write; 67 68static struct cdevsw avg_cdevsw = { 69 .d_version = D_VERSION, 70 .d_mmap = altera_avgen_mmap, 71 .d_read = altera_avgen_read, 72 .d_write = altera_avgen_write, 73 .d_name = "altera_avgen", 74}; 75 76static int 77altera_avgen_read(struct cdev *dev, struct uio *uio, int flag) 78{ 79 struct altera_avgen_softc *sc; 80 u_long offset, size; 81#ifdef NOTYET 82 uint64_t v8; 83#endif 84 uint32_t v4; 85 uint16_t v2; 86 uint8_t v1; 87 u_int width; 88 int error; 89 90 sc = dev->si_drv1; 91 if ((sc->avg_flags & ALTERA_AVALON_FLAG_READ) == 0) 92 return (EACCES); 93 width = sc->avg_width; 94 if (uio->uio_offset < 0 || uio->uio_offset % width != 0 || 95 uio->uio_resid % width != 0) 96 return (ENODEV); 97 size = rman_get_size(sc->avg_res); 98 if ((uio->uio_offset + uio->uio_resid < 0) || 99 (uio->uio_offset + uio->uio_resid > size)) 100 return (ENODEV); 101 while (uio->uio_resid > 0) { 102 offset = uio->uio_offset; 103 if (offset + width > size) 104 return (ENODEV); 105 switch (width) { 106 case 1: 107 v1 = bus_read_1(sc->avg_res, offset); 108 error = uiomove(&v1, sizeof(v1), uio); 109 break; 110 111 case 2: 112 v2 = bus_read_2(sc->avg_res, offset); 113 error = uiomove(&v2, sizeof(v2), uio); 114 break; 115 116 case 4: 117 v4 = bus_read_4(sc->avg_res, offset); 118 error = uiomove(&v4, sizeof(v4), uio); 119 break; 120 121#ifdef NOTYET 122 case 8: 123 v8 = bus_read_8(sc->avg_res, offset); 124 error = uiomove(&v8, sizeof(v8), uio); 125 break; 126 127#endif 128 129 default: 130 panic("%s: unexpected widthment %u", __func__, width); 131 } 132 if (error) 133 return (error); 134 } 135 return (0); 136} 137 138static int 139altera_avgen_write(struct cdev *dev, struct uio *uio, int flag) 140{ 141 struct altera_avgen_softc *sc; 142 u_long offset, size; 143#ifdef NOTYET 144 uint64_t v8; 145#endif 146 uint32_t v4; 147 uint16_t v2; 148 uint8_t v1; 149 u_int width; 150 int error; 151 152 sc = dev->si_drv1; 153 if ((sc->avg_flags & ALTERA_AVALON_FLAG_WRITE) == 0) 154 return (EACCES); 155 width = sc->avg_width; 156 if (uio->uio_offset < 0 || uio->uio_offset % width != 0 || 157 uio->uio_resid % width != 0) 158 return (ENODEV); 159 size = rman_get_size(sc->avg_res); 160 while (uio->uio_resid > 0) { 161 offset = uio->uio_offset; 162 if (offset + width > size) 163 return (ENODEV); 164 switch (width) { 165 case 1: 166 error = uiomove(&v1, sizeof(v1), uio); 167 if (error) 168 return (error); 169 bus_write_1(sc->avg_res, offset, v1); 170 break; 171 172 case 2: 173 error = uiomove(&v2, sizeof(v2), uio); 174 if (error) 175 return (error); 176 bus_write_2(sc->avg_res, offset, v2); 177 break; 178 179 case 4: 180 error = uiomove(&v4, sizeof(v4), uio); 181 if (error) 182 return (error); 183 bus_write_4(sc->avg_res, offset, v4); 184 break; 185 186#ifdef NOTYET 187 case 8: 188 error = uiomove(&v8, sizeof(v8), uio); 189 if (error) 190 return (error); 191 bus_write_8(sc->avg_res, offset, v8); 192 break; 193#endif 194 195 default: 196 panic("%s: unexpected width %u", __func__, width); 197 } 198 } 199 return (0); 200} 201 202static int 203altera_avgen_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr, 204 int nprot, vm_memattr_t *memattr) 205{ 206 struct altera_avgen_softc *sc; 207 208 sc = dev->si_drv1; 209 if (nprot & VM_PROT_READ) { 210 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_READ) == 0) 211 return (EACCES); 212 } 213 if (nprot & VM_PROT_WRITE) { 214 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_WRITE) == 0) 215 return (EACCES); 216 } 217 if (nprot & VM_PROT_EXECUTE) { 218 if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_EXEC) == 0) 219 return (EACCES); 220 } 221 if (trunc_page(offset) == offset && 222 rman_get_size(sc->avg_res) >= offset + PAGE_SIZE) { 223 *paddr = rman_get_start(sc->avg_res) + offset; 224 *memattr = VM_MEMATTR_UNCACHEABLE; 225 } else 226 return (ENODEV); 227 return (0); 228} 229 230 231static int 232altera_avgen_process_options(struct altera_avgen_softc *sc, 233 const char *str_fileio, const char *str_mmapio, const char *str_devname, 234 int devunit) 235{ 236 const char *cp; 237 device_t dev = sc->avg_dev; 238 239 /* 240 * Check for valid combinations of options. 241 */ 242 if (str_fileio == NULL && str_mmapio == NULL) { 243 device_printf(dev, 244 "at least one of %s or %s must be specified\n", 245 ALTERA_AVALON_STR_FILEIO, ALTERA_AVALON_STR_MMAPIO); 246 return (ENXIO); 247 } 248 if (str_devname == NULL && devunit != -1) { 249 device_printf(dev, "%s requires %s be specified\n", 250 ALTERA_AVALON_STR_DEVUNIT, ALTERA_AVALON_STR_DEVNAME); 251 return (ENXIO); 252 } 253 254 /* 255 * Extract, digest, and save values. 256 */ 257 switch (sc->avg_width) { 258 case 1: 259 case 2: 260 case 4: 261#ifdef NOTYET 262 case 8: 263#endif 264 break; 265 266 default: 267 device_printf(dev, "%s unsupported value %u\n", 268 ALTERA_AVALON_STR_WIDTH, sc->avg_width); 269 return (ENXIO); 270 } 271 sc->avg_flags = 0; 272 if (str_fileio != NULL) { 273 for (cp = str_fileio; *cp != '\0'; cp++) { 274 switch (*cp) { 275 case ALTERA_AVALON_CHAR_READ: 276 sc->avg_flags |= ALTERA_AVALON_FLAG_READ; 277 break; 278 279 case ALTERA_AVALON_CHAR_WRITE: 280 sc->avg_flags |= ALTERA_AVALON_FLAG_WRITE; 281 break; 282 283 default: 284 device_printf(dev, 285 "invalid %s character %c\n", 286 ALTERA_AVALON_STR_FILEIO, *cp); 287 return (ENXIO); 288 } 289 } 290 } 291 if (str_mmapio != NULL) { 292 for (cp = str_mmapio; *cp != '\0'; cp++) { 293 switch (*cp) { 294 case ALTERA_AVALON_CHAR_READ: 295 sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_READ; 296 break; 297 298 case ALTERA_AVALON_CHAR_WRITE: 299 sc->avg_flags |= 300 ALTERA_AVALON_FLAG_MMAP_WRITE; 301 break; 302 303 case ALTERA_AVALON_CHAR_EXEC: 304 sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_EXEC; 305 break; 306 307 default: 308 device_printf(dev, 309 "invalid %s character %c\n", 310 ALTERA_AVALON_STR_MMAPIO, *cp); 311 return (ENXIO); 312 } 313 } 314 } 315 return (0); 316} 317 318int 319altera_avgen_attach(struct altera_avgen_softc *sc, const char *str_fileio, 320 const char *str_mmapio, const char *str_devname, int devunit) 321{ 322 device_t dev = sc->avg_dev; 323 int error; 324 325 error = altera_avgen_process_options(sc, str_fileio, str_mmapio, 326 str_devname, devunit); 327 if (error) 328 return (error); 329 330 if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) { 331 if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) { 332 device_printf(dev, 333 "memory region not even multiple of page size\n"); 334 return (ENXIO); 335 } 336 if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) { 337 device_printf(dev, "memory region not page-aligned\n"); 338 return (ENXIO); 339 } 340 } 341 342 /* Device node allocation. */ 343 if (str_devname == NULL) { 344 str_devname = "altera_avgen%d"; 345 devunit = sc->avg_unit; 346 } 347 if (devunit != -1) 348 sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT, 349 GID_WHEEL, S_IRUSR | S_IWUSR, str_devname, devunit); 350 else 351 sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT, 352 GID_WHEEL, S_IRUSR | S_IWUSR, str_devname); 353 if (sc->avg_cdev == NULL) { 354 device_printf(sc->avg_dev, "%s: make_dev failed\n", __func__); 355 return (ENXIO); 356 } 357 /* XXXRW: Slight race between make_dev(9) and here. */ 358 sc->avg_cdev->si_drv1 = sc; 359 return (0); 360} 361 362void 363altera_avgen_detach(struct altera_avgen_softc *sc) 364{ 365 366 destroy_dev(sc->avg_cdev); 367} 368