1 /*- 2 * Copyright (c) 2014 Marcel Moolenaar 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 #include <sys/param.h> 31 #include <sys/systm.h> 32 #include <sys/bus.h> 33 #include <sys/conf.h> 34 #include <sys/cons.h> 35 #include <sys/fcntl.h> 36 #include <sys/interrupt.h> 37 #include <sys/kdb.h> 38 #include <sys/kernel.h> 39 #include <sys/malloc.h> 40 #include <sys/mman.h> 41 #include <sys/proc.h> 42 #include <sys/queue.h> 43 #include <sys/reboot.h> 44 #include <machine/bus.h> 45 #include <sys/rman.h> 46 #include <sys/uio.h> 47 #include <machine/resource.h> 48 #include <machine/stdarg.h> 49 50 #include <dev/pci/pcivar.h> 51 52 #include <dev/proto/proto.h> 53 #include <dev/proto/proto_dev.h> 54 #include <dev/proto/proto_busdma.h> 55 56 CTASSERT(SYS_RES_IRQ != PROTO_RES_UNUSED && 57 SYS_RES_DRQ != PROTO_RES_UNUSED && 58 SYS_RES_MEMORY != PROTO_RES_UNUSED && 59 SYS_RES_IOPORT != PROTO_RES_UNUSED); 60 CTASSERT(SYS_RES_IRQ != PROTO_RES_PCICFG && 61 SYS_RES_DRQ != PROTO_RES_PCICFG && 62 SYS_RES_MEMORY != PROTO_RES_PCICFG && 63 SYS_RES_IOPORT != PROTO_RES_PCICFG); 64 CTASSERT(SYS_RES_IRQ != PROTO_RES_BUSDMA && 65 SYS_RES_DRQ != PROTO_RES_BUSDMA && 66 SYS_RES_MEMORY != PROTO_RES_BUSDMA && 67 SYS_RES_IOPORT != PROTO_RES_BUSDMA); 68 69 devclass_t proto_devclass; 70 char proto_driver_name[] = "proto"; 71 72 static d_open_t proto_open; 73 static d_close_t proto_close; 74 static d_read_t proto_read; 75 static d_write_t proto_write; 76 static d_ioctl_t proto_ioctl; 77 static d_mmap_t proto_mmap; 78 79 struct cdevsw proto_devsw = { 80 .d_version = D_VERSION, 81 .d_flags = 0, 82 .d_name = proto_driver_name, 83 .d_open = proto_open, 84 .d_close = proto_close, 85 .d_read = proto_read, 86 .d_write = proto_write, 87 .d_ioctl = proto_ioctl, 88 .d_mmap = proto_mmap, 89 }; 90 91 static MALLOC_DEFINE(M_PROTO, "PROTO", "PROTO driver"); 92 93 int 94 proto_add_resource(struct proto_softc *sc, int type, int rid, 95 struct resource *res) 96 { 97 struct proto_res *r; 98 99 if (type == PROTO_RES_UNUSED) 100 return (EINVAL); 101 if (sc->sc_rescnt == PROTO_RES_MAX) 102 return (ENOSPC); 103 104 r = sc->sc_res + sc->sc_rescnt++; 105 r->r_type = type; 106 r->r_rid = rid; 107 r->r_d.res = res; 108 return (0); 109 } 110 111 #ifdef notyet 112 static int 113 proto_intr(void *arg) 114 { 115 struct proto_softc *sc = arg; 116 117 /* XXX TODO */ 118 return (FILTER_HANDLED); 119 } 120 #endif 121 122 int 123 proto_attach(device_t dev) 124 { 125 struct proto_softc *sc; 126 struct proto_res *r; 127 u_int res; 128 129 sc = device_get_softc(dev); 130 sc->sc_dev = dev; 131 132 for (res = 0; res < sc->sc_rescnt; res++) { 133 r = sc->sc_res + res; 134 switch (r->r_type) { 135 case SYS_RES_IRQ: 136 /* XXX TODO */ 137 break; 138 case SYS_RES_DRQ: 139 break; 140 case SYS_RES_MEMORY: 141 case SYS_RES_IOPORT: 142 r->r_size = rman_get_size(r->r_d.res); 143 r->r_u.cdev = make_dev(&proto_devsw, res, 0, 0, 0666, 144 "proto/%s/%02x.%s", device_get_desc(dev), r->r_rid, 145 (r->r_type == SYS_RES_IOPORT) ? "io" : "mem"); 146 r->r_u.cdev->si_drv1 = sc; 147 r->r_u.cdev->si_drv2 = r; 148 break; 149 case PROTO_RES_PCICFG: 150 r->r_size = 4096; 151 r->r_u.cdev = make_dev(&proto_devsw, res, 0, 0, 0666, 152 "proto/%s/pcicfg", device_get_desc(dev)); 153 r->r_u.cdev->si_drv1 = sc; 154 r->r_u.cdev->si_drv2 = r; 155 break; 156 case PROTO_RES_BUSDMA: 157 r->r_d.busdma = proto_busdma_attach(sc); 158 r->r_size = 0; /* no read(2) nor write(2) */ 159 r->r_u.cdev = make_dev(&proto_devsw, res, 0, 0, 0666, 160 "proto/%s/busdma", device_get_desc(dev)); 161 r->r_u.cdev->si_drv1 = sc; 162 r->r_u.cdev->si_drv2 = r; 163 break; 164 } 165 } 166 return (0); 167 } 168 169 int 170 proto_detach(device_t dev) 171 { 172 struct proto_softc *sc; 173 struct proto_res *r; 174 u_int res; 175 176 sc = device_get_softc(dev); 177 178 /* Don't detach if we have open device files. */ 179 for (res = 0; res < sc->sc_rescnt; res++) { 180 r = sc->sc_res + res; 181 if (r->r_opened) 182 return (EBUSY); 183 } 184 185 for (res = 0; res < sc->sc_rescnt; res++) { 186 r = sc->sc_res + res; 187 switch (r->r_type) { 188 case SYS_RES_IRQ: 189 /* XXX TODO */ 190 bus_release_resource(dev, r->r_type, r->r_rid, 191 r->r_d.res); 192 break; 193 case SYS_RES_DRQ: 194 bus_release_resource(dev, r->r_type, r->r_rid, 195 r->r_d.res); 196 break; 197 case SYS_RES_MEMORY: 198 case SYS_RES_IOPORT: 199 bus_release_resource(dev, r->r_type, r->r_rid, 200 r->r_d.res); 201 destroy_dev(r->r_u.cdev); 202 break; 203 case PROTO_RES_PCICFG: 204 destroy_dev(r->r_u.cdev); 205 break; 206 case PROTO_RES_BUSDMA: 207 proto_busdma_detach(sc, r->r_d.busdma); 208 destroy_dev(r->r_u.cdev); 209 break; 210 } 211 r->r_type = PROTO_RES_UNUSED; 212 } 213 sc->sc_rescnt = 0; 214 return (0); 215 } 216 217 /* 218 * Device functions 219 */ 220 221 static int 222 proto_open(struct cdev *cdev, int oflags, int devtype, struct thread *td) 223 { 224 struct proto_res *r; 225 226 r = cdev->si_drv2; 227 if (!atomic_cmpset_acq_ptr(&r->r_opened, 0UL, (uintptr_t)td->td_proc)) 228 return (EBUSY); 229 return (0); 230 } 231 232 static int 233 proto_close(struct cdev *cdev, int fflag, int devtype, struct thread *td) 234 { 235 struct proto_res *r; 236 struct proto_softc *sc; 237 238 sc = cdev->si_drv1; 239 r = cdev->si_drv2; 240 if (!atomic_cmpset_acq_ptr(&r->r_opened, (uintptr_t)td->td_proc, 0UL)) 241 return (ENXIO); 242 if (r->r_type == PROTO_RES_BUSDMA) 243 proto_busdma_cleanup(sc, r->r_d.busdma); 244 return (0); 245 } 246 247 static int 248 proto_read(struct cdev *cdev, struct uio *uio, int ioflag) 249 { 250 union { 251 uint8_t x1[8]; 252 uint16_t x2[4]; 253 uint32_t x4[2]; 254 uint64_t x8[1]; 255 } buf; 256 struct proto_softc *sc; 257 struct proto_res *r; 258 device_t dev; 259 off_t ofs; 260 u_long width; 261 int error; 262 263 sc = cdev->si_drv1; 264 dev = sc->sc_dev; 265 r = cdev->si_drv2; 266 267 width = uio->uio_resid; 268 if (width < 1 || width > 8 || bitcount16(width) > 1) 269 return (EIO); 270 ofs = uio->uio_offset; 271 if (ofs + width > r->r_size) 272 return (EIO); 273 274 switch (width) { 275 case 1: 276 buf.x1[0] = (r->r_type == PROTO_RES_PCICFG) ? 277 pci_read_config(dev, ofs, 1) : bus_read_1(r->r_d.res, ofs); 278 break; 279 case 2: 280 buf.x2[0] = (r->r_type == PROTO_RES_PCICFG) ? 281 pci_read_config(dev, ofs, 2) : bus_read_2(r->r_d.res, ofs); 282 break; 283 case 4: 284 buf.x4[0] = (r->r_type == PROTO_RES_PCICFG) ? 285 pci_read_config(dev, ofs, 4) : bus_read_4(r->r_d.res, ofs); 286 break; 287 #ifndef __i386__ 288 case 8: 289 if (r->r_type == PROTO_RES_PCICFG) 290 return (EINVAL); 291 buf.x8[0] = bus_read_8(r->r_d.res, ofs); 292 break; 293 #endif 294 default: 295 return (EIO); 296 } 297 298 error = uiomove(&buf, width, uio); 299 return (error); 300 } 301 302 static int 303 proto_write(struct cdev *cdev, struct uio *uio, int ioflag) 304 { 305 union { 306 uint8_t x1[8]; 307 uint16_t x2[4]; 308 uint32_t x4[2]; 309 uint64_t x8[1]; 310 } buf; 311 struct proto_softc *sc; 312 struct proto_res *r; 313 device_t dev; 314 off_t ofs; 315 u_long width; 316 int error; 317 318 sc = cdev->si_drv1; 319 dev = sc->sc_dev; 320 r = cdev->si_drv2; 321 322 width = uio->uio_resid; 323 if (width < 1 || width > 8 || bitcount16(width) > 1) 324 return (EIO); 325 ofs = uio->uio_offset; 326 if (ofs + width > r->r_size) 327 return (EIO); 328 329 error = uiomove(&buf, width, uio); 330 if (error) 331 return (error); 332 333 switch (width) { 334 case 1: 335 if (r->r_type == PROTO_RES_PCICFG) 336 pci_write_config(dev, ofs, buf.x1[0], 1); 337 else 338 bus_write_1(r->r_d.res, ofs, buf.x1[0]); 339 break; 340 case 2: 341 if (r->r_type == PROTO_RES_PCICFG) 342 pci_write_config(dev, ofs, buf.x2[0], 2); 343 else 344 bus_write_2(r->r_d.res, ofs, buf.x2[0]); 345 break; 346 case 4: 347 if (r->r_type == PROTO_RES_PCICFG) 348 pci_write_config(dev, ofs, buf.x4[0], 4); 349 else 350 bus_write_4(r->r_d.res, ofs, buf.x4[0]); 351 break; 352 #ifndef __i386__ 353 case 8: 354 if (r->r_type == PROTO_RES_PCICFG) 355 return (EINVAL); 356 bus_write_8(r->r_d.res, ofs, buf.x8[0]); 357 break; 358 #endif 359 default: 360 return (EIO); 361 } 362 363 return (0); 364 } 365 366 static int 367 proto_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, 368 struct thread *td) 369 { 370 struct proto_ioc_region *region; 371 struct proto_ioc_busdma *busdma; 372 struct proto_res *r; 373 struct proto_softc *sc; 374 int error; 375 376 sc = cdev->si_drv1; 377 r = cdev->si_drv2; 378 379 error = 0; 380 switch (cmd) { 381 case PROTO_IOC_REGION: 382 if (r->r_type == PROTO_RES_BUSDMA) { 383 error = EINVAL; 384 break; 385 } 386 region = (struct proto_ioc_region *)data; 387 region->size = r->r_size; 388 if (r->r_type == PROTO_RES_PCICFG) 389 region->address = 0; 390 else 391 region->address = rman_get_start(r->r_d.res); 392 break; 393 case PROTO_IOC_BUSDMA: 394 if (r->r_type != PROTO_RES_BUSDMA) { 395 error = EINVAL; 396 break; 397 } 398 busdma = (struct proto_ioc_busdma *)data; 399 error = proto_busdma_ioctl(sc, r->r_d.busdma, busdma, td); 400 break; 401 default: 402 error = ENOIOCTL; 403 break; 404 } 405 return (error); 406 } 407 408 static int 409 proto_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr, 410 int prot, vm_memattr_t *memattr) 411 { 412 struct proto_res *r; 413 414 if (offset & PAGE_MASK) 415 return (EINVAL); 416 if (prot & PROT_EXEC) 417 return (EACCES); 418 419 r = cdev->si_drv2; 420 421 switch (r->r_type) { 422 case SYS_RES_MEMORY: 423 if (offset >= r->r_size) 424 return (EINVAL); 425 *paddr = rman_get_start(r->r_d.res) + offset; 426 #ifndef __sparc64__ 427 *memattr = VM_MEMATTR_UNCACHEABLE; 428 #endif 429 break; 430 case PROTO_RES_BUSDMA: 431 if (!proto_busdma_mmap_allowed(r->r_d.busdma, offset)) 432 return (EINVAL); 433 *paddr = offset; 434 break; 435 default: 436 return (ENXIO); 437 } 438 return (0); 439 } 440