altera_avgen_fdt.c revision 245375
1275963Srpaulo/*- 2275963Srpaulo * Copyright (c) 2012 Robert N. M. Watson 3275963Srpaulo * All rights reserved. 4275963Srpaulo * 5275963Srpaulo * This software was developed by SRI International and the University of 6275963Srpaulo * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) 7275963Srpaulo * ("CTSRD"), as part of the DARPA CRASH research programme. 8275963Srpaulo * 9275963Srpaulo * Redistribution and use in source and binary forms, with or without 10275963Srpaulo * modification, are permitted provided that the following conditions 11275963Srpaulo * are met: 12275963Srpaulo * 1. Redistributions of source code must retain the above copyright 13275963Srpaulo * notice, this list of conditions and the following disclaimer. 14275963Srpaulo * 2. Redistributions in binary form must reproduce the above copyright 15275963Srpaulo * notice, this list of conditions and the following disclaimer in the 16275963Srpaulo * documentation and/or other materials provided with the distribution. 17275963Srpaulo * 18275963Srpaulo * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19275963Srpaulo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20275963Srpaulo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21275963Srpaulo * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22275963Srpaulo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23275963Srpaulo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24275963Srpaulo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25275963Srpaulo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26275963Srpaulo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27275963Srpaulo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28275963Srpaulo * SUCH DAMAGE. 29275963Srpaulo */ 30275963Srpaulo 31275963Srpaulo#include <sys/cdefs.h> 32275963Srpaulo__FBSDID("$FreeBSD: head/sys/dev/altera/avgen/altera_avgen_nexus.c 245375 2013-01-13 16:41:25Z rwatson $"); 33275963Srpaulo 34275963Srpaulo#include <sys/param.h> 35275963Srpaulo#include <sys/bus.h> 36275963Srpaulo#include <sys/condvar.h> 37275963Srpaulo#include <sys/conf.h> 38275963Srpaulo#include <sys/kernel.h> 39275963Srpaulo#include <sys/lock.h> 40275963Srpaulo#include <sys/malloc.h> 41275963Srpaulo#include <sys/module.h> 42275963Srpaulo#include <sys/mutex.h> 43275963Srpaulo#include <sys/rman.h> 44275963Srpaulo#include <sys/stat.h> 45275963Srpaulo#include <sys/systm.h> 46275963Srpaulo#include <sys/uio.h> 47275963Srpaulo 48275963Srpaulo#include <machine/bus.h> 49275963Srpaulo#include <machine/resource.h> 50275963Srpaulo#include <machine/vm.h> 51275963Srpaulo 52275963Srpaulo#include <vm/vm.h> 53275963Srpaulo 54275963Srpaulo#include <dev/altera/avgen/altera_avgen.h> 55322724Smarius 56322724Smarius/* 57322724Smarius * Generic device driver for allowing read(), write(), and mmap() on 58322724Smarius * memory-mapped, Avalon-attached devices. There is no actual dependence on 59322724Smarius * Avalon, so conceivably this should just be soc_dev or similar, since many 60322724Smarius * system-on-chip bus environments would work fine with the same code. 61322724Smarius */ 62322724Smarius 63322724Smariusstatic d_mmap_t altera_avgen_mmap; 64322724Smariusstatic d_read_t altera_avgen_read; 65322724Smariusstatic d_write_t altera_avgen_write; 66322724Smarius 67322724Smariusstatic struct cdevsw avg_cdevsw = { 68322724Smarius .d_version = D_VERSION, 69322724Smarius .d_mmap = altera_avgen_mmap, 70322724Smarius .d_read = altera_avgen_read, 71322724Smarius .d_write = altera_avgen_write, 72322724Smarius .d_name = "altera_avgen", 73322724Smarius}; 74322724Smarius 75322724Smariusstatic int 76322724Smariusaltera_avgen_read(struct cdev *dev, struct uio *uio, int flag) 77322724Smarius{ 78322724Smarius struct altera_avgen_softc *sc; 79322724Smarius u_long offset, size; 80322724Smarius#ifdef NOTYET 81322724Smarius uint64_t v8; 82322724Smarius#endif 83322724Smarius uint32_t v4; 84322724Smarius uint16_t v2; 85322724Smarius uint8_t v1; 86322724Smarius u_int width; 87322724Smarius int error; 88322724Smarius 89322724Smarius sc = dev->si_drv1; 90322724Smarius if ((sc->avg_flags & ALTERA_AVALON_FLAG_READ) == 0) 91322724Smarius return (EACCES); 92322724Smarius width = sc->avg_width; 93322724Smarius if (uio->uio_offset < 0 || uio->uio_offset % width != 0 || 94322724Smarius uio->uio_resid % width != 0) 95322724Smarius return (ENODEV); 96322724Smarius size = rman_get_size(sc->avg_res); 97322724Smarius if ((uio->uio_offset + uio->uio_resid < 0) || 98322724Smarius (uio->uio_offset + uio->uio_resid > size)) 99322724Smarius return (ENODEV); 100322724Smarius while (uio->uio_resid > 0) { 101322724Smarius offset = uio->uio_offset; 102322724Smarius if (offset + width > size) 103322724Smarius return (ENODEV); 104322724Smarius switch (width) { 105322724Smarius case 1: 106322724Smarius v1 = bus_read_1(sc->avg_res, offset); 107322724Smarius error = uiomove(&v1, sizeof(v1), uio); 108322724Smarius break; 109322724Smarius 110322724Smarius case 2: 111275963Srpaulo v2 = bus_read_2(sc->avg_res, offset); 112275963Srpaulo error = uiomove(&v2, sizeof(v2), uio); 113275963Srpaulo break; 114275963Srpaulo 115275963Srpaulo case 4: 116275963Srpaulo v4 = bus_read_4(sc->avg_res, offset); 117275963Srpaulo error = uiomove(&v4, sizeof(v4), uio); 118275963Srpaulo break; 119275963Srpaulo 120275963Srpaulo#ifdef NOTYET 121275963Srpaulo case 8: 122275963Srpaulo v8 = bus_read_8(sc->avg_res, offset); 123275963Srpaulo error = uiomove(&v8, sizeof(v8), uio); 124275963Srpaulo break; 125275963Srpaulo 126275963Srpaulo#endif 127275963Srpaulo 128275963Srpaulo default: 129275963Srpaulo panic("%s: unexpected widthment %u", __func__, width); 130275963Srpaulo } 131275963Srpaulo if (error) 132275963Srpaulo return (error); 133275963Srpaulo } 134275963Srpaulo return (0); 135275963Srpaulo} 136275963Srpaulo 137275963Srpaulostatic int 138275963Srpauloaltera_avgen_write(struct cdev *dev, struct uio *uio, int flag) 139275963Srpaulo{ 140275963Srpaulo struct altera_avgen_softc *sc; 141275963Srpaulo u_long offset, size; 142275963Srpaulo#ifdef NOTYET 143275963Srpaulo uint64_t v8; 144275963Srpaulo#endif 145275963Srpaulo uint32_t v4; 146275963Srpaulo uint16_t v2; 147275963Srpaulo uint8_t v1; 148275963Srpaulo u_int width; 149275963Srpaulo int error; 150275963Srpaulo 151275963Srpaulo sc = dev->si_drv1; 152275963Srpaulo if ((sc->avg_flags & ALTERA_AVALON_FLAG_WRITE) == 0) 153275963Srpaulo return (EACCES); 154275963Srpaulo width = sc->avg_width; 155275963Srpaulo if (uio->uio_offset < 0 || uio->uio_offset % width != 0 || 156275963Srpaulo uio->uio_resid % width != 0) 157275963Srpaulo return (ENODEV); 158275963Srpaulo size = rman_get_size(sc->avg_res); 159275963Srpaulo while (uio->uio_resid > 0) { 160275963Srpaulo offset = uio->uio_offset; 161275963Srpaulo if (offset + width > size) 162275963Srpaulo return (ENODEV); 163275963Srpaulo switch (width) { 164275963Srpaulo case 1: 165275963Srpaulo error = uiomove(&v1, sizeof(v1), uio); 166275963Srpaulo if (error) 167275963Srpaulo return (error); 168275963Srpaulo bus_write_1(sc->avg_res, offset, v1); 169275963Srpaulo break; 170275963Srpaulo 171275963Srpaulo case 2: 172275963Srpaulo error = uiomove(&v2, sizeof(v2), uio); 173275963Srpaulo if (error) 174275963Srpaulo return (error); 175275963Srpaulo bus_write_2(sc->avg_res, offset, v2); 176275963Srpaulo break; 177275963Srpaulo 178275963Srpaulo case 4: 179275963Srpaulo error = uiomove(&v4, sizeof(v4), uio); 180275963Srpaulo if (error) 181275963Srpaulo return (error); 182275963Srpaulo bus_write_4(sc->avg_res, offset, v4); 183275963Srpaulo break; 184275963Srpaulo 185275963Srpaulo#ifdef NOTYET 186275963Srpaulo case 8: 187275963Srpaulo error = uiomove(&v8, sizeof(v8), uio); 188322724Smarius if (error) 189322724Smarius return (error); 190275963Srpaulo bus_write_8(sc->avg_res, offset, v8); 191275963Srpaulo break; 192275963Srpaulo#endif 193275963Srpaulo 194275963Srpaulo default: 195275963Srpaulo panic("%s: unexpected width %u", __func__, width); 196275963Srpaulo } 197275963Srpaulo } 198275963Srpaulo return (0); 199275963Srpaulo} 200275963Srpaulo 201275963Srpaulostatic int 202275963Srpauloaltera_avgen_mmap(struct cdev *dev, vm_ooffset_t offset, vm_paddr_t *paddr, 203275963Srpaulo int nprot, vm_memattr_t *memattr) 204275963Srpaulo{ 205275963Srpaulo struct altera_avgen_softc *sc; 206275963Srpaulo 207275963Srpaulo sc = dev->si_drv1; 208275963Srpaulo if (nprot & VM_PROT_READ) { 209275963Srpaulo if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_READ) == 0) 210275963Srpaulo return (EACCES); 211275963Srpaulo } 212275963Srpaulo if (nprot & VM_PROT_WRITE) { 213275963Srpaulo if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_WRITE) == 0) 214275963Srpaulo return (EACCES); 215275963Srpaulo } 216275963Srpaulo if (nprot & VM_PROT_EXECUTE) { 217275963Srpaulo if ((sc->avg_flags & ALTERA_AVALON_FLAG_MMAP_EXEC) == 0) 218275963Srpaulo return (EACCES); 219275963Srpaulo } 220275963Srpaulo if (trunc_page(offset) == offset && 221275963Srpaulo rman_get_size(sc->avg_res) >= offset + PAGE_SIZE) { 222275963Srpaulo *paddr = rman_get_start(sc->avg_res) + offset; 223275963Srpaulo *memattr = VM_MEMATTR_UNCACHEABLE; 224275963Srpaulo } else 225275963Srpaulo return (ENODEV); 226275963Srpaulo return (0); 227275963Srpaulo} 228275963Srpaulo 229275963Srpaulostatic int 230275963Srpauloaltera_avgen_nexus_probe(device_t dev) 231275963Srpaulo{ 232275963Srpaulo 233275963Srpaulo device_set_desc(dev, "Generic Altera Avalon device attachment"); 234275963Srpaulo return (BUS_PROBE_DEFAULT); 235275963Srpaulo} 236275963Srpaulo 237275963Srpaulostatic int 238275963Srpauloaltera_avgen_process_options(struct altera_avgen_softc *sc, 239275963Srpaulo const char *str_fileio, const char *str_mmapio, const char *str_devname, 240275963Srpaulo int devunit) 241275963Srpaulo{ 242275963Srpaulo const char *cp; 243275963Srpaulo device_t dev = sc->avg_dev; 244275963Srpaulo 245275963Srpaulo /* 246275963Srpaulo * Check for valid combinations of options. 247275963Srpaulo */ 248275963Srpaulo if (str_fileio == NULL && str_mmapio == NULL) { 249275963Srpaulo device_printf(dev, 250275963Srpaulo "at least one of %s or %s must be specified\n", 251275963Srpaulo ALTERA_AVALON_STR_FILEIO, ALTERA_AVALON_STR_MMAPIO); 252275963Srpaulo return (ENXIO); 253275963Srpaulo } 254275963Srpaulo if (str_devname == NULL && devunit != -1) { 255275963Srpaulo device_printf(dev, "%s requires %s be specified\n", 256275963Srpaulo ALTERA_AVALON_STR_DEVUNIT, ALTERA_AVALON_STR_DEVNAME); 257275963Srpaulo return (ENXIO); 258275963Srpaulo } 259275963Srpaulo 260275963Srpaulo /* 261275963Srpaulo * Extract, digest, and save values. 262275963Srpaulo */ 263275963Srpaulo switch (sc->avg_width) { 264275963Srpaulo case 1: 265275963Srpaulo case 2: 266275963Srpaulo case 4: 267275963Srpaulo#ifdef NOTYET 268275963Srpaulo case 8: 269275963Srpaulo#endif 270275963Srpaulo break; 271275963Srpaulo 272275963Srpaulo default: 273275963Srpaulo device_printf(dev, "%s unsupported value %u\n", 274275963Srpaulo ALTERA_AVALON_STR_WIDTH, sc->avg_width); 275275963Srpaulo return (ENXIO); 276275963Srpaulo } 277275963Srpaulo sc->avg_flags = 0; 278275963Srpaulo if (str_fileio != NULL) { 279275963Srpaulo for (cp = str_fileio; *cp != '\0'; cp++) { 280275963Srpaulo switch (*cp) { 281275963Srpaulo case ALTERA_AVALON_CHAR_READ: 282275963Srpaulo sc->avg_flags |= ALTERA_AVALON_FLAG_READ; 283275963Srpaulo break; 284275963Srpaulo 285275963Srpaulo case ALTERA_AVALON_CHAR_WRITE: 286275963Srpaulo sc->avg_flags |= ALTERA_AVALON_FLAG_WRITE; 287275963Srpaulo break; 288275963Srpaulo 289275963Srpaulo default: 290275963Srpaulo device_printf(dev, 291275963Srpaulo "invalid %s character %c\n", 292275963Srpaulo ALTERA_AVALON_STR_FILEIO, *cp); 293275963Srpaulo return (ENXIO); 294275963Srpaulo } 295275963Srpaulo } 296275963Srpaulo } 297275963Srpaulo if (str_mmapio != NULL) { 298275963Srpaulo for (cp = str_mmapio; *cp != '\0'; cp++) { 299275963Srpaulo switch (*cp) { 300275963Srpaulo case ALTERA_AVALON_CHAR_READ: 301275963Srpaulo sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_READ; 302275963Srpaulo break; 303275963Srpaulo 304275963Srpaulo case ALTERA_AVALON_CHAR_WRITE: 305275963Srpaulo sc->avg_flags |= 306275963Srpaulo ALTERA_AVALON_FLAG_MMAP_WRITE; 307275963Srpaulo break; 308275963Srpaulo 309275963Srpaulo case ALTERA_AVALON_CHAR_EXEC: 310275963Srpaulo sc->avg_flags |= ALTERA_AVALON_FLAG_MMAP_EXEC; 311275963Srpaulo break; 312275963Srpaulo 313275963Srpaulo default: 314275963Srpaulo device_printf(dev, 315275963Srpaulo "invalid %s character %c\n", 316275963Srpaulo ALTERA_AVALON_STR_MMAPIO, *cp); 317275963Srpaulo return (ENXIO); 318275963Srpaulo } 319275963Srpaulo } 320275963Srpaulo } 321275963Srpaulo return (0); 322275963Srpaulo} 323275963Srpaulo 324275963Srpaulostatic int 325275963Srpauloaltera_avgen_nexus_attach(device_t dev) 326275963Srpaulo{ 327275963Srpaulo struct altera_avgen_softc *sc; 328275963Srpaulo const char *str_fileio, *str_mmapio; 329275963Srpaulo const char *str_devname; 330275963Srpaulo char devname[SPECNAMELEN + 1]; 331322724Smarius int devunit, error; 332322724Smarius 333322724Smarius sc = device_get_softc(dev); 334322724Smarius sc->avg_dev = dev; 335322724Smarius sc->avg_unit = device_get_unit(dev); 336322724Smarius 337322724Smarius /* 338322724Smarius * Query non-standard hints to find out what operations are permitted 339322724Smarius * on the device, and whether it is cached. 340322724Smarius */ 341322724Smarius str_fileio = NULL; 342322724Smarius str_mmapio = NULL; 343322724Smarius str_devname = NULL; 344322724Smarius devunit = -1; 345322724Smarius sc->avg_width = 1; 346322724Smarius error = resource_int_value(device_get_name(dev), device_get_unit(dev), 347322724Smarius ALTERA_AVALON_STR_WIDTH, &sc->avg_width); 348322724Smarius if (error != 0 && error != ENOENT) { 349322724Smarius device_printf(dev, "invalid %s\n", ALTERA_AVALON_STR_WIDTH); 350322724Smarius return (error); 351322724Smarius } 352322724Smarius (void)resource_string_value(device_get_name(dev), 353322724Smarius device_get_unit(dev), ALTERA_AVALON_STR_FILEIO, &str_fileio); 354322724Smarius (void)resource_string_value(device_get_name(dev), 355322724Smarius device_get_unit(dev), ALTERA_AVALON_STR_MMAPIO, &str_mmapio); 356322724Smarius (void)resource_string_value(device_get_name(dev), 357322724Smarius device_get_unit(dev), ALTERA_AVALON_STR_DEVNAME, &str_devname); 358322724Smarius (void)resource_int_value(device_get_name(dev), device_get_unit(dev), 359322724Smarius ALTERA_AVALON_STR_DEVUNIT, &devunit); 360322724Smarius error = altera_avgen_process_options(sc, str_fileio, str_mmapio, 361322724Smarius str_devname, devunit); 362322724Smarius if (error) 363322724Smarius return (error); 364322724Smarius 365322724Smarius /* Select a device name. */ 366322724Smarius if (str_devname != NULL) { 367322724Smarius if (devunit != -1) 368322724Smarius (void)snprintf(devname, sizeof(devname), "%s%d", 369322724Smarius str_devname, devunit); 370322724Smarius else 371322724Smarius (void)snprintf(devname, sizeof(devname), "%s", 372322724Smarius str_devname); 373322724Smarius } else 374322724Smarius snprintf(devname, sizeof(devname), "%s%d", "avgen", 375322724Smarius sc->avg_unit); 376322724Smarius 377322724Smarius /* Memory allocation and checking. */ 378322724Smarius sc->avg_rid = 0; 379322724Smarius sc->avg_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 380322724Smarius &sc->avg_rid, RF_ACTIVE); 381322724Smarius if (sc->avg_res == NULL) { 382322724Smarius device_printf(dev, "couldn't map memory\n"); 383322724Smarius return (ENXIO); 384322724Smarius } 385322724Smarius if (rman_get_size(sc->avg_res) >= PAGE_SIZE || str_mmapio != NULL) { 386322724Smarius if (rman_get_size(sc->avg_res) % PAGE_SIZE != 0) { 387322724Smarius device_printf(dev, 388322724Smarius "memory region not even multiple of page size\n"); 389322724Smarius error = ENXIO; 390322724Smarius goto error; 391322724Smarius } 392322724Smarius if (rman_get_start(sc->avg_res) % PAGE_SIZE != 0) { 393322724Smarius device_printf(dev, "memory region not page-aligned\n"); 394322724Smarius error = ENXIO; 395322724Smarius goto error; 396322724Smarius } 397322724Smarius } 398322724Smarius 399322724Smarius /* Device node allocation. */ 400322724Smarius if (str_devname == NULL) { 401322724Smarius str_devname = "altera_avgen%d"; 402322724Smarius devunit = sc->avg_unit; 403322724Smarius } 404322724Smarius if (devunit != -1) 405322724Smarius sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT, 406322724Smarius GID_WHEEL, S_IRUSR | S_IWUSR, str_devname, devunit); 407322724Smarius else 408322724Smarius sc->avg_cdev = make_dev(&avg_cdevsw, sc->avg_unit, UID_ROOT, 409322724Smarius GID_WHEEL, S_IRUSR | S_IWUSR, str_devname); 410322724Smarius if (sc->avg_cdev == NULL) { 411322724Smarius device_printf(sc->avg_dev, "%s: make_dev failed\n", __func__); 412322724Smarius error = ENXIO; 413322724Smarius goto error; 414322724Smarius } 415322724Smarius /* XXXRW: Slight race between make_dev(9) and here. */ 416322724Smarius sc->avg_cdev->si_drv1 = sc; 417322724Smarius return (0); 418322724Smarius 419322724Smariuserror: 420322724Smarius bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res); 421322724Smarius return (error); 422322724Smarius} 423322724Smarius 424322724Smariusstatic int 425322724Smariusaltera_avgen_nexus_detach(device_t dev) 426322724Smarius{ 427322724Smarius struct altera_avgen_softc *sc; 428322724Smarius 429322724Smarius sc = device_get_softc(dev); 430322724Smarius destroy_dev(sc->avg_cdev); 431322724Smarius bus_release_resource(dev, SYS_RES_MEMORY, sc->avg_rid, sc->avg_res); 432322724Smarius return (0); 433322724Smarius} 434322724Smarius 435322724Smariusstatic device_method_t altera_avgen_nexus_methods[] = { 436322724Smarius DEVMETHOD(device_probe, altera_avgen_nexus_probe), 437322724Smarius DEVMETHOD(device_attach, altera_avgen_nexus_attach), 438322724Smarius DEVMETHOD(device_detach, altera_avgen_nexus_detach), 439322724Smarius { 0, 0 } 440322724Smarius}; 441322724Smarius 442322724Smariusstatic driver_t altera_avgen_nexus_driver = { 443322724Smarius "altera_avgen", 444322724Smarius altera_avgen_nexus_methods, 445322724Smarius sizeof(struct altera_avgen_softc), 446322724Smarius}; 447322724Smarius 448322724Smariusstatic devclass_t altera_avgen_devclass; 449322724Smarius 450322724SmariusDRIVER_MODULE(avgen, nexus, altera_avgen_nexus_driver, altera_avgen_devclass, 451322724Smarius 0, 0); 452322724Smarius