cfi_dev.c revision 188086
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: head/sys/dev/cfi/cfi_dev.c 188086 2009-02-03 19:07:41Z sam $"); 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 52static d_open_t cfi_devopen; 53static d_close_t cfi_devclose; 54static d_read_t cfi_devread; 55static d_write_t cfi_devwrite; 56static d_ioctl_t cfi_devioctl; 57 58struct 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 */ 75static int 76cfi_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 */ 125static int 126cfi_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 139static int 140cfi_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 151static int 152cfi_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 167static int 168cfi_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 208static int 209cfi_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 246static int 247cfi_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 sc = dev->si_drv1; 256 error = 0; 257 258 switch(cmd) { 259 case CFIOCQRY: 260 if (sc->sc_writing) { 261 error = cfi_block_finish(sc); 262 if (error) 263 break; 264 } 265 266 rq = (struct cfiocqry *)data; 267 if (rq->offset >= sc->sc_size / sc->sc_width) 268 return (ESPIPE); 269 if (rq->offset + rq->count > sc->sc_size / sc->sc_width) 270 return (ENOSPC); 271 272 while (!error && rq->count--) { 273 val = cfi_read_qry(sc, rq->offset++); 274 error = copyout(&val, rq->buffer++, 1); 275 } 276 break; 277 default: 278 error = ENOIOCTL; 279 break; 280 } 281 return (error); 282} 283