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(&sc->sc_opened, NULL, td->td_proc)) 149 return (EBUSY); 150 return (0); 151 } 152 153 static int 154 cfi_devclose(struct cdev *dev, int fflag, int devtype, struct thread *td) 155 { 156 struct cfi_softc *sc; 157 int error; 158 159 sc = dev->si_drv1; 160 /* Sanity. Not really necessary. */ 161 if (sc->sc_opened != td->td_proc) 162 return (ENXIO); 163 164 error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; 165 sc->sc_opened = NULL; 166 return (error); 167 } 168 169 static int 170 cfi_devread(struct cdev *dev, struct uio *uio, int ioflag) 171 { 172 union { 173 uint8_t x8[4]; 174 uint16_t x16[2]; 175 uint32_t x32[1]; 176 } buf; 177 struct cfi_softc *sc; 178 u_int ofs; 179 uint32_t val; 180 int error; 181 182 sc = dev->si_drv1; 183 184 error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; 185 if (!error) 186 error = (uio->uio_offset > sc->sc_size) ? EIO : 0; 187 188 while (error == 0 && uio->uio_resid > 0 && 189 uio->uio_offset < sc->sc_size) { 190 ofs = uio->uio_offset; 191 val = cfi_read(sc, ofs); 192 switch (sc->sc_width) { 193 case 1: 194 buf.x8[0] = val; 195 break; 196 case 2: 197 buf.x16[0] = val; 198 break; 199 case 4: 200 buf.x32[0] = val; 201 break; 202 } 203 ofs &= sc->sc_width - 1; 204 error = uiomove(buf.x8 + ofs, 205 MIN(uio->uio_resid, sc->sc_width - ofs), uio); 206 } 207 return (error); 208 } 209 210 static int 211 cfi_devwrite(struct cdev *dev, struct uio *uio, int ioflag) 212 { 213 struct cfi_softc *sc; 214 u_int ofs, top; 215 int error; 216 217 sc = dev->si_drv1; 218 219 error = (uio->uio_offset > sc->sc_size) ? EIO : 0; 220 while (error == 0 && uio->uio_resid > 0 && 221 uio->uio_offset < sc->sc_size) { 222 ofs = uio->uio_offset; 223 224 /* 225 * Finish the current block if we're about to write 226 * to a different block. 227 */ 228 if (sc->sc_writing) { 229 top = sc->sc_wrofs + sc->sc_wrbufsz; 230 if (ofs < sc->sc_wrofs || ofs >= top) 231 cfi_block_finish(sc); 232 } 233 234 /* Start writing to a (new) block if applicable. */ 235 if (!sc->sc_writing) { 236 error = cfi_block_start(sc, uio->uio_offset); 237 if (error) 238 break; 239 } 240 241 top = sc->sc_wrofs + sc->sc_wrbufsz; 242 error = uiomove(sc->sc_wrbuf + ofs - sc->sc_wrofs, 243 MIN(top - ofs, uio->uio_resid), uio); 244 } 245 return (error); 246 } 247 248 static int 249 cfi_devioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, 250 struct thread *td) 251 { 252 struct cfi_softc *sc; 253 struct cfiocqry *rq; 254 int error; 255 u_char val; 256 257 sc = dev->si_drv1; 258 error = 0; 259 260 switch (cmd) { 261 case CFIOCQRY: 262 if (sc->sc_writing) { 263 error = cfi_block_finish(sc); 264 if (error) 265 break; 266 } 267 rq = (struct cfiocqry *)data; 268 if (rq->offset >= sc->sc_size / sc->sc_width) 269 return (ESPIPE); 270 if (rq->offset + rq->count > sc->sc_size / sc->sc_width) 271 return (ENOSPC); 272 273 while (!error && rq->count--) { 274 val = cfi_read_qry(sc, rq->offset++); 275 error = copyout(&val, rq->buffer++, 1); 276 } 277 break; 278 #ifdef CFI_SUPPORT_STRATAFLASH 279 case CFIOCGFACTORYPR: 280 error = cfi_intel_get_factory_pr(sc, (uint64_t *)data); 281 break; 282 case CFIOCGOEMPR: 283 error = cfi_intel_get_oem_pr(sc, (uint64_t *)data); 284 break; 285 case CFIOCSOEMPR: 286 error = cfi_intel_set_oem_pr(sc, *(uint64_t *)data); 287 break; 288 case CFIOCGPLR: 289 error = cfi_intel_get_plr(sc, (uint32_t *)data); 290 break; 291 case CFIOCSPLR: 292 error = cfi_intel_set_plr(sc); 293 break; 294 #endif /* CFI_SUPPORT_STRATAFLASH */ 295 default: 296 error = ENOIOCTL; 297 break; 298 } 299 return (error); 300 } 301