1 /*- 2 * Copyright (c) 2007, Juniper Networks, Inc. 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 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of the author nor the names of any co-contributors 14 * may be used to endorse or promote products derived from this software 15 * without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 24 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #include "opt_cfi.h" 34 35 #include <sys/param.h> 36 #include <sys/systm.h> 37 #include <sys/bus.h> 38 #include <sys/conf.h> 39 #include <sys/ioccom.h> 40 #include <sys/kernel.h> 41 #include <sys/malloc.h> 42 #include <sys/proc.h> 43 #include <sys/sysctl.h> 44 #include <sys/types.h> 45 #include <sys/uio.h> 46 47 #include <sys/cfictl.h> 48 49 #include <machine/atomic.h> 50 #include <machine/bus.h> 51 52 #include <dev/cfi/cfi_var.h> 53 54 static d_open_t cfi_devopen; 55 static d_close_t cfi_devclose; 56 static d_read_t cfi_devread; 57 static d_write_t cfi_devwrite; 58 static d_ioctl_t cfi_devioctl; 59 60 struct cdevsw cfi_cdevsw = { 61 .d_version = D_VERSION, 62 .d_flags = 0, 63 .d_name = cfi_driver_name, 64 .d_open = cfi_devopen, 65 .d_close = cfi_devclose, 66 .d_read = cfi_devread, 67 .d_write = cfi_devwrite, 68 .d_ioctl = cfi_devioctl, 69 }; 70 71 /* 72 * Begin writing into a new block/sector. We read the sector into 73 * memory and keep updating that, until we move into another sector 74 * or the process stops writing. At that time we write the whole 75 * sector to flash (see cfi_block_finish). 76 */ 77 int 78 cfi_block_start(struct cfi_softc *sc, u_int ofs) 79 { 80 union { 81 uint8_t *x8; 82 uint16_t *x16; 83 uint32_t *x32; 84 } ptr; 85 u_int rofs, rsz; 86 uint32_t val; 87 int r; 88 89 rofs = 0; 90 for (r = 0; r < sc->sc_regions; r++) { 91 rsz = sc->sc_region[r].r_blocks * sc->sc_region[r].r_blksz; 92 if (ofs < rofs + rsz) 93 break; 94 rofs += rsz; 95 } 96 if (r == sc->sc_regions) 97 return (EFAULT); 98 99 sc->sc_wrbufsz = sc->sc_region[r].r_blksz; 100 sc->sc_wrbuf = malloc(sc->sc_wrbufsz, M_TEMP, M_WAITOK); 101 sc->sc_wrofs = ofs - (ofs - rofs) % sc->sc_wrbufsz; 102 103 /* Read the block from flash for byte-serving. */ 104 ptr.x8 = sc->sc_wrbuf; 105 for (r = 0; r < sc->sc_wrbufsz; r += sc->sc_width) { 106 val = cfi_read(sc, sc->sc_wrofs + r); 107 switch (sc->sc_width) { 108 case 1: 109 *(ptr.x8)++ = val; 110 break; 111 case 2: 112 *(ptr.x16)++ = val; 113 break; 114 case 4: 115 *(ptr.x32)++ = val; 116 break; 117 } 118 } 119 sc->sc_writing = 1; 120 return (0); 121 } 122 123 /* 124 * Finish updating the current block/sector by writing the compound 125 * set of changes to the flash. 126 */ 127 int 128 cfi_block_finish(struct cfi_softc *sc) 129 { 130 int error; 131 132 error = cfi_write_block(sc); 133 free(sc->sc_wrbuf, M_TEMP); 134 sc->sc_wrbuf = NULL; 135 sc->sc_wrbufsz = 0; 136 sc->sc_wrofs = 0; 137 sc->sc_writing = 0; 138 return (error); 139 } 140 141 static int 142 cfi_devopen(struct cdev *dev, int oflags, int devtype, struct thread *td) 143 { 144 struct cfi_softc *sc; 145 146 sc = dev->si_drv1; 147 /* We allow only 1 open. */ 148 if (!atomic_cmpset_acq_ptr((uintptr_t *)&sc->sc_opened, 149 (uintptr_t)NULL, (uintptr_t)td->td_proc)) 150 return (EBUSY); 151 return (0); 152 } 153 154 static int 155 cfi_devclose(struct cdev *dev, int fflag, int devtype, struct thread *td) 156 { 157 struct cfi_softc *sc; 158 int error; 159 160 sc = dev->si_drv1; 161 /* Sanity. Not really necessary. */ 162 if (sc->sc_opened != td->td_proc) 163 return (ENXIO); 164 165 error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; 166 sc->sc_opened = NULL; 167 return (error); 168 } 169 170 static int 171 cfi_devread(struct cdev *dev, struct uio *uio, int ioflag) 172 { 173 union { 174 uint8_t x8[4]; 175 uint16_t x16[2]; 176 uint32_t x32[1]; 177 } buf; 178 struct cfi_softc *sc; 179 u_int ofs; 180 uint32_t val; 181 int error; 182 183 sc = dev->si_drv1; 184 185 error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; 186 if (!error) 187 error = (uio->uio_offset > sc->sc_size) ? EIO : 0; 188 189 while (error == 0 && uio->uio_resid > 0 && 190 uio->uio_offset < sc->sc_size) { 191 ofs = uio->uio_offset; 192 val = cfi_read(sc, ofs); 193 switch (sc->sc_width) { 194 case 1: 195 buf.x8[0] = val; 196 break; 197 case 2: 198 buf.x16[0] = val; 199 break; 200 case 4: 201 buf.x32[0] = val; 202 break; 203 } 204 ofs &= sc->sc_width - 1; 205 error = uiomove(buf.x8 + ofs, 206 MIN(uio->uio_resid, sc->sc_width - ofs), uio); 207 } 208 return (error); 209 } 210 211 static int 212 cfi_devwrite(struct cdev *dev, struct uio *uio, int ioflag) 213 { 214 struct cfi_softc *sc; 215 u_int ofs, top; 216 int error; 217 218 sc = dev->si_drv1; 219 220 error = (uio->uio_offset > sc->sc_size) ? EIO : 0; 221 while (error == 0 && uio->uio_resid > 0 && 222 uio->uio_offset < sc->sc_size) { 223 ofs = uio->uio_offset; 224 225 /* 226 * Finish the current block if we're about to write 227 * to a different block. 228 */ 229 if (sc->sc_writing) { 230 top = sc->sc_wrofs + sc->sc_wrbufsz; 231 if (ofs < sc->sc_wrofs || ofs >= top) 232 cfi_block_finish(sc); 233 } 234 235 /* Start writing to a (new) block if applicable. */ 236 if (!sc->sc_writing) { 237 error = cfi_block_start(sc, uio->uio_offset); 238 if (error) 239 break; 240 } 241 242 top = sc->sc_wrofs + sc->sc_wrbufsz; 243 error = uiomove(sc->sc_wrbuf + ofs - sc->sc_wrofs, 244 MIN(top - ofs, uio->uio_resid), uio); 245 } 246 return (error); 247 } 248 249 static int 250 cfi_devioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, 251 struct thread *td) 252 { 253 struct cfi_softc *sc; 254 struct cfiocqry *rq; 255 int error; 256 u_char val; 257 258 sc = dev->si_drv1; 259 error = 0; 260 261 switch (cmd) { 262 case CFIOCQRY: 263 if (sc->sc_writing) { 264 error = cfi_block_finish(sc); 265 if (error) 266 break; 267 } 268 rq = (struct cfiocqry *)data; 269 if (rq->offset >= sc->sc_size / sc->sc_width) 270 return (ESPIPE); 271 if (rq->offset + rq->count > sc->sc_size / sc->sc_width) 272 return (ENOSPC); 273 274 while (!error && rq->count--) { 275 val = cfi_read_qry(sc, rq->offset++); 276 error = copyout(&val, rq->buffer++, 1); 277 } 278 break; 279 #ifdef CFI_SUPPORT_STRATAFLASH 280 case CFIOCGFACTORYPR: 281 error = cfi_intel_get_factory_pr(sc, (uint64_t *)data); 282 break; 283 case CFIOCGOEMPR: 284 error = cfi_intel_get_oem_pr(sc, (uint64_t *)data); 285 break; 286 case CFIOCSOEMPR: 287 error = cfi_intel_set_oem_pr(sc, *(uint64_t *)data); 288 break; 289 case CFIOCGPLR: 290 error = cfi_intel_get_plr(sc, (uint32_t *)data); 291 break; 292 case CFIOCSPLR: 293 error = cfi_intel_set_plr(sc); 294 break; 295 #endif /* CFI_SUPPORT_STRATAFLASH */ 296 default: 297 error = ENOIOCTL; 298 break; 299 } 300 return (error); 301 } 302