/*1* Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 20092* The President and Fellows of Harvard College.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12* 3. Neither the name of the University nor the names of its contributors13* may be used to endorse or promote products derived from this software14* without specific prior written permission.15*16* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*/2829/*30* LAMEbus hard disk (lhd) driver.31*/3233#include <types.h>34#include <kern/errno.h>35#include <lib.h>36#include <uio.h>37#include <synch.h>38#include <platform/bus.h>39#include <vfs.h>40#include <lamebus/lhd.h>41#include "autoconf.h"4243/* Registers (offsets within slot) */44#define LHD_REG_NSECT 0 /* Number of sectors */45#define LHD_REG_STAT 4 /* Status */46#define LHD_REG_SECT 8 /* Sector for I/O */47#define LHD_REG_RPM 12 /* Disk rotation speed (revs per minute) */4849/* Status codes */50#define LHD_IDLE 0 /* Device idle */51#define LHD_WORKING 1 /* Operation in progress */52#define LHD_OK 4 /* Operation succeeded */53#define LHD_INVSECT 12 /* Invalid sector requested */54#define LHD_MEDIA 20 /* Media error */55#define LHD_ISWRITE 2 /* OR with above: I/O is a write */56#define LHD_STATEMASK 0x1d /* mask for masking out LHD_ISWRITE */5758/* Buffer (offset within slot) */59#define LHD_BUFFER 327686061/*62* Shortcut for reading a register.63*/64static65inline66uint32_t lhd_rdreg(struct lhd_softc *lh, uint32_t reg)67{68return bus_read_register(lh->lh_busdata, lh->lh_buspos, reg);69}7071/*72* Shortcut for writing a register.73*/74static75inline76void lhd_wreg(struct lhd_softc *lh, uint32_t reg, uint32_t val)77{78bus_write_register(lh->lh_busdata, lh->lh_buspos, reg, val);79}8081/*82* Convert a result code from the hardware to an errno value.83*/84static85int lhd_code_to_errno(struct lhd_softc *lh, int code)86{87switch (code & LHD_STATEMASK) {88case LHD_OK: return 0;89case LHD_INVSECT: return EINVAL;90case LHD_MEDIA: return EIO;91}92kprintf("lhd%d: Unknown result code %d\n", lh->lh_unit, code);93return EAGAIN;94}9596/*97* Record that an I/O has completed: save the result and poke the98* completion semaphore.99*/100static101void102lhd_iodone(struct lhd_softc *lh, int err)103{104lh->lh_result = err;105V(lh->lh_done);106}107108/*109* Interrupt handler for lhd.110* Read the status register; if an operation finished, clear the status111* register and report completion.112*/113void114lhd_irq(void *vlh)115{116struct lhd_softc *lh = vlh;117uint32_t val;118119val = lhd_rdreg(lh, LHD_REG_STAT);120121switch (val & LHD_STATEMASK) {122case LHD_IDLE:123case LHD_WORKING:124break;125case LHD_OK:126case LHD_INVSECT:127case LHD_MEDIA:128lhd_wreg(lh, LHD_REG_STAT, 0);129lhd_iodone(lh, lhd_code_to_errno(lh, val));130break;131}132}133134/*135* Function called when we are open()'d.136*/137static138int139lhd_open(struct device *d, int openflags)140{141/*142* Don't need to do anything.143*/144(void)d;145(void)openflags;146147return 0;148}149150/*151* Function called when we are close()'d.152*/153static154int155lhd_close(struct device *d)156{157/*158* Don't need to do anything.159*/160(void)d;161162return 0;163}164165/*166* Function for handling ioctls.167*/168static169int170lhd_ioctl(struct device *d, int op, userptr_t data)171{172/*173* We don't support any ioctls.174*/175(void)d;176(void)op;177(void)data;178return EIOCTL;179}180181#if 0182/*183* Reset the device.184* This could be used, for instance, on timeout, if you implement suitable185* facilities.186*/187static188void189lhd_reset(struct lhd_softc *lh)190{191lhd_wreg(lh, LHD_REG_STAT, 0);192}193#endif194195/*196* I/O function (for both reads and writes)197*/198static199int200lhd_io(struct device *d, struct uio *uio)201{202struct lhd_softc *lh = d->d_data;203204uint32_t sector = uio->uio_offset / LHD_SECTSIZE;205uint32_t sectoff = uio->uio_offset % LHD_SECTSIZE;206uint32_t len = uio->uio_resid / LHD_SECTSIZE;207uint32_t lenoff = uio->uio_resid % LHD_SECTSIZE;208uint32_t i;209uint32_t statval = LHD_WORKING;210int result;211212/* Don't allow I/O that isn't sector-aligned. */213if (sectoff != 0 || lenoff != 0) {214return EINVAL;215}216217/* Don't allow I/O past the end of the disk. */218if (sector+len > lh->lh_dev.d_blocks) {219return EINVAL;220}221222/* Set up the value to write into the status register. */223if (uio->uio_rw==UIO_WRITE) {224statval |= LHD_ISWRITE;225}226227/* Loop over all the sectors we were asked to do. */228for (i=0; i<len; i++) {229230/* Wait until nobody else is using the device. */231P(lh->lh_clear);232233/*234* Are we writing? If so, transfer the data to the235* on-card buffer.236*/237if (uio->uio_rw == UIO_WRITE) {238result = uiomove(lh->lh_buf, LHD_SECTSIZE, uio);239if (result) {240V(lh->lh_clear);241return result;242}243}244245/* Tell it what sector we want... */246lhd_wreg(lh, LHD_REG_SECT, sector+i);247248/* and start the operation. */249lhd_wreg(lh, LHD_REG_STAT, statval);250251/* Now wait until the interrupt handler tells us we're done. */252P(lh->lh_done);253254/* Get the result value saved by the interrupt handler. */255result = lh->lh_result;256257/*258* Are we reading? If so, and if we succeeded,259* transfer the data out of the on-card buffer.260*/261if (result==0 && uio->uio_rw==UIO_READ) {262result = uiomove(lh->lh_buf, LHD_SECTSIZE, uio);263}264265/* Tell another thread it's cleared to go ahead. */266V(lh->lh_clear);267268/* If we failed, return the error. */269if (result) {270return result;271}272}273274return 0;275}276277/*278* Setup routine called by autoconf.c when an lhd is found.279*/280int281config_lhd(struct lhd_softc *lh, int lhdno)282{283char name[32];284285/* Figure out what our name is. */286snprintf(name, sizeof(name), "lhd%d", lhdno);287288/* Get a pointer to the on-chip buffer. */289lh->lh_buf = bus_map_area(lh->lh_busdata, lh->lh_buspos, LHD_BUFFER);290291/* Create the semaphores. */292lh->lh_clear = sem_create("lhd-clear", 1);293if (lh->lh_clear == NULL) {294return ENOMEM;295}296lh->lh_done = sem_create("lhd-done", 0);297if (lh->lh_done == NULL) {298sem_destroy(lh->lh_clear);299lh->lh_clear = NULL;300return ENOMEM;301}302303/* Set up the VFS device structure. */304lh->lh_dev.d_open = lhd_open;305lh->lh_dev.d_close = lhd_close;306lh->lh_dev.d_io = lhd_io;307lh->lh_dev.d_ioctl = lhd_ioctl;308lh->lh_dev.d_blocks = bus_read_register(lh->lh_busdata, lh->lh_buspos,309LHD_REG_NSECT);310lh->lh_dev.d_blocksize = LHD_SECTSIZE;311lh->lh_dev.d_data = lh;312313/* Add the VFS device structure to the VFS device list. */314return vfs_adddev(name, &lh->lh_dev, 1);315}316317318