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 <sys/param.h> 34 #include <sys/systm.h> 35 #include <sys/bus.h> 36 #include <sys/conf.h> 37 #include <sys/ioccom.h> 38 #include <sys/kernel.h> 39 #include <sys/malloc.h> 40 #include <sys/proc.h> 41 #include <sys/sysctl.h> 42 #include <sys/types.h> 43 #include <sys/uio.h> 44 45 #include <sys/cfictl.h> 46 47 #include <machine/atomic.h> 48 #include <machine/bus.h> 49 50 #include <dev/cfi/cfi_var.h> 51 52 static d_open_t cfi_devopen; 53 static d_close_t cfi_devclose; 54 static d_read_t cfi_devread; 55 static d_write_t cfi_devwrite; 56 static d_ioctl_t cfi_devioctl; 57 58 struct cdevsw cfi_cdevsw = { 59 .d_version = D_VERSION, 60 .d_flags = 0, 61 .d_name = cfi_driver_name, 62 .d_open = cfi_devopen, 63 .d_close = cfi_devclose, 64 .d_read = cfi_devread, 65 .d_write = cfi_devwrite, 66 .d_ioctl = cfi_devioctl, 67 }; 68 69 /* 70 * Begin writing into a new block/sector. We read the sector into 71 * memory and keep updating that, until we move into another sector 72 * or the process stops writing. At that time we write the whole 73 * sector to flash (see cfi_block_finish). 74 */ 75 static int 76 cfi_block_start(struct cfi_softc *sc, u_int ofs) 77 { 78 union { 79 uint8_t *x8; 80 uint16_t *x16; 81 uint32_t *x32; 82 } ptr; 83 u_int rofs, rsz; 84 uint32_t val; 85 int r; 86 87 rofs = 0; 88 for (r = 0; r < sc->sc_regions; r++) { 89 rsz = sc->sc_region[r].r_blocks * sc->sc_region[r].r_blksz; 90 if (ofs < rofs + rsz) 91 break; 92 rofs += rsz; 93 } 94 if (r == sc->sc_regions) 95 return (EFAULT); 96 97 sc->sc_wrbufsz = sc->sc_region[r].r_blksz; 98 sc->sc_wrbuf = malloc(sc->sc_wrbufsz, M_TEMP, M_WAITOK); 99 sc->sc_wrofs = ofs - (ofs - rofs) % sc->sc_wrbufsz; 100 101 /* Read the block from flash for byte-serving. */ 102 ptr.x8 = sc->sc_wrbuf; 103 for (r = 0; r < sc->sc_wrbufsz; r += sc->sc_width) { 104 val = cfi_read(sc, sc->sc_wrofs + r); 105 switch (sc->sc_width) { 106 case 1: 107 *(ptr.x8)++ = val; 108 break; 109 case 2: 110 *(ptr.x16)++ = val; 111 break; 112 case 4: 113 *(ptr.x32)++ = val; 114 break; 115 } 116 } 117 sc->sc_writing = 1; 118 return (0); 119 } 120 121 /* 122 * Finish updating the current block/sector by writing the compound 123 * set of changes to the flash. 124 */ 125 static int 126 cfi_block_finish(struct cfi_softc *sc) 127 { 128 int error; 129 130 error = cfi_write_block(sc); 131 free(sc->sc_wrbuf, M_TEMP); 132 sc->sc_wrbuf = NULL; 133 sc->sc_wrbufsz = 0; 134 sc->sc_wrofs = 0; 135 sc->sc_writing = 0; 136 return (error); 137 } 138 139 static int 140 cfi_devopen(struct cdev *dev, int oflags, int devtype, struct thread *td) 141 { 142 struct cfi_softc *sc; 143 144 sc = dev->si_drv1; 145 /* We allow only 1 open. */ 146 if (!atomic_cmpset_acq_ptr(&sc->sc_opened, NULL, td->td_proc)) 147 return (EBUSY); 148 return (0); 149 } 150 151 static int 152 cfi_devclose(struct cdev *dev, int fflag, int devtype, struct thread *td) 153 { 154 struct cfi_softc *sc; 155 int error; 156 157 sc = dev->si_drv1; 158 /* Sanity. Not really necessary. */ 159 if (sc->sc_opened != td->td_proc) 160 return (ENXIO); 161 162 error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; 163 sc->sc_opened = NULL; 164 return (error); 165 } 166 167 static int 168 cfi_devread(struct cdev *dev, struct uio *uio, int ioflag) 169 { 170 union { 171 uint8_t x8[4]; 172 uint16_t x16[2]; 173 uint32_t x32[1]; 174 } buf; 175 struct cfi_softc *sc; 176 u_int ofs; 177 uint32_t val; 178 int error; 179 180 sc = dev->si_drv1; 181 182 error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; 183 if (!error) 184 error = (uio->uio_offset > sc->sc_size) ? EIO : 0; 185 186 while (error == 0 && uio->uio_resid > 0 && 187 uio->uio_offset < sc->sc_size) { 188 ofs = uio->uio_offset; 189 val = cfi_read(sc, ofs); 190 switch (sc->sc_width) { 191 case 1: 192 buf.x8[0] = val; 193 break; 194 case 2: 195 buf.x16[0] = val; 196 break; 197 case 4: 198 buf.x32[0] = val; 199 break; 200 } 201 ofs &= sc->sc_width - 1; 202 error = uiomove(buf.x8 + ofs, 203 MIN(uio->uio_resid, sc->sc_width - ofs), uio); 204 } 205 return (error); 206 } 207 208 static int 209 cfi_devwrite(struct cdev *dev, struct uio *uio, int ioflag) 210 { 211 struct cfi_softc *sc; 212 u_int ofs, top; 213 int error; 214 215 sc = dev->si_drv1; 216 217 error = (uio->uio_offset > sc->sc_size) ? EIO : 0; 218 while (error == 0 && uio->uio_resid > 0 && 219 uio->uio_offset < sc->sc_size) { 220 ofs = uio->uio_offset; 221 222 /* 223 * Finish the current block if we're about to write 224 * to a different block. 225 */ 226 if (sc->sc_writing) { 227 top = sc->sc_wrofs + sc->sc_wrbufsz; 228 if (ofs < sc->sc_wrofs || ofs >= top) 229 cfi_block_finish(sc); 230 } 231 232 /* Start writing to a (new) block if applicable. */ 233 if (!sc->sc_writing) { 234 error = cfi_block_start(sc, uio->uio_offset); 235 if (error) 236 break; 237 } 238 239 top = sc->sc_wrofs + sc->sc_wrbufsz; 240 error = uiomove(sc->sc_wrbuf + ofs - sc->sc_wrofs, 241 MIN(top - ofs, uio->uio_resid), uio); 242 } 243 return (error); 244 } 245 246 static int 247 cfi_devioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, 248 struct thread *td) 249 { 250 struct cfi_softc *sc; 251 struct cfiocqry *rq; 252 int error; 253 u_char val; 254 255 if (cmd != CFIOCQRY) 256 return (ENOIOCTL); 257 258 sc = dev->si_drv1; 259 260 error = (sc->sc_writing) ? cfi_block_finish(sc) : 0; 261 if (error) 262 return (error); 263 264 rq = (struct cfiocqry *)data; 265 266 if (rq->offset >= sc->sc_size / sc->sc_width) 267 return (ESPIPE); 268 if (rq->offset + rq->count > sc->sc_size / sc->sc_width) 269 return (ENOSPC); 270 271 while (!error && rq->count--) { 272 val = cfi_read_qry(sc, rq->offset++); 273 error = copyout(&val, rq->buffer++, 1); 274 } 275 276 return (error); 277 } 278