Mercurial > vordog
view vordog.c @ 12:6bfd60bcd7b8
disable timer when shuting down
author | Thinker K.F. Li <thinker@branda.to> |
---|---|
date | Wed, 09 Jul 2008 04:40:09 +0800 |
parents | 3968bbb5303d |
children | e4e6727f530f |
line wrap: on
line source
/*- * Copyright (c) 2008 Kueifong Li <thinker@branda.to>. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <sys/param.h> #include <sys/bus.h> #include <sys/conf.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/proc.h> #include <sys/ioccom.h> #include <sys/types.h> #include <sys/malloc.h> #include <sys/watchdog.h> #include "vordog.h" #if 0 #undef __FreeBSD_version #define __FreeBSD_version 800029 #endif #define VD_STATUS 0x841 #define VD_INITVAL 0x84a #define VD_CTR 0x84b /* trigger PCIRST */ #define VD_TIMER_RST 0xc typedef struct vd_softc { struct cdev *_cdev; device_t dev; int open_cnt; u_int init_val; eventhandler_tag wd9_tag; } *vd_softc_t; #define CDEV_2_SOFTC(_cdev) ((_cdev)->si_drv1) static int vordog_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { vd_softc_t sc; sc = CDEV_2_SOFTC(dev); sc->open_cnt++; return 0; } static int vordog_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { vd_softc_t sc; sc = CDEV_2_SOFTC(dev); sc->open_cnt--; return 0; } static int vordog_go_detach(void) { devclass_t vd_dc; device_t vd_dev, nexus_dev; int r; vd_dc = devclass_find("vordog"); if(vd_dc == NULL) return EINVAL; vd_dev = devclass_get_device(vd_dc, 0); if(vd_dev == NULL) return EINVAL; nexus_dev = device_get_parent(vd_dev); if(nexus_dev == NULL) return EINVAL; r = device_delete_child(nexus_dev, vd_dev); return r; } static void setup_timer(vordog_cfg_t cfg) { int val; val = cfg->init_val & 0xff; outb(VD_INITVAL, val); val = 0x80 | ((cfg->unit & 0x3) << 4) | VD_TIMER_RST; outb(VD_CTR, val); } static void reset_timer(void) { outb(VD_STATUS, 0xc0); } static void disable_timer(void) { outb(VD_CTR, 0); outb(VD_STATUS, 0xc0); } static int vordog_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { int r = 0; vd_softc_t sc; vordog_cfg_t cfg; static const u_int unit_factors[] = {0, 1, 60, 360}; sc = CDEV_2_SOFTC(dev); switch(cmd) { case VDCTL_ENABLE: cfg = (vordog_cfg_t)data; sc->init_val = (cfg->init_val & 0xff) * unit_factors[cfg->unit]; setup_timer(cfg); break; case VDCTL_RESET: reset_timer(); break; case VDCTL_DISABLE: disable_timer(); break; default: r = EINVAL; } return r; } struct cdevsw vordog_cdevsw = { .d_version = D_VERSION, .d_open = vordog_open, .d_close = vordog_close, .d_ioctl = vordog_ioctl, .d_name = "vordog", }; /* * Implements watchdog(9) compatible driver. */ /*! \brief Initialize timer for an interval in specified seconds. */ static int vordog_wd9_init(u_int timeout) { int val; if(timeout < 0 || timeout > 255) return EINVAL; outb(VD_INITVAL, timeout & 0xff); val = 0x80 | (VDT_1S << 4) | VD_TIMER_RST; outb(VD_CTR, val); return 0; } /*! \brief Reset timer before it is timeout. * * It make the timer reload it's value from initial value register. */ static void vordog_wd9_reset(void) { outb(VD_STATUS, 0xc0); } /*! \brief Stop counting of watchdog timer. */ static void vordog_wd9_disable(void) { outb(VD_CTR, 0); outb(VD_STATUS, 0xc0); } /*! \brief event handler for watchdog(9). * * Timeout interval of watchdog(9) is defined by power of 2 nanoseconds. * Watchdog timer of Vortex86 provides only small range of intervals. * We use only the timer in seconds to simplize the code. It provides * interval 1~255 seconds. * * Since, only SFTMR1_STS is work, in my experience, and it is triggered * after 4 times of initial value. So, it only (i * 4) seconds in range * of 4 ~ (255 * 4), where i is one of positive integer, are supported. * * The supported time intervals of watchdog(9) are 2^32~2^39 nanoseconds. * vordog used a (i * 4) seconds for 2^n seconds while i * 4 seconds is * closest to 2^n nanoseconds. The variances between i * 4 seconds and * 2^n nanoseconds are less than 7% (6.8678%). */ static void vordog_wd9_evh(void *priv_data, u_int cmd, int *error) { vd_softc_t sc; u_int timeout, u; /* 2^32, 2^33, ..., 2^39 nanoseconds */ static const int timeouts[] = { 1, 2, 4, 9, 17, 34, 69, 137}; sc = (vd_softc_t)priv_data; u = cmd & WD_INTERVAL; if(u < 32 || u > 39) { /* Out of range! Can not be configured. */ vordog_wd9_disable(); sc->init_val = 0; return; } timeout = timeouts[u - 31]; if(timeout != sc->init_val) { vordog_wd9_init(timeout); sc->init_val = timeout; } else { vordog_wd9_reset(); } *error = 0; } static void vordog_wd9_register(vd_softc_t sc) { sc->wd9_tag = \ EVENTHANDLER_REGISTER(watchdog_list, vordog_wd9_evh, sc, 0); } static void vordog_wd9_unregister(vd_softc_t sc) { EVENTHANDLER_DEREGISTER(watchdog_list, sc->wd9_tag); } /* Following implements a new bus device. * It is attached under nexus bus, an on board device as `vordog0'. */ static void vordog_identify(driver_t *driver, device_t parent) { device_t vordog; /* make sure vordog device is not in the bus. */ vordog = device_find_child(parent, "vordog", 0); if(vordog != NULL) return; vordog = BUS_ADD_CHILD(parent, 10, "vordog", 0); } static int vordog_probe(device_t dev) { int b; b = inb(VD_INITVAL); if(b != 0) return EINVAL; b = inb(VD_CTR); if(b != 0) return EINVAL; b = inb(VD_STATUS); if(b != 0) return EINVAL; return 0; } static int vordog_attach(device_t dev) { vd_softc_t sc; struct cdev *_cdev; disable_timer(); sc = (vd_softc_t)malloc(sizeof(struct vd_softc), M_TEMP, M_WAITOK); if(sc == NULL) return ENOMEM; _cdev = make_dev(&vordog_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "vordog0"); if(_cdev == NULL) { free(sc, M_TEMP); return EINVAL; } sc->_cdev = _cdev; sc->dev = dev; device_set_softc(dev, sc); CDEV_2_SOFTC(_cdev) = sc; sc->open_cnt = 0; sc->init_val = 0; sc->wd9_tag = NULL; device_set_desc(dev, "Watchdog of Vortex86 SoC"); device_printf(dev, "<Vortex86 SoC watchdog> at port 0x%x\n", VD_STATUS); vordog_wd9_register(sc); return 0; } static int vordog_detach(device_t dev) { vd_softc_t sc; struct cdev *_cdev; sc = device_get_softc(dev); if(sc->open_cnt != 0) return EBUSY; disable_timer(); vordog_wd9_unregister(sc); _cdev = sc->_cdev; destroy_dev(_cdev); free(sc, M_TEMP); return 0; } static device_method_t vordog_methods[] = { DEVMETHOD(device_identify, vordog_identify), DEVMETHOD(device_probe, vordog_probe), DEVMETHOD(device_attach, vordog_attach), DEVMETHOD(device_detach, vordog_detach), {0, 0} }; static driver_t vordog_driver = { "vordog", vordog_methods, 0, }; static int vordog_evh(module_t mod, int cmd, void *arg) { int r = 0; switch(cmd) { case MOD_LOAD: break; case MOD_UNLOAD: vordog_go_detach(); /* remove device from parent */ break; case MOD_SHUTDOWN: disable_timer(); /* prevent system reset when shuting down */ break; default: r = EINVAL; break; } return r; } static devclass_t vordog_devclass; DRIVER_MODULE(vordog, nexus, vordog_driver, vordog_devclass, vordog_evh, NULL);