DF-5565 / df5563_rig.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 | /* * df5563_rig.c -- DF-5563/5564/5565 rig: instantiate the REAL in-tree * iicbus stack (iicbus.ko + iicbb.ko + iic.ko) under a fake GPIO parent * and expose the iiconf.c blocking primitives to userland. * * Targets (sys/bus/iicbus/iiconf.c): * DF-5563 iicbus_request_bus() parks requesters in * iicbus_poll() -> tsleep(sc, PCATCH, "iicreq", 0) (:63) * with NO timeout, NO detach drain, and the sleep channel * IS the iicbus softc. kldunload of the bridge/iicbus * modules frees the softc AND the module text the parked * thread would resume into -> freed-text execution (UAF). * DF-5564 `IIC_NOINTR == 0' collapses `IIC_WAIT|IIC_NOINTR' to plain * IIC_WAIT, so WAIT-only callers (smbus passthrough of * SMB_WAIT: ipmi_ssif.c:81,181, cyapa.c:1176,1624) sleep at * iiconf.c:67 WITHOUT PCATCH and with timeout 0 -- an * unkillable indefinite park (plus the request path's * check-then-sleep race can lose the wakeup entirely). * DF-5565 the first do-while of iicbus_request_bus (:92-97) loops * `callback -> poll' with NO exit when how == IIC_DONTWAIT * and the bridge callback persistently reports busy * (in-tree: lpbb -> ppb_request_bus(DONTWAIT) -> * EWOULDBLOCK, lpbb.c:97-101, ppbconf.c:458-493): an * unkillable tight kernel loop (no sleep, no yield, no * signal checkpoint). * * Rig structure (mirrors the DF-5539/5558 fakebb rig): * * nexus -> fakebb0 (this module: GPIO emulator + /dev/df5563) * -> iicbb0 (real iicbb.ko) * -> iicbus0 (real iicbus.ko) <-- code under test * -> iic0 (real iic.ko, /dev/iic0; needs the * disclosed DF-5559/DF-5560 setup fixes) * * fakebb_callback() returns EWOULDBLOCK for IIC_REQUEST_BUS while * hw.df5563.busy=1 -- the same contract lpbb gives when the ppbus is * held by another client (lpt). This is what forces iiconf.c's first * do-while into iicbus_poll() (or into the livelock for DONTWAIT). * * /dev/df5563 ioctls drive iicbus_request_bus()/iicbus_release_bus() * directly with each `how' variant. The owner token is fakebb0 itself * (any device_t is valid -- iicsmb uses the iicsmb child, ipmi_ssif * the ipmi device; the API only compares pointers). * * On attach the rig also runs one request -> block_write -> block_read * -> release round trip against the emulated slave (busy defaults to * 0), proving the production stack works end-to-end before each test * and after the fix. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/malloc.h> #include <sys/types.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/sysctl.h> #include <bus/iicbus/iiconf.h> #include <bus/iicbus/iicbus.h> #include "iicbb_if.h" #define FAKEBB_NACK_ADDR 0x6e /* slave refuses this address */ #define FAKEBB_TX_BASE 0xc0 /* slave TX pattern base */ /* park.c <-> rig ioctl protocol */ #define DF5563_PARK_INTR _IOWR('d', 1, int) /* IIC_WAIT | IIC_INTR */ #define DF5563_PARK_WAIT _IOWR('d', 2, int) /* IIC_WAIT (== WAIT|NOINTR, NOINTR==0) */ #define DF5563_PARK_NOWAIT _IOWR('d', 3, int) /* IIC_DONTWAIT */ #define DF5563_RELEASE _IOWR('d', 4, int) /* iicbus_release_bus */ static struct dev_ops df5563_ops; struct fakebb_softc { device_t self; /* fakebb0: the bus-owner token */ device_t iicbb; /* our iicbb child */ device_t iicbus; /* located iicbus grandchild (code under test) */ cdev_t cdev; /* /dev/df5563 */ /* emulated GPIO pin state (what the master drove) */ int sda, scl, last_scl; /* emulated I2C slave */ int started; /* START seen and no STOP yet */ int clk; /* SCL rising edges this byte slot (1..9) */ u_char shift; /* shift-in register */ int first_byte; /* next byte slot is the address */ int reading; /* current transfer direction */ u_char addr; /* addressed slave byte */ int nack_this; /* NACK the current address */ u_char rx[32]; /* bytes captured from master */ int rxlen; u_char txnext; /* next TX pattern byte */ u_char txcur; /* current TX byte being clocked out */ int starts, stops; }; static int df5563_busy = 0; /* fake "ppbus held by lpt" */ SYSCTL_NODE(_hw, OID_AUTO, df5563, CTLFLAG_RW, 0, "DF-5563 rig"); SYSCTL_INT(_hw_df5563, OID_AUTO, busy, CTLFLAG_RW, &df5563_busy, 0, "bridge reports the bus busy (lpbb/ppbus-held emulation)"); /* ------------------------------------------------------------------ */ /* emulated wire: what a master reading SDA would see */ /* ------------------------------------------------------------------ */ static int fakebb_wire_sda(struct fakebb_softc *sc) { if (!sc->started) return (sc->sda); if (sc->clk == 9) { /* ACK slot: the slave owns the wire */ return (sc->nack_this ? 1 : 0); } if (sc->reading && !sc->first_byte && sc->clk >= 1 && sc->clk <= 8) return ((sc->txcur >> (8 - sc->clk)) & 1); return (sc->sda); /* slave idle: master's own level */ } /* ------------------------------------------------------------------ */ /* iicbb_if methods (the "fake GPIO" vtable) */ /* ------------------------------------------------------------------ */ static void fakebb_setsda(device_t dev, int val) { struct fakebb_softc *sc = device_get_softc(dev); val = val ? 1 : 0; if (sc->scl && sc->sda && !val) { /* SDA falling while SCL high: START / repeated START */ sc->started = 1; sc->clk = 0; sc->shift = 0; sc->first_byte = 1; sc->reading = 0; sc->nack_this = 0; sc->rxlen = 0; sc->starts++; } else if (sc->scl && !sc->sda && val && sc->started) { /* SDA rising while SCL high: STOP */ sc->started = 0; sc->stops++; } sc->sda = val; } static void fakebb_setscl(device_t dev, int val) { struct fakebb_softc *sc = device_get_softc(dev); val = val ? 1 : 0; if (sc->started) { if (val && !sc->last_scl) { /* rising edge: data bits are sampled here */ sc->clk++; if (sc->clk >= 1 && sc->clk <= 8) sc->shift = (u_char)((sc->shift << 1) | (fakebb_wire_sda(sc) & 1)); } else if (!val && sc->last_scl && sc->clk == 9) { /* falling edge of the ACK clock: byte slot done */ if (sc->first_byte) { sc->addr = sc->shift; sc->first_byte = 0; sc->reading = sc->shift & 1; sc->nack_this = (sc->addr == FAKEBB_NACK_ADDR); sc->txnext = FAKEBB_TX_BASE; if (sc->reading) sc->txcur = sc->txnext++; } else if (sc->reading) { sc->txcur = sc->txnext++; } else { if (sc->rxlen < (int)sizeof(sc->rx)) sc->rx[sc->rxlen] = sc->shift; sc->rxlen++; } sc->clk = 0; sc->shift = 0; } } sc->last_scl = val; sc->scl = val; } static int fakebb_getsda(device_t dev) { struct fakebb_softc *sc = device_get_softc(dev); return (fakebb_wire_sda(sc)); } static int fakebb_getscl(device_t dev) { struct fakebb_softc *sc = device_get_softc(dev); return (sc->scl); } static int fakebb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { (void)dev; (void)speed; (void)addr; /* NOT the DF-5539 contract violation: we always write *oldaddr */ if (oldaddr != NULL) *oldaddr = 0; return (0); } /* * The busy knob. lpbb gives exactly this contract when the ppbus is * held by another client (sys/dev/misc/lpbb/lpbb.c:97-101 -> * ppb_request_bus(..., PPB_DONTWAIT) -> EWOULDBLOCK, * sys/bus/ppbus/ppbconf.c:458-493). */ static int fakebb_callback(device_t dev, int index, caddr_t data) { (void)dev; (void)data; if (index == IIC_REQUEST_BUS && df5563_busy) return (EWOULDBLOCK); return (0); } /* ------------------------------------------------------------------ */ /* /dev/df5563: userland entry into the iiconf.c blocking primitives */ /* ------------------------------------------------------------------ */ static int df5563_open(struct dev_open_args *ap) { return (0); } static int df5563_close(struct dev_close_args *ap) { return (0); } static int df5563_ioctl(struct dev_ioctl_args *ap) { cdev_t cdev = ap->a_head.a_dev; struct fakebb_softc *sc = cdev->si_drv1; u_long cmd = ap->a_cmd; int how; int error; if (sc->iicbus == NULL) { sc->iicbus = devclass_get_device(devclass_find("iicbus"), 0); if (sc->iicbus == NULL) return (ENXIO); } switch (cmd) { case DF5563_PARK_INTR: how = IIC_WAIT | IIC_INTR; break; case DF5563_PARK_WAIT: how = IIC_WAIT; /* == IIC_WAIT | IIC_NOINTR (NOINTR==0) */ break; case DF5563_PARK_NOWAIT: how = IIC_DONTWAIT; break; case DF5563_RELEASE: error = iicbus_release_bus(sc->iicbus, sc->self); kprintf("[df5563] ioctl: iicbus_release_bus returned %d\n", error); return (error); default: return (ENOTTY); } kprintf("[df5563] ioctl: parking in iicbus_request_bus(how=0x%x)\n", how); error = iicbus_request_bus(sc->iicbus, sc->self, how); kprintf("[df5563] ioctl: iicbus_request_bus returned %d\n", error); return (error); } /* ------------------------------------------------------------------ */ /* fakebb bus device plumbing */ /* ------------------------------------------------------------------ */ static void fakebb_identify(driver_t *driver, device_t parent) { device_t child; child = device_find_child(parent, "fakebb", 0); if (child == NULL) BUS_ADD_CHILD(parent, parent, 0, "fakebb", 0); } static int fakebb_probe(device_t dev) { device_set_desc(dev, "DF-5563 fake GPIO bit-bang i2c master+slave"); return (0); } static int fakebb_attach(device_t dev) { struct fakebb_softc *sc = (struct fakebb_softc *)device_get_softc(dev); device_t bus = NULL; devclass_t dc; char wbuf[8], rbuf[8]; int i, error, sent, rlen; int ok_w = 1, ok_r = 1; int tries; sc->self = dev; sc->iicbb = device_add_child(dev, "iicbb", -1); if (sc->iicbb == NULL) return (ENXIO); /* cascades: iicbb0 -> iicbus0 (+ iic0/iicsmb0 when loaded) */ bus_generic_attach(dev); for (tries = 0; tries < 50 && bus == NULL; tries++) { dc = devclass_find("iicbus"); if (dc != NULL) bus = devclass_get_device(dc, 0); if (bus == NULL) tsleep(sc, 0, "fkbw", hz / 20); } sc->iicbus = bus; sc->cdev = make_dev(&df5563_ops, 0, UID_ROOT, GID_WHEEL, 0600, "df5563"); if (sc->cdev == NULL) kprintf("[df5563] make_dev failed\n"); else sc->cdev->si_drv1 = sc; kprintf("[df5563] attach done: iicbus=%p cdev=%p busy=%d\n", bus, sc->cdev, df5563_busy); if (bus == NULL) return (0); /* ---------------- sanity round trip ---------------- */ error = iicbus_request_bus(bus, dev, IIC_WAIT | IIC_INTR); kprintf("[df5563] sanity request_bus error=%d\n", error); wbuf[0] = 0xde; wbuf[1] = 0xad; wbuf[2] = 0xbe; wbuf[3] = 0xef; wbuf[4] = 0x55; wbuf[5] = 0xaa; wbuf[6] = 0x12; wbuf[7] = 0x34; error = iicbus_block_write(bus, 0x50, wbuf, 8, &sent); if (error != 0 || sent != 8) ok_w = 0; for (i = 0; i < 8; i++) if (sc->rx[i] != (u_char)wbuf[i]) ok_w = 0; error = iicbus_block_read(bus, 0x50, rbuf, 8, &rlen); if (error != 0 || rlen != 8) ok_r = 0; for (i = 0; i < 8; i++) if ((u_char)rbuf[i] != (u_char)(FAKEBB_TX_BASE + i)) ok_r = 0; iicbus_release_bus(bus, dev); kprintf("[df5563] SANITY: write_ok=%d read_ok=%d starts=%d stops=%d\n", ok_w, ok_r, sc->starts, sc->stops); return (0); } static int fakebb_detach(device_t dev) { struct fakebb_softc *sc = (struct fakebb_softc *)device_get_softc(dev); device_t child; if (sc->cdev) { dev_ops_remove_minor(&df5563_ops, 0); sc->cdev = NULL; } child = sc->iicbb; bus_generic_detach(dev); if (child) device_delete_child(dev, child); return (0); } static void fakebb_child_detached(device_t dev, device_t child) { struct fakebb_softc *sc = (struct fakebb_softc *)device_get_softc(dev); if (child == sc->iicbb) sc->iicbb = NULL; } static struct dev_ops df5563_ops = { { "df5563", 0, 0 }, .d_open = df5563_open, .d_close = df5563_close, .d_ioctl = df5563_ioctl, }; static device_method_t fakebb_methods[] = { /* device interface */ DEVMETHOD(device_identify, fakebb_identify), DEVMETHOD(device_probe, fakebb_probe), DEVMETHOD(device_attach, fakebb_attach), DEVMETHOD(device_detach, fakebb_detach), /* bus interface */ DEVMETHOD(bus_child_detached, fakebb_child_detached), /* iicbb interface (the fake GPIO vtable) */ DEVMETHOD(iicbb_callback, fakebb_callback), DEVMETHOD(iicbb_setsda, fakebb_setsda), DEVMETHOD(iicbb_setscl, fakebb_setscl), DEVMETHOD(iicbb_getsda, fakebb_getsda), DEVMETHOD(iicbb_getscl, fakebb_getscl), DEVMETHOD(iicbb_reset, fakebb_reset), DEVMETHOD_END }; static driver_t fakebb_driver = { "fakebb", fakebb_methods, sizeof(struct fakebb_softc), }; static devclass_t fakebb_devclass; DRIVER_MODULE(fakebb, nexus, fakebb_driver, fakebb_devclass, NULL, NULL); MODULE_DEPEND(fakebb, iicbb, 1, 1, 1); MODULE_VERSION(fakebb, 1); |