diff --git a/sys/dev/cfi/cfi_core.c b/sys/dev/cfi/cfi_core.c index 6179f0a5a79a..b85f6f351219 100644 --- a/sys/dev/cfi/cfi_core.c +++ b/sys/dev/cfi/cfi_core.c @@ -1,587 +1,614 @@ /*- * Copyright (c) 2007, Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_cfi.h" #include #include #include #include +#include #include #include #include #include #include #include #include #include extern struct cdevsw cfi_cdevsw; char cfi_driver_name[] = "cfi"; devclass_t cfi_devclass; devclass_t cfi_diskclass; uint32_t -cfi_read(struct cfi_softc *sc, u_int ofs) +cfi_read_raw(struct cfi_softc *sc, u_int ofs) { uint32_t val; ofs &= ~(sc->sc_width - 1); switch (sc->sc_width) { case 1: val = bus_space_read_1(sc->sc_tag, sc->sc_handle, ofs); break; case 2: val = bus_space_read_2(sc->sc_tag, sc->sc_handle, ofs); break; case 4: val = bus_space_read_4(sc->sc_tag, sc->sc_handle, ofs); break; default: val = ~0; break; } return (val); } +uint32_t +cfi_read(struct cfi_softc *sc, u_int ofs) +{ + uint32_t val; + uint16_t sval; + + ofs &= ~(sc->sc_width - 1); + switch (sc->sc_width) { + case 1: + val = bus_space_read_1(sc->sc_tag, sc->sc_handle, ofs); + break; + case 2: + sval = bus_space_read_2(sc->sc_tag, sc->sc_handle, ofs); + val = le16toh(sval); + break; + case 4: + val = bus_space_read_4(sc->sc_tag, sc->sc_handle, ofs); + val = le32toh(val); + break; + default: + val = ~0; + break; + } + return (val); +} + static void cfi_write(struct cfi_softc *sc, u_int ofs, u_int val) { ofs &= ~(sc->sc_width - 1); switch (sc->sc_width) { case 1: bus_space_write_1(sc->sc_tag, sc->sc_handle, ofs, val); break; case 2: - bus_space_write_2(sc->sc_tag, sc->sc_handle, ofs, val); + bus_space_write_2(sc->sc_tag, sc->sc_handle, ofs, htole16(val)); break; case 4: - bus_space_write_4(sc->sc_tag, sc->sc_handle, ofs, val); + bus_space_write_4(sc->sc_tag, sc->sc_handle, ofs, htole32(val)); break; } } uint8_t cfi_read_qry(struct cfi_softc *sc, u_int ofs) { uint8_t val; cfi_write(sc, CFI_QRY_CMD_ADDR * sc->sc_width, CFI_QRY_CMD_DATA); val = cfi_read(sc, ofs * sc->sc_width); cfi_write(sc, 0, CFI_BCS_READ_ARRAY); return (val); } static void cfi_amd_write(struct cfi_softc *sc, u_int ofs, u_int addr, u_int data) { cfi_write(sc, ofs + AMD_ADDR_START, CFI_AMD_UNLOCK); cfi_write(sc, ofs + AMD_ADDR_ACK, CFI_AMD_UNLOCK_ACK); cfi_write(sc, ofs + addr, data); } static char * cfi_fmtsize(uint32_t sz) { static char buf[8]; static const char *sfx[] = { "", "K", "M", "G" }; int sfxidx; sfxidx = 0; while (sfxidx < 3 && sz > 1023) { sz /= 1024; sfxidx++; } sprintf(buf, "%u%sB", sz, sfx[sfxidx]); return (buf); } int cfi_probe(device_t dev) { char desc[80]; struct cfi_softc *sc; char *vend_str; int error; uint16_t iface, vend; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_rid = 0; sc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_rid, RF_ACTIVE); if (sc->sc_res == NULL) return (ENXIO); sc->sc_tag = rman_get_bustag(sc->sc_res); sc->sc_handle = rman_get_bushandle(sc->sc_res); if (sc->sc_width == 0) { sc->sc_width = 1; while (sc->sc_width <= 4) { if (cfi_read_qry(sc, CFI_QRY_IDENT) == 'Q') break; sc->sc_width <<= 1; } } else if (cfi_read_qry(sc, CFI_QRY_IDENT) != 'Q') { error = ENXIO; goto out; } if (sc->sc_width > 4) { error = ENXIO; goto out; } /* We got a Q. Check if we also have the R and the Y. */ if (cfi_read_qry(sc, CFI_QRY_IDENT + 1) != 'R' || cfi_read_qry(sc, CFI_QRY_IDENT + 2) != 'Y') { error = ENXIO; goto out; } /* Get the vendor and command set. */ vend = cfi_read_qry(sc, CFI_QRY_VEND) | (cfi_read_qry(sc, CFI_QRY_VEND + 1) << 8); sc->sc_cmdset = vend; switch (vend) { case CFI_VEND_AMD_ECS: case CFI_VEND_AMD_SCS: vend_str = "AMD/Fujitsu"; break; case CFI_VEND_INTEL_ECS: vend_str = "Intel/Sharp"; break; case CFI_VEND_INTEL_SCS: vend_str = "Intel"; break; case CFI_VEND_MITSUBISHI_ECS: case CFI_VEND_MITSUBISHI_SCS: vend_str = "Mitsubishi"; break; default: vend_str = "Unknown vendor"; break; } /* Get the device size. */ sc->sc_size = 1U << cfi_read_qry(sc, CFI_QRY_SIZE); /* Sanity-check the I/F */ iface = cfi_read_qry(sc, CFI_QRY_IFACE) | (cfi_read_qry(sc, CFI_QRY_IFACE + 1) << 8); /* * Adding 1 to iface will give us a bit-wise "switch" * that allows us to test for the interface width by * testing a single bit. */ iface++; error = (iface & sc->sc_width) ? 0 : EINVAL; if (error) goto out; snprintf(desc, sizeof(desc), "%s - %s", vend_str, cfi_fmtsize(sc->sc_size)); device_set_desc_copy(dev, desc); out: bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid, sc->sc_res); return (error); } int cfi_attach(device_t dev) { struct cfi_softc *sc; u_int blksz, blocks; u_int r, u; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_rid = 0; sc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_rid, RF_ACTIVE); if (sc->sc_res == NULL) return (ENXIO); sc->sc_tag = rman_get_bustag(sc->sc_res); sc->sc_handle = rman_get_bushandle(sc->sc_res); /* Get time-out values for erase and write. */ sc->sc_write_timeout = 1 << cfi_read_qry(sc, CFI_QRY_TTO_WRITE); sc->sc_erase_timeout = 1 << cfi_read_qry(sc, CFI_QRY_TTO_ERASE); sc->sc_write_timeout *= 1 << cfi_read_qry(sc, CFI_QRY_MTO_WRITE); sc->sc_erase_timeout *= 1 << cfi_read_qry(sc, CFI_QRY_MTO_ERASE); /* Get erase regions. */ sc->sc_regions = cfi_read_qry(sc, CFI_QRY_NREGIONS); sc->sc_region = malloc(sc->sc_regions * sizeof(struct cfi_region), M_TEMP, M_WAITOK | M_ZERO); for (r = 0; r < sc->sc_regions; r++) { blocks = cfi_read_qry(sc, CFI_QRY_REGION(r)) | (cfi_read_qry(sc, CFI_QRY_REGION(r) + 1) << 8); sc->sc_region[r].r_blocks = blocks + 1; blksz = cfi_read_qry(sc, CFI_QRY_REGION(r) + 2) | (cfi_read_qry(sc, CFI_QRY_REGION(r) + 3) << 8); sc->sc_region[r].r_blksz = (blksz == 0) ? 128 : blksz * 256; } /* Reset the device to a default state. */ cfi_write(sc, 0, CFI_BCS_CLEAR_STATUS); if (bootverbose) { device_printf(dev, "["); for (r = 0; r < sc->sc_regions; r++) { printf("%ux%s%s", sc->sc_region[r].r_blocks, cfi_fmtsize(sc->sc_region[r].r_blksz), (r == sc->sc_regions - 1) ? "]\n" : ","); } } u = device_get_unit(dev); sc->sc_nod = make_dev(&cfi_cdevsw, u, UID_ROOT, GID_WHEEL, 0600, "%s%u", cfi_driver_name, u); sc->sc_nod->si_drv1 = sc; device_add_child(dev, "cfid", -1); bus_generic_attach(dev); return (0); } int cfi_detach(device_t dev) { struct cfi_softc *sc; sc = device_get_softc(dev); destroy_dev(sc->sc_nod); free(sc->sc_region, M_TEMP); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid, sc->sc_res); return (0); } static int cfi_wait_ready(struct cfi_softc *sc, u_int ofs, u_int timeout) { int done, error; uint32_t st0 = 0, st = 0; done = 0; error = 0; timeout *= 10; while (!done && !error && timeout) { DELAY(100); timeout--; switch (sc->sc_cmdset) { case CFI_VEND_INTEL_ECS: case CFI_VEND_INTEL_SCS: st = cfi_read(sc, ofs); done = (st & CFI_INTEL_STATUS_WSMS); if (done) { /* NB: bit 0 is reserved */ st &= ~(CFI_INTEL_XSTATUS_RSVD | CFI_INTEL_STATUS_WSMS | CFI_INTEL_STATUS_RSVD); if (st & CFI_INTEL_STATUS_DPS) error = EPERM; else if (st & CFI_INTEL_STATUS_PSLBS) error = EIO; else if (st & CFI_INTEL_STATUS_ECLBS) error = ENXIO; else if (st) error = EACCES; } break; case CFI_VEND_AMD_SCS: case CFI_VEND_AMD_ECS: st0 = cfi_read(sc, ofs); st = cfi_read(sc, ofs); done = ((st & 0x40) == (st0 & 0x40)) ? 1 : 0; break; } } if (!done && !error) error = ETIMEDOUT; if (error) printf("\nerror=%d (st 0x%x st0 0x%x)\n", error, st, st0); return (error); } int cfi_write_block(struct cfi_softc *sc) { union { uint8_t *x8; uint16_t *x16; uint32_t *x32; } ptr; register_t intr; int error, i; /* Erase the block. */ switch (sc->sc_cmdset) { case CFI_VEND_INTEL_ECS: case CFI_VEND_INTEL_SCS: cfi_write(sc, sc->sc_wrofs, CFI_BCS_BLOCK_ERASE); cfi_write(sc, sc->sc_wrofs, CFI_BCS_CONFIRM); break; case CFI_VEND_AMD_SCS: case CFI_VEND_AMD_ECS: cfi_amd_write(sc, sc->sc_wrofs, AMD_ADDR_START, CFI_AMD_ERASE_SECTOR); cfi_amd_write(sc, sc->sc_wrofs, 0, CFI_AMD_BLOCK_ERASE); break; default: /* Better safe than sorry... */ return (ENODEV); } error = cfi_wait_ready(sc, sc->sc_wrofs, sc->sc_erase_timeout); if (error) goto out; /* Write the block. */ ptr.x8 = sc->sc_wrbuf; for (i = 0; i < sc->sc_wrbufsz; i += sc->sc_width) { /* * Make sure the command to start a write and the * actual write happens back-to-back without any * excessive delays. */ intr = intr_disable(); switch (sc->sc_cmdset) { case CFI_VEND_INTEL_ECS: case CFI_VEND_INTEL_SCS: cfi_write(sc, sc->sc_wrofs + i, CFI_BCS_PROGRAM); break; case CFI_VEND_AMD_SCS: case CFI_VEND_AMD_ECS: cfi_amd_write(sc, 0, AMD_ADDR_START, CFI_AMD_PROGRAM); break; } switch (sc->sc_width) { case 1: bus_space_write_1(sc->sc_tag, sc->sc_handle, sc->sc_wrofs + i, *(ptr.x8)++); break; case 2: bus_space_write_2(sc->sc_tag, sc->sc_handle, sc->sc_wrofs + i, *(ptr.x16)++); break; case 4: bus_space_write_4(sc->sc_tag, sc->sc_handle, sc->sc_wrofs + i, *(ptr.x32)++); break; } intr_restore(intr); error = cfi_wait_ready(sc, sc->sc_wrofs, sc->sc_write_timeout); if (error) goto out; } /* error is 0. */ out: cfi_write(sc, 0, CFI_BCS_READ_ARRAY); return (error); } #ifdef CFI_SUPPORT_STRATAFLASH /* * Intel StrataFlash Protection Register Support. * * The memory includes a 128-bit Protection Register that can be * used for security. There are two 64-bit segments; one is programmed * at the factory with a unique 64-bit number which is immutable. * The other segment is left blank for User (OEM) programming. * The User/OEM segment is One Time Programmable (OTP). It can also * be locked to prevent any further writes by setting bit 0 of the * Protection Lock Register (PLR). The PLR can written only once. */ static uint16_t cfi_get16(struct cfi_softc *sc, int off) { uint16_t v = bus_space_read_2(sc->sc_tag, sc->sc_handle, off<<1); return v; } #ifdef CFI_ARMEDANDDANGEROUS static void cfi_put16(struct cfi_softc *sc, int off, uint16_t v) { bus_space_write_2(sc->sc_tag, sc->sc_handle, off<<1, v); } #endif /* * Read the factory-defined 64-bit segment of the PR. */ int cfi_intel_get_factory_pr(struct cfi_softc *sc, uint64_t *id) { if (sc->sc_cmdset != CFI_VEND_INTEL_ECS) return EOPNOTSUPP; KASSERT(sc->sc_width == 2, ("sc_width %d", sc->sc_width)); cfi_write(sc, 0, CFI_INTEL_READ_ID); *id = ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(0)))<<48 | ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(1)))<<32 | ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(2)))<<16 | ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(3))); cfi_write(sc, 0, CFI_BCS_READ_ARRAY); return 0; } /* * Read the User/OEM 64-bit segment of the PR. */ int cfi_intel_get_oem_pr(struct cfi_softc *sc, uint64_t *id) { if (sc->sc_cmdset != CFI_VEND_INTEL_ECS) return EOPNOTSUPP; KASSERT(sc->sc_width == 2, ("sc_width %d", sc->sc_width)); cfi_write(sc, 0, CFI_INTEL_READ_ID); *id = ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(4)))<<48 | ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(5)))<<32 | ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(6)))<<16 | ((uint64_t)cfi_get16(sc, CFI_INTEL_PR(7))); cfi_write(sc, 0, CFI_BCS_READ_ARRAY); return 0; } /* * Write the User/OEM 64-bit segment of the PR. * XXX should allow writing individual words/bytes */ int cfi_intel_set_oem_pr(struct cfi_softc *sc, uint64_t id) { #ifdef CFI_ARMEDANDDANGEROUS register_t intr; int i, error; #endif if (sc->sc_cmdset != CFI_VEND_INTEL_ECS) return EOPNOTSUPP; KASSERT(sc->sc_width == 2, ("sc_width %d", sc->sc_width)); #ifdef CFI_ARMEDANDDANGEROUS for (i = 7; i >= 4; i--, id >>= 16) { intr = intr_disable(); cfi_write(sc, 0, CFI_INTEL_PP_SETUP); cfi_put16(sc, CFI_INTEL_PR(i), id&0xffff); intr_restore(intr); error = cfi_wait_ready(sc, CFI_BCS_READ_STATUS, sc->sc_write_timeout); if (error) break; } cfi_write(sc, 0, CFI_BCS_READ_ARRAY); return error; #else device_printf(sc->sc_dev, "%s: OEM PR not set, " "CFI_ARMEDANDDANGEROUS not configured\n", __func__); return ENXIO; #endif } /* * Read the contents of the Protection Lock Register. */ int cfi_intel_get_plr(struct cfi_softc *sc, uint32_t *plr) { if (sc->sc_cmdset != CFI_VEND_INTEL_ECS) return EOPNOTSUPP; KASSERT(sc->sc_width == 2, ("sc_width %d", sc->sc_width)); cfi_write(sc, 0, CFI_INTEL_READ_ID); *plr = cfi_get16(sc, CFI_INTEL_PLR); cfi_write(sc, 0, CFI_BCS_READ_ARRAY); return 0; } /* * Write the Protection Lock Register to lock down the * user-settable segment of the Protection Register. * NOTE: this operation is not reversible. */ int cfi_intel_set_plr(struct cfi_softc *sc) { #ifdef CFI_ARMEDANDDANGEROUS register_t intr; int error; #endif if (sc->sc_cmdset != CFI_VEND_INTEL_ECS) return EOPNOTSUPP; KASSERT(sc->sc_width == 2, ("sc_width %d", sc->sc_width)); #ifdef CFI_ARMEDANDDANGEROUS /* worthy of console msg */ device_printf(sc->sc_dev, "set PLR\n"); intr = intr_disable(); cfi_write(sc, 0, CFI_INTEL_PP_SETUP); cfi_put16(sc, CFI_INTEL_PLR, 0xFFFD); intr_restore(intr); error = cfi_wait_ready(sc, CFI_BCS_READ_STATUS, sc->sc_write_timeout); cfi_write(sc, 0, CFI_BCS_READ_ARRAY); return error; #else device_printf(sc->sc_dev, "%s: PLR not set, " "CFI_ARMEDANDDANGEROUS not configured\n", __func__); return ENXIO; #endif } #endif /* CFI_SUPPORT_STRATAFLASH */ diff --git a/sys/dev/cfi/cfi_dev.c b/sys/dev/cfi/cfi_dev.c index 9b232bd064ea..d511eacd6611 100644 --- a/sys/dev/cfi/cfi_dev.c +++ b/sys/dev/cfi/cfi_dev.c @@ -1,301 +1,301 @@ /*- * Copyright (c) 2007, Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_cfi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static d_open_t cfi_devopen; static d_close_t cfi_devclose; static d_read_t cfi_devread; static d_write_t cfi_devwrite; static d_ioctl_t cfi_devioctl; struct cdevsw cfi_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_name = cfi_driver_name, .d_open = cfi_devopen, .d_close = cfi_devclose, .d_read = cfi_devread, .d_write = cfi_devwrite, .d_ioctl = cfi_devioctl, }; /* * Begin writing into a new block/sector. We read the sector into * memory and keep updating that, until we move into another sector * or the process stops writing. At that time we write the whole * sector to flash (see cfi_block_finish). */ int cfi_block_start(struct cfi_softc *sc, u_int ofs) { union { uint8_t *x8; uint16_t *x16; uint32_t *x32; } ptr; u_int rofs, rsz; uint32_t val; int r; rofs = 0; for (r = 0; r < sc->sc_regions; r++) { rsz = sc->sc_region[r].r_blocks * sc->sc_region[r].r_blksz; if (ofs < rofs + rsz) break; rofs += rsz; } if (r == sc->sc_regions) return (EFAULT); sc->sc_wrbufsz = sc->sc_region[r].r_blksz; sc->sc_wrbuf = malloc(sc->sc_wrbufsz, M_TEMP, M_WAITOK); sc->sc_wrofs = ofs - (ofs - rofs) % sc->sc_wrbufsz; /* Read the block from flash for byte-serving. */ ptr.x8 = sc->sc_wrbuf; for (r = 0; r < sc->sc_wrbufsz; r += sc->sc_width) { - val = cfi_read(sc, sc->sc_wrofs + r); + val = cfi_read_raw(sc, sc->sc_wrofs + r); switch (sc->sc_width) { case 1: *(ptr.x8)++ = val; break; case 2: *(ptr.x16)++ = val; break; case 4: *(ptr.x32)++ = val; break; } } sc->sc_writing = 1; return (0); } /* * Finish updating the current block/sector by writing the compound * set of changes to the flash. */ int cfi_block_finish(struct cfi_softc *sc) { int error; error = cfi_write_block(sc); free(sc->sc_wrbuf, M_TEMP); sc->sc_wrbuf = NULL; sc->sc_wrbufsz = 0; sc->sc_wrofs = 0; sc->sc_writing = 0; return (error); } static int cfi_devopen(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct cfi_softc *sc; sc = dev->si_drv1; /* We allow only 1 open. */ if (!atomic_cmpset_acq_ptr((uintptr_t *)&sc->sc_opened, (uintptr_t)NULL, (uintptr_t)td->td_proc)) return (EBUSY); return (0); } static int cfi_devclose(struct cdev *dev, int fflag, int devtype, struct thread *td) { struct cfi_softc *sc; int error; sc = dev->si_drv1; /* Sanity. Not really necessary. */ if (sc->sc_opened != td->td_proc) return (ENXIO); error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; sc->sc_opened = NULL; return (error); } static int cfi_devread(struct cdev *dev, struct uio *uio, int ioflag) { union { uint8_t x8[4]; uint16_t x16[2]; uint32_t x32[1]; } buf; struct cfi_softc *sc; u_int ofs; uint32_t val; int error; sc = dev->si_drv1; error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; if (!error) error = (uio->uio_offset > sc->sc_size) ? EIO : 0; while (error == 0 && uio->uio_resid > 0 && uio->uio_offset < sc->sc_size) { ofs = uio->uio_offset; - val = cfi_read(sc, ofs); + val = cfi_read_raw(sc, ofs); switch (sc->sc_width) { case 1: buf.x8[0] = val; break; case 2: buf.x16[0] = val; break; case 4: buf.x32[0] = val; break; } ofs &= sc->sc_width - 1; error = uiomove(buf.x8 + ofs, MIN(uio->uio_resid, sc->sc_width - ofs), uio); } return (error); } static int cfi_devwrite(struct cdev *dev, struct uio *uio, int ioflag) { struct cfi_softc *sc; u_int ofs, top; int error; sc = dev->si_drv1; error = (uio->uio_offset > sc->sc_size) ? EIO : 0; while (error == 0 && uio->uio_resid > 0 && uio->uio_offset < sc->sc_size) { ofs = uio->uio_offset; /* * Finish the current block if we're about to write * to a different block. */ if (sc->sc_writing) { top = sc->sc_wrofs + sc->sc_wrbufsz; if (ofs < sc->sc_wrofs || ofs >= top) cfi_block_finish(sc); } /* Start writing to a (new) block if applicable. */ if (!sc->sc_writing) { error = cfi_block_start(sc, uio->uio_offset); if (error) break; } top = sc->sc_wrofs + sc->sc_wrbufsz; error = uiomove(sc->sc_wrbuf + ofs - sc->sc_wrofs, MIN(top - ofs, uio->uio_resid), uio); } return (error); } static int cfi_devioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct cfi_softc *sc; struct cfiocqry *rq; int error; u_char val; sc = dev->si_drv1; error = 0; switch (cmd) { case CFIOCQRY: if (sc->sc_writing) { error = cfi_block_finish(sc); if (error) break; } rq = (struct cfiocqry *)data; if (rq->offset >= sc->sc_size / sc->sc_width) return (ESPIPE); if (rq->offset + rq->count > sc->sc_size / sc->sc_width) return (ENOSPC); while (!error && rq->count--) { val = cfi_read_qry(sc, rq->offset++); error = copyout(&val, rq->buffer++, 1); } break; #ifdef CFI_SUPPORT_STRATAFLASH case CFIOCGFACTORYPR: error = cfi_intel_get_factory_pr(sc, (uint64_t *)data); break; case CFIOCGOEMPR: error = cfi_intel_get_oem_pr(sc, (uint64_t *)data); break; case CFIOCSOEMPR: error = cfi_intel_set_oem_pr(sc, *(uint64_t *)data); break; case CFIOCGPLR: error = cfi_intel_get_plr(sc, (uint32_t *)data); break; case CFIOCSPLR: error = cfi_intel_set_plr(sc); break; #endif /* CFI_SUPPORT_STRATAFLASH */ default: error = ENOIOCTL; break; } return (error); } diff --git a/sys/dev/cfi/cfi_disk.c b/sys/dev/cfi/cfi_disk.c index 20f422b19d5c..55f78eac41b9 100644 --- a/sys/dev/cfi/cfi_disk.c +++ b/sys/dev/cfi/cfi_disk.c @@ -1,326 +1,326 @@ /*- * Copyright (c) 2009 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct cfi_disk_softc { struct cfi_softc *parent; struct disk *disk; int flags; #define CFI_DISK_OPEN 0x0001 struct bio_queue_head bioq; /* bio queue */ struct mtx qlock; /* bioq lock */ struct taskqueue *tq; /* private task queue for i/o request */ struct task iotask; /* i/o processing */ }; #define CFI_DISK_SECSIZE 512 #define CFI_DISK_MAXIOSIZE 65536 static int cfi_disk_detach(device_t); static int cfi_disk_open(struct disk *); static int cfi_disk_close(struct disk *); static void cfi_io_proc(void *, int); static void cfi_disk_strategy(struct bio *); static int cfi_disk_ioctl(struct disk *, u_long, void *, int, struct thread *); static int cfi_disk_probe(device_t dev) { return 0; } static int cfi_disk_attach(device_t dev) { struct cfi_disk_softc *sc = device_get_softc(dev); sc->parent = device_get_softc(device_get_parent(dev)); /* validate interface width; assumed by other code */ if (sc->parent->sc_width != 1 && sc->parent->sc_width != 2 && sc->parent->sc_width != 4) return EINVAL; sc->disk = disk_alloc(); if (sc->disk == NULL) return ENOMEM; sc->disk->d_name = "cfid"; sc->disk->d_unit = device_get_unit(dev); sc->disk->d_open = cfi_disk_open; sc->disk->d_close = cfi_disk_close; sc->disk->d_strategy = cfi_disk_strategy; sc->disk->d_ioctl = cfi_disk_ioctl; sc->disk->d_dump = NULL; /* NB: no dumps */ sc->disk->d_sectorsize = CFI_DISK_SECSIZE; sc->disk->d_mediasize = sc->parent->sc_size; sc->disk->d_maxsize = CFI_DISK_MAXIOSIZE; /* NB: use stripesize to hold the erase/region size */ if (sc->parent->sc_regions) { /* * Multiple regions, use the last one. This is a * total hack as it's (presently) used only by * geom_redboot to locate the FIS directory which * lies at the start of the last erase region. */ sc->disk->d_stripesize = sc->parent->sc_region[sc->parent->sc_regions-1].r_blksz; } else sc->disk->d_stripesize = sc->disk->d_mediasize; sc->disk->d_drv1 = sc; disk_create(sc->disk, DISK_VERSION); mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF); bioq_init(&sc->bioq); sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT, taskqueue_thread_enqueue, &sc->tq); taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq"); TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc); return 0; } static int cfi_disk_detach(device_t dev) { struct cfi_disk_softc *sc = device_get_softc(dev); if (sc->flags & CFI_DISK_OPEN) return EBUSY; taskqueue_free(sc->tq); /* XXX drain bioq */ disk_destroy(sc->disk); mtx_destroy(&sc->qlock); return 0; } static int cfi_disk_open(struct disk *dp) { struct cfi_disk_softc *sc = dp->d_drv1; /* XXX no interlock with /dev/cfi */ sc->flags |= CFI_DISK_OPEN; return 0; } static int cfi_disk_close(struct disk *dp) { struct cfi_disk_softc *sc = dp->d_drv1; sc->flags &= ~CFI_DISK_OPEN; return 0; } static void cfi_disk_read(struct cfi_softc *sc, struct bio *bp) { long resid; KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, ("sc_width %d", sc->sc_width)); if (sc->sc_writing) { bp->bio_error = cfi_block_finish(sc); if (bp->bio_error) { bp->bio_flags |= BIO_ERROR; goto done; } } if (bp->bio_offset > sc->sc_size) { bp->bio_flags |= BIO_ERROR; bp->bio_error = EIO; goto done; } resid = bp->bio_bcount; if (sc->sc_width == 1) { uint8_t *dp = (uint8_t *)bp->bio_data; while (resid > 0 && bp->bio_offset < sc->sc_size) { - *dp++ = cfi_read(sc, bp->bio_offset); + *dp++ = cfi_read_raw(sc, bp->bio_offset); bp->bio_offset += 1, resid -= 1; } } else if (sc->sc_width == 2) { uint16_t *dp = (uint16_t *)bp->bio_data; while (resid > 0 && bp->bio_offset < sc->sc_size) { - *dp++ = cfi_read(sc, bp->bio_offset); + *dp++ = cfi_read_raw(sc, bp->bio_offset); bp->bio_offset += 2, resid -= 2; } } else { uint32_t *dp = (uint32_t *)bp->bio_data; while (resid > 0 && bp->bio_offset < sc->sc_size) { - *dp++ = cfi_read(sc, bp->bio_offset); + *dp++ = cfi_read_raw(sc, bp->bio_offset); bp->bio_offset += 4, resid -= 4; } } bp->bio_resid = resid; done: biodone(bp); } static void cfi_disk_write(struct cfi_softc *sc, struct bio *bp) { long resid; u_int top; KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4, ("sc_width %d", sc->sc_width)); if (bp->bio_offset > sc->sc_size) { bp->bio_flags |= BIO_ERROR; bp->bio_error = EIO; goto done; } resid = bp->bio_bcount; while (resid > 0) { /* * Finish the current block if we're about to write * to a different block. */ if (sc->sc_writing) { top = sc->sc_wrofs + sc->sc_wrbufsz; if (bp->bio_offset < sc->sc_wrofs || bp->bio_offset >= top) cfi_block_finish(sc); } /* Start writing to a (new) block if applicable. */ if (!sc->sc_writing) { bp->bio_error = cfi_block_start(sc, bp->bio_offset); if (bp->bio_error) { bp->bio_flags |= BIO_ERROR; goto done; } } top = sc->sc_wrofs + sc->sc_wrbufsz; bcopy(bp->bio_data, sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs, MIN(top - bp->bio_offset, resid)); resid -= MIN(top - bp->bio_offset, resid); } bp->bio_resid = resid; done: biodone(bp); } static void cfi_io_proc(void *arg, int pending) { struct cfi_disk_softc *sc = arg; struct cfi_softc *cfi = sc->parent; struct bio *bp; for (;;) { mtx_lock(&sc->qlock); bp = bioq_takefirst(&sc->bioq); mtx_unlock(&sc->qlock); if (bp == NULL) break; switch (bp->bio_cmd) { case BIO_READ: cfi_disk_read(cfi, bp); break; case BIO_WRITE: cfi_disk_write(cfi, bp); break; } } } static void cfi_disk_strategy(struct bio *bp) { struct cfi_disk_softc *sc = bp->bio_disk->d_drv1; if (sc == NULL) goto invalid; if (bp->bio_bcount == 0) { bp->bio_resid = bp->bio_bcount; biodone(bp); return; } switch (bp->bio_cmd) { case BIO_READ: case BIO_WRITE: mtx_lock(&sc->qlock); /* no value in sorting requests? */ bioq_insert_tail(&sc->bioq, bp); mtx_unlock(&sc->qlock); taskqueue_enqueue(sc->tq, &sc->iotask); return; } /* fall thru... */ invalid: bp->bio_flags |= BIO_ERROR; bp->bio_error = EINVAL; biodone(bp); } static int cfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag, struct thread *td) { return EINVAL; } static device_method_t cfi_disk_methods[] = { DEVMETHOD(device_probe, cfi_disk_probe), DEVMETHOD(device_attach, cfi_disk_attach), DEVMETHOD(device_detach, cfi_disk_detach), { 0, 0 } }; static driver_t cfi_disk_driver = { "cfid", cfi_disk_methods, sizeof(struct cfi_disk_softc), }; DRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL); diff --git a/sys/dev/cfi/cfi_var.h b/sys/dev/cfi/cfi_var.h index 4dd9a60ac3aa..15c77692ee36 100644 --- a/sys/dev/cfi/cfi_var.h +++ b/sys/dev/cfi/cfi_var.h @@ -1,87 +1,88 @@ /*- * Copyright (c) 2007, Juniper Networks, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _DEV_CFI_VAR_H_ #define _DEV_CFI_VAR_H_ struct cfi_region { u_int r_blocks; u_int r_blksz; }; struct cfi_softc { device_t sc_dev; struct resource *sc_res; bus_space_handle_t sc_handle; bus_space_tag_t sc_tag; int sc_rid; u_int sc_size; /* Flash size. */ u_int sc_width; /* Interface width. */ u_int sc_regions; /* Erase regions. */ struct cfi_region *sc_region; /* Array of region info. */ u_int sc_cmdset; u_int sc_erase_timeout; u_int sc_write_timeout; struct cdev *sc_nod; struct proc *sc_opened; /* Process that has us opened. */ u_char *sc_wrbuf; u_int sc_wrbufsz; u_int sc_wrofs; u_int sc_writing; }; extern char cfi_driver_name[]; extern devclass_t cfi_devclass; extern devclass_t cfi_diskclass; int cfi_probe(device_t); int cfi_attach(device_t); int cfi_detach(device_t); +uint32_t cfi_read_raw(struct cfi_softc *, u_int); uint32_t cfi_read(struct cfi_softc *, u_int); uint8_t cfi_read_qry(struct cfi_softc *, u_int); int cfi_write_block(struct cfi_softc *); int cfi_block_start(struct cfi_softc *, u_int); int cfi_block_finish(struct cfi_softc *); #ifdef CFI_SUPPORT_STRATAFLASH int cfi_intel_get_factory_pr(struct cfi_softc *sc, uint64_t *); int cfi_intel_get_oem_pr(struct cfi_softc *sc, uint64_t *); int cfi_intel_set_oem_pr(struct cfi_softc *sc, uint64_t); int cfi_intel_get_plr(struct cfi_softc *sc, uint32_t *); int cfi_intel_set_plr(struct cfi_softc *sc); #endif /* CFI_SUPPORT_STRATAFLASH */ #endif /* _DEV_CFI_VAR_H_ */