root/sys/arch/sandpoint/sandpoint/satmgr.c
/* $NetBSD: satmgr.c,v 1.31 2021/09/26 01:16:08 thorpej Exp $ */

/*-
 * Copyright (c) 2010 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Tohru Nishimura.
 *
 * 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/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/reboot.h>
#include <sys/intr.h>

#include <dev/sysmon/sysmonvar.h>
#include <dev/sysmon/sysmon_taskq.h>

#include <dev/ic/comreg.h>

#include <sys/bus.h>
#include <machine/intr.h>
#include <machine/bootinfo.h>

#include <sandpoint/sandpoint/eumbvar.h>
#include "locators.h"


struct satops;

struct satmgr_softc {
        device_t                sc_dev;
        bus_space_tag_t         sc_iot;
        bus_space_handle_t      sc_ioh;
        struct selinfo          sc_rsel;
        callout_t               sc_ch_wdog;
        callout_t               sc_ch_pbutton;
        callout_t               sc_ch_sync;
        struct sysmon_pswitch   sc_sm_pbutton;
        int                     sc_open;
        void                    *sc_si;
        uint32_t                sc_ierror, sc_overr;
        kmutex_t                sc_lock;
        kcondvar_t              sc_rdcv, sc_wrcv;
        char                    sc_rd_buf[16];
        char                    *sc_rd_lim, *sc_rd_cur, *sc_rd_ptr;
        char                    sc_wr_buf[16];
        char                    *sc_wr_lim, *sc_wr_cur, *sc_wr_ptr;
        int                     sc_rd_cnt, sc_wr_cnt;
        struct satops           *sc_ops;
        char                    sc_btn_buf[8];
        int                     sc_btn_cnt;
        char                    sc_cmd_buf[8];
        kmutex_t                sc_replk;
        kcondvar_t              sc_repcv;
        int                     sc_sysctl_wdog;
        int                     sc_sysctl_fanlow;
        int                     sc_sysctl_fanhigh;
};

static int  satmgr_match(device_t, cfdata_t, void *);
static void satmgr_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(satmgr, sizeof(struct satmgr_softc),
    satmgr_match, satmgr_attach, NULL, NULL);
extern struct cfdriver satmgr_cd;

static int found = 0;
extern void (*md_reboot)(int);

static dev_type_open(satopen);
static dev_type_close(satclose);
static dev_type_read(satread);
static dev_type_write(satwrite);
static dev_type_poll(satpoll);
static dev_type_kqfilter(satkqfilter);

const struct cdevsw satmgr_cdevsw = {
        .d_open = satopen,
        .d_close = satclose,
        .d_read = satread,
        .d_write = satwrite,
        .d_ioctl = noioctl,
        .d_stop = nostop,
        .d_tty = notty,
        .d_poll = satpoll,
        .d_mmap = nommap,
        .d_kqfilter = satkqfilter,
        .d_discard = nodiscard,
        .d_flag = D_OTHER
};

static void satmgr_reboot(int);
static int satmgr_sysctl_wdogenable(SYSCTLFN_PROTO);
static int satmgr_sysctl_fanlow(SYSCTLFN_PROTO);
static int satmgr_sysctl_fanhigh(SYSCTLFN_PROTO);
static void wdog_tickle(void *);
static void send_sat(struct satmgr_softc *, const char *);
static void send_sat_len(struct satmgr_softc *, const char *, int);
static int hwintr(void *);
static void rxintr(struct satmgr_softc *);
static void txintr(struct satmgr_softc *);
static void startoutput(struct satmgr_softc *);
static void swintr(void *);
static void minit(device_t);
static void sinit(device_t);
static void qinit(device_t);
static void iinit(device_t);
static void kreboot(struct satmgr_softc *);
static void mreboot(struct satmgr_softc *);
static void sreboot(struct satmgr_softc *);
static void qreboot(struct satmgr_softc *);
static void ireboot(struct satmgr_softc *);
static void kpwroff(struct satmgr_softc *);
static void mpwroff(struct satmgr_softc *);
static void spwroff(struct satmgr_softc *);
static void qpwroff(struct satmgr_softc *);
static void dpwroff(struct satmgr_softc *);
static void ipwroff(struct satmgr_softc *);
static void kbutton(struct satmgr_softc *, int);
static void mbutton(struct satmgr_softc *, int);
static void sbutton(struct satmgr_softc *, int);
static void qbutton(struct satmgr_softc *, int);
static void dbutton(struct satmgr_softc *, int);
static void ibutton(struct satmgr_softc *, int);
static void msattalk(struct satmgr_softc *, const char *);
static void isattalk(struct satmgr_softc *, int, int, int, int, int, int);
static int  mbtnintr(void *);
static void guarded_pbutton(void *);
static void sched_sysmon_pbutton(void *);

struct satops {
        const char *family;
        void (*init)(device_t);
        void (*reboot)(struct satmgr_softc *);
        void (*pwroff)(struct satmgr_softc *);
        void (*dispatch)(struct satmgr_softc *, int);
};

static struct satops satmodel[] = {
    { "dlink",    NULL,  NULL,    dpwroff, dbutton },
    { "iomega",   iinit, ireboot, ipwroff, ibutton },
    { "kurobox",  NULL,  kreboot, kpwroff, kbutton },
    { "kurot4",   minit, mreboot, mpwroff, mbutton },
    { "qnap",     qinit, qreboot, qpwroff, qbutton },
    { "synology", sinit, sreboot, spwroff, sbutton }
};

/* single byte stride register layout */
#define RBR             0
#define THR             0
#define DLB             0
#define IER             1
#define DMB             1
#define IIR             2
#define LCR             3
#define LSR             5
#define CSR_READ(t,r)   bus_space_read_1((t)->sc_iot, (t)->sc_ioh, (r))
#define CSR_WRITE(t,r,v) bus_space_write_1((t)->sc_iot, (t)->sc_ioh, (r), (v))

static int
satmgr_match(device_t parent, cfdata_t match, void *aux)
{
        struct eumb_attach_args *eaa = aux;
        int unit = eaa->eumb_unit;

        if (unit == EUMBCF_UNIT_DEFAULT && found == 0)
                return 1;
        if (unit == 0 || unit == 1)
                return 1;
        return 0;
}

static void
satmgr_attach(device_t parent, device_t self, void *aux)
{
        struct eumb_attach_args *eaa = aux;
        struct satmgr_softc *sc = device_private(self);
        struct btinfo_prodfamily *pfam;
        struct satops *ops;
        int i, sataddr, epicirq;
        char intr_xname[INTRDEVNAMEBUF];

        found = 1;

        if ((pfam = lookup_bootinfo(BTINFO_PRODFAMILY)) == NULL)
                goto notavail;
        ops = NULL;
        for (i = 0; i < (int)(sizeof(satmodel)/sizeof(satmodel[0])); i++) {
                if (strcmp(pfam->name, satmodel[i].family) == 0) {
                        ops = &satmodel[i];
                        break;
                }
        }
        if (ops == NULL)
                goto notavail;
        aprint_naive(": button manager\n");
        aprint_normal(": button manager (%s)\n", ops->family);
        sc->sc_ops = ops;

        sc->sc_dev = self;
        sataddr = (eaa->eumb_unit == 0) ? 0x4500 : 0x4600;
        sc->sc_iot = eaa->eumb_bt;
        bus_space_map(eaa->eumb_bt, sataddr, 0x20, 0, &sc->sc_ioh);
        sc->sc_open = 0;
        sc->sc_rd_cnt = 0;
        sc->sc_rd_cur = sc->sc_rd_ptr = &sc->sc_rd_buf[0];
        sc->sc_rd_lim = sc->sc_rd_cur + sizeof(sc->sc_rd_buf);
        sc->sc_wr_cnt = 0;
        sc->sc_wr_cur = sc->sc_wr_ptr = &sc->sc_wr_buf[0];
        sc->sc_wr_lim = sc->sc_wr_cur + sizeof(sc->sc_wr_buf);
        sc->sc_ierror = sc->sc_overr = 0;
        selinit(&sc->sc_rsel);
        callout_init(&sc->sc_ch_wdog, 0);
        callout_init(&sc->sc_ch_pbutton, 0);
        callout_init(&sc->sc_ch_sync, 0);
        mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_HIGH);
        cv_init(&sc->sc_rdcv, "satrd");
        cv_init(&sc->sc_wrcv, "satwr");
        sc->sc_btn_cnt = 0;
        mutex_init(&sc->sc_replk, MUTEX_DEFAULT, IPL_SERIAL);
        cv_init(&sc->sc_repcv, "stalk");

        epicirq = (eaa->eumb_unit == 0) ? 24 : 25;
        intr_establish_xname(epicirq + I8259_ICU, IST_LEVEL, IPL_SERIAL,
            hwintr, sc, device_xname(self));
        aprint_normal_dev(self, "interrupting at irq %d\n",
            epicirq + I8259_ICU);
        sc->sc_si = softint_establish(SOFTINT_SERIAL, swintr, sc);

        CSR_WRITE(sc, IER, 0x7f); /* all but MSR */

        if (!pmf_device_register(sc->sc_dev, NULL, NULL))
                aprint_error_dev(sc->sc_dev, "couldn't establish handler\n");

        sysmon_task_queue_init();
        memset(&sc->sc_sm_pbutton, 0, sizeof(struct sysmon_pswitch));
        sc->sc_sm_pbutton.smpsw_name = device_xname(sc->sc_dev);
        sc->sc_sm_pbutton.smpsw_type = PSWITCH_TYPE_POWER;
        if (sysmon_pswitch_register(&sc->sc_sm_pbutton) != 0)
                aprint_error_dev(sc->sc_dev,
                    "unable to register power button with sysmon\n");

        /* create machdep.satmgr subtree for those models which support it */
        if (strcmp(ops->family, "kurobox") == 0) {
                const struct sysctlnode *rnode;
                struct sysctllog *clog;

                clog = NULL;
                sysctl_createv(&clog, 0, NULL, &rnode,
                        CTLFLAG_PERMANENT,
                        CTLTYPE_NODE, "machdep", NULL,
                        NULL, 0, NULL, 0,
                        CTL_MACHDEP, CTL_EOL);
                sysctl_createv(&clog, 0, &rnode, &rnode,
                        CTLFLAG_PERMANENT,
                        CTLTYPE_NODE, "satmgr", NULL,
                        NULL, 0, NULL, 0,
                        CTL_CREATE, CTL_EOL);
                sysctl_createv(&clog, 0, &rnode, NULL,
                        CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
                        CTLTYPE_INT, "hwwdog_enable",
                        SYSCTL_DESCR("watchdog enable"),
                        satmgr_sysctl_wdogenable, 0, NULL, 0,
                        CTL_CREATE, CTL_EOL);
        }
        else if (strcmp(ops->family, "iomega") == 0) {
                const struct sysctlnode *rnode;
                struct sysctllog *clog;

                clog = NULL;
                sysctl_createv(&clog, 0, NULL, &rnode,
                        CTLFLAG_PERMANENT,
                        CTLTYPE_NODE, "machdep", NULL,
                        NULL, 0, NULL, 0,
                        CTL_MACHDEP, CTL_EOL);
                sysctl_createv(&clog, 0, &rnode, &rnode,
                        CTLFLAG_PERMANENT,
                        CTLTYPE_NODE, "satmgr", NULL,
                        NULL, 0, NULL, 0,
                        CTL_CREATE, CTL_EOL);
                sysctl_createv(&clog, 0, &rnode, NULL,
                        CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
                        CTLTYPE_INT, "fan_low_temp",
                        SYSCTL_DESCR("Turn off fan below this temperature"),
                        satmgr_sysctl_fanlow, 0, NULL, 0,
                        CTL_CREATE, CTL_EOL);
                sysctl_createv(&clog, 0, &rnode, NULL,
                        CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
                        CTLTYPE_INT, "fan_high_temp",
                        SYSCTL_DESCR("Turn on fan above this temperature"),
                        satmgr_sysctl_fanhigh, 0, NULL, 0,
                        CTL_CREATE, CTL_EOL);
        }
        else if (strcmp(ops->family, "kurot4") == 0) {
                snprintf(intr_xname, sizeof(intr_xname), "%s mbtn",
                    device_xname(self));
                intr_establish_xname(2 + I8259_ICU,
                        IST_LEVEL, IPL_SERIAL, mbtnintr, sc, intr_xname);
        }

        md_reboot = satmgr_reboot;      /* cpu_reboot() hook */
        if (ops->init != NULL)          /* init sat.cpu, LEDs, etc. */
                config_interrupts(self, ops->init);
        return;

  notavail:
        aprint_normal(": button manager (not supported)\n");
}

static void
satmgr_reboot(int howto)
{
        struct satmgr_softc *sc = device_lookup_private(&satmgr_cd, 0);

        if ((howto & RB_POWERDOWN) == RB_AUTOBOOT) {
                if (sc->sc_ops->reboot != NULL)
                        (*sc->sc_ops->reboot)(sc);      /* REBOOT */
                else
                        return;                         /* default reboot */
        } else
                if (sc->sc_ops->pwroff != NULL)
                        (*sc->sc_ops->pwroff)(sc);      /* HALT or POWERDOWN */

        tsleep(satmgr_reboot, PWAIT, "reboot", 0);
        /*NOTREACHED*/
}

static int
satmgr_sysctl_wdogenable(SYSCTLFN_ARGS)
{
        struct sysctlnode node;
        struct satmgr_softc *sc;
        int error, t;

        sc = device_lookup_private(&satmgr_cd, 0);
        node = *rnode;
        t = sc->sc_sysctl_wdog;
        node.sysctl_data = &t;
        error = sysctl_lookup(SYSCTLFN_CALL(&node));
        if (error || newp == NULL)
                return error;
        if (t < 0 || t > 1)
                return EINVAL;

        sc->sc_sysctl_wdog = t;
        if (t == 1) {
                callout_setfunc(&sc->sc_ch_wdog, wdog_tickle, sc);
                callout_schedule(&sc->sc_ch_wdog, 90 * hz);
                send_sat(sc, "JJ");
        } else {
                callout_stop(&sc->sc_ch_wdog);
                send_sat(sc, "KK");
        }
        return 0;
}

/* disarm watchdog timer periodically */
static void
wdog_tickle(void *arg)
{
        struct satmgr_softc *sc = arg;

        send_sat(sc, "GG");
        callout_schedule(&sc->sc_ch_wdog, 90 * hz);
}

static int
satmgr_sysctl_fanlow(SYSCTLFN_ARGS)
{
        struct sysctlnode node;
        struct satmgr_softc *sc;
        int error, t;

        sc = device_lookup_private(&satmgr_cd, 0);
        node = *rnode;
        t = sc->sc_sysctl_fanlow;
        node.sysctl_data = &t;
        error = sysctl_lookup(SYSCTLFN_CALL(&node));
        if (error || newp == NULL)
                return error;
        if (t < 0 || t > 99)
                return EINVAL;
        sc->sc_sysctl_fanlow = t;
        isattalk(sc, 'b', 'b', 10, 'a',
            sc->sc_sysctl_fanhigh, sc->sc_sysctl_fanlow);
        return 0;
}

static int
satmgr_sysctl_fanhigh(SYSCTLFN_ARGS)
{
        struct sysctlnode node;
        struct satmgr_softc *sc;
        int error, t;

        sc = device_lookup_private(&satmgr_cd, 0);
        node = *rnode;
        t = sc->sc_sysctl_fanhigh;
        node.sysctl_data = &t;
        error = sysctl_lookup(SYSCTLFN_CALL(&node));
        if (error || newp == NULL)
                return error;
        if (t < 0 || t > 99)
                return EINVAL;
        sc->sc_sysctl_fanhigh = t;
        isattalk(sc, 'b', 'b', 10, 'a',
            sc->sc_sysctl_fanhigh, sc->sc_sysctl_fanlow);
        return 0;
}

static void
send_sat(struct satmgr_softc *sc, const char *msg)
{

        send_sat_len(sc, msg, strlen(msg));
}


static void
send_sat_len(struct satmgr_softc *sc, const char *msg, int len)
{
        unsigned lsr;
        int n;

 again:
        do {
                lsr = CSR_READ(sc, LSR);
        } while ((lsr & LSR_TXRDY) == 0);
        n = 16; /* FIFO depth */
        while (len-- > 0 && n-- > 0)
                CSR_WRITE(sc, THR, *msg++);
        if (len > 0)
                goto again;
}

static int
satopen(dev_t dev, int flags, int fmt, struct lwp *l)
{
        struct satmgr_softc *sc;

        sc = device_lookup_private(&satmgr_cd, 0);
        if (sc == NULL)
                return ENXIO;
        if (sc->sc_open > 0)
                return EBUSY;
        sc->sc_open = 1;
        return 0;
}

static int
satclose(dev_t dev, int flags, int fmt, struct lwp *l)
{
        struct satmgr_softc *sc;

        sc = device_lookup_private(&satmgr_cd, 0);
        if (sc == NULL)
                return ENXIO;
        KASSERT(sc->sc_open > 0);
        sc->sc_open = 0;
        return 0;
}

static int
satread(dev_t dev, struct uio *uio, int flags)
{
        struct satmgr_softc *sc;
        size_t n;
        int error;

        sc = device_lookup_private(&satmgr_cd, 0);
        if (sc == NULL)
                return ENXIO;

        mutex_enter(&sc->sc_lock);
        if (sc->sc_rd_cnt == 0 && (flags & IO_NDELAY)) {
                error = EWOULDBLOCK;
                goto out;
        }
        error = 0;
        while (sc->sc_rd_cnt == 0) {
                error = cv_wait_sig(&sc->sc_rdcv, &sc->sc_lock);
                if (error)
                        goto out;
        }
        while (uio->uio_resid > 0 && sc->sc_rd_cnt > 0) {
                n = uimin(sc->sc_rd_cnt, uio->uio_resid);
                n = uimin(n, sc->sc_rd_lim - sc->sc_rd_ptr);
                error = uiomove(sc->sc_rd_ptr, n, uio);
                if (error)
                        goto out;
                sc->sc_rd_cnt -= n;
                sc->sc_rd_ptr += n;
                if (sc->sc_rd_ptr == sc->sc_rd_lim)
                        sc->sc_rd_ptr = &sc->sc_rd_buf[0];
        }
 out:
        mutex_exit(&sc->sc_lock);
        return error;
}

static int
satwrite(dev_t dev, struct uio *uio, int flags)
{
        struct satmgr_softc *sc;
        int error;
        size_t n;

        sc = device_lookup_private(&satmgr_cd, 0);
        if (sc == NULL)
                return ENXIO;

        mutex_enter(&sc->sc_lock);
        if (sc->sc_wr_cnt == sizeof(sc->sc_wr_buf) && (flags & IO_NDELAY)) {
                error = EWOULDBLOCK;
                goto out;
        }
        error = 0;
        while (sc->sc_wr_cnt == sizeof(sc->sc_wr_buf)) {
                error = cv_wait_sig(&sc->sc_wrcv, &sc->sc_lock);
                if (error)
                        goto out;
        }
        while (uio->uio_resid > 0 && sc->sc_wr_cnt < sizeof(sc->sc_wr_buf)) {
                n = uimin(uio->uio_resid, sizeof(sc->sc_wr_buf));
                n = uimin(n, sc->sc_wr_lim - sc->sc_wr_cur);
                error = uiomove(sc->sc_wr_cur, n, uio);
                if (error)
                        goto out;
                sc->sc_wr_cnt += n;
                sc->sc_wr_cur += n;
                if (sc->sc_wr_cur == sc->sc_wr_lim)
                        sc->sc_wr_cur = &sc->sc_wr_buf[0];
        }
        startoutput(sc); /* start xmit */
 out:
        mutex_exit(&sc->sc_lock);
        return error;
}

static int
satpoll(dev_t dev, int events, struct lwp *l)
{
        struct satmgr_softc *sc;
        int revents = 0;

        sc = device_lookup_private(&satmgr_cd, 0);
        mutex_enter(&sc->sc_lock);
        if (events & (POLLIN | POLLRDNORM)) {
                if (sc->sc_rd_cnt)
                        revents |= events & (POLLIN | POLLRDNORM);
                else
                        selrecord(l, &sc->sc_rsel);
        }
        mutex_exit(&sc->sc_lock);

        return revents;
}

static void
filt_rdetach(struct knote *kn)
{
        struct satmgr_softc *sc = kn->kn_hook;

        mutex_enter(&sc->sc_lock);
        selremove_knote(&sc->sc_rsel, kn);
        mutex_exit(&sc->sc_lock);
}

static int
filt_read(struct knote *kn, long hint)
{
        struct satmgr_softc *sc = kn->kn_hook;

        kn->kn_data = sc->sc_rd_cnt;
        return (kn->kn_data > 0);
}

static const struct filterops read_filtops ={
        .f_flags = FILTEROP_ISFD,
        .f_attach = NULL,
        .f_detach = filt_rdetach,
        .f_event = filt_read,
};

static int
satkqfilter(dev_t dev, struct knote *kn)
{
        struct satmgr_softc *sc = device_lookup_private(&satmgr_cd, 0);

        switch (kn->kn_filter) {
        case EVFILT_READ:
                kn->kn_fop = &read_filtops;
                break;

        default:
                return EINVAL;
        }

        kn->kn_hook = sc;

        mutex_enter(&sc->sc_lock);
        selrecord_knote(&sc->sc_rsel, kn);
        mutex_exit(&sc->sc_lock);

        return 0;
}

static int
hwintr(void *arg)
{
        struct satmgr_softc *sc = arg;
        int iir;

        mutex_spin_enter(&sc->sc_lock);
        iir = CSR_READ(sc, IIR) & IIR_IMASK;
        if (iir == IIR_NOPEND) {
                mutex_spin_exit(&sc->sc_lock);
                return 0;
        }
        do {
                switch (iir) {
                case IIR_RLS:   /* LSR updated */
                case IIR_RXRDY: /* RxFIFO has been accumulated */
                case IIR_RXTOUT:/* receive timeout occurred */
                        rxintr(sc);
                        break;
                case IIR_TXRDY: /* TxFIFO is ready to swallow data */
                        txintr(sc);
                        break;
                case IIR_MLSC:  /* MSR updated */
                        break;
                }
                iir = CSR_READ(sc, IIR) & IIR_IMASK;
        } while (iir != IIR_NOPEND);
        mutex_spin_exit(&sc->sc_lock);
        return 1;
}

static void
rxintr(struct satmgr_softc *sc)
{
        int lsr, ch;

        lsr = CSR_READ(sc, LSR);
        if (lsr & LSR_OE)
                sc->sc_overr++;
        ch = -1;
        while (lsr & LSR_RXRDY) {
                if (lsr & (LSR_BI | LSR_FE | LSR_PE)) {
                        (void) CSR_READ(sc, RBR);
                        sc->sc_ierror++;
                        lsr = CSR_READ(sc, LSR);
                        continue;
                }
                ch = CSR_READ(sc, RBR);
                if (sc->sc_rd_cnt < sizeof(sc->sc_rd_buf)) {
                        *sc->sc_rd_cur = ch;
                        if (++sc->sc_rd_cur == sc->sc_rd_lim)
                                sc->sc_rd_cur = &sc->sc_rd_buf[0];
                        sc->sc_rd_cnt += 1;
                }
                lsr = CSR_READ(sc, LSR);
        }
        if (ch != -1)
                softint_schedule(sc->sc_si);
}

static void
txintr(struct satmgr_softc *sc)
{

        cv_signal(&sc->sc_wrcv);
        startoutput(sc);
}

static void
startoutput(struct satmgr_softc *sc)
{
        int n;

        mutex_enter(&sc->sc_replk);
        n = uimin(sc->sc_wr_cnt, 16);
        while (n-- > 0) {
                CSR_WRITE(sc, THR, *sc->sc_wr_ptr);
                if (++sc->sc_wr_ptr == sc->sc_wr_lim)
                        sc->sc_wr_ptr = &sc->sc_wr_buf[0];
                sc->sc_wr_cnt -= 1;
        }
        mutex_exit(&sc->sc_replk);
}

static void
swintr(void *arg)
{
        struct satmgr_softc *sc = arg;
        char *ptr;
        int n;

        /* we're now in softint(9) context */
        mutex_spin_enter(&sc->sc_lock);
        ptr = sc->sc_rd_ptr;
        for (n = 0; n < sc->sc_rd_cnt; n++) {
                (*sc->sc_ops->dispatch)(sc, *ptr);
                if (++ptr == sc->sc_rd_lim)
                        ptr = &sc->sc_rd_buf[0];
        }
        if (sc->sc_open == 0) {
                sc->sc_rd_cnt = 0;
                sc->sc_rd_ptr = ptr;
                mutex_spin_exit(&sc->sc_lock);
                return;         /* drop characters down to the floor */
        }
        cv_signal(&sc->sc_rdcv);
        selnotify(&sc->sc_rsel, 0, 0);
        mutex_spin_exit(&sc->sc_lock);
}

static void
kreboot(struct satmgr_softc *sc)
{

        send_sat(sc, "CCGG"); /* perform reboot */
}

static void
kpwroff(struct satmgr_softc *sc)
{

        send_sat(sc, "EEGG"); /* force power off */
}

static void
kbutton(struct satmgr_softc *sc, int ch)
{

        switch (ch) {
        case '!':
                /* schedule 3 second poweroff guard time */
                if (callout_pending(&sc->sc_ch_pbutton) == true)
                        callout_stop(&sc->sc_ch_pbutton);
                callout_reset(&sc->sc_ch_pbutton,
                     3 * hz, guarded_pbutton, sc);
                break;
        case ' ':
                if (callout_expired(&sc->sc_ch_pbutton) == false)
                        callout_stop(&sc->sc_ch_pbutton);
                else
                        /* should never come here */;
                break;
        case '#':
        case '"':
                break;
        }
}

static void
sinit(device_t self)
{
        struct satmgr_softc *sc = device_private(self);

        send_sat(sc, "8");      /* status LED green */
}

static void
sreboot(struct satmgr_softc *sc)
{

        send_sat(sc, "C");
}

static void
spwroff(struct satmgr_softc *sc)
{

        send_sat(sc, "1");
}

static void
sbutton(struct satmgr_softc *sc, int ch)
{

        switch (ch) {
        case '0':
                /* notified after 5 seconds guard time */
                sysmon_task_queue_sched(0, sched_sysmon_pbutton, sc);
                break;
        case 'a':
        case '`':
                break;
        }
}

static void
qinit(device_t self)
{
        struct satmgr_softc *sc = device_private(self);

        send_sat(sc, "V");      /* status LED green */
}

static void
qreboot(struct satmgr_softc *sc)
{

        send_sat(sc, "Pf");     /* beep and reboot */
}

static void
qpwroff(struct satmgr_softc *sc)
{

        send_sat(sc, "PA");     /* beep and power off */
}

static void
qbutton(struct satmgr_softc *sc, int ch)
{

        switch (ch) {
        case '@':
                /* power button, notified after 2 seconds guard time */
                sysmon_task_queue_sched(0, sched_sysmon_pbutton, sc);
                break;
        case 'j':       /* reset to default button */
        case 'h':       /* USB copy button */
                break;
        }
}

static void
dpwroff(struct satmgr_softc *sc)
{

        send_sat(sc, "ZWC\n");

        /*
         * When this line is reached, then this board revision doesn't
         * support hardware-shutdown, so we flash the power LED
         * to indicate that the device can be switched off.
         */
        send_sat(sc, "SYN\nSYN\n");

        /* drops into default power-off handling (looping forever) */
}

static void
dbutton(struct satmgr_softc *sc, int ch)
{

        if (sc->sc_btn_cnt < sizeof(sc->sc_btn_buf))
                sc->sc_btn_buf[sc->sc_btn_cnt++] = ch;
        if (ch == '\n' || ch == '\r') {
                if (memcmp(sc->sc_btn_buf, "PKO", 3) == 0) {
                        /* notified after 5 seconds guard time */
                        sysmon_task_queue_sched(0, sched_sysmon_pbutton, sc);
                }
                sc->sc_btn_cnt = 0;
        }
}

static void
iinit(device_t self)
{
        struct satmgr_softc *sc = device_private(self);

        /* LED blue, auto-fan, turn on at 50C, turn off at 45C */
        sc->sc_sysctl_fanhigh = 50;
        sc->sc_sysctl_fanlow = 45;
        isattalk(sc, 'b', 'b', 10, 'a',
            sc->sc_sysctl_fanhigh, sc->sc_sysctl_fanlow);
}

static void
ireboot(struct satmgr_softc *sc)
{

        isattalk(sc, 'g', 0, 0, 0, 0, 0);
}

static void
ipwroff(struct satmgr_softc *sc)
{

        isattalk(sc, 'c', 0, 0, 0, 0, 0);
}

static void
ibutton(struct satmgr_softc *sc, int ch)
{

        mutex_enter(&sc->sc_replk);
        if (++sc->sc_btn_cnt >= 8) {
                cv_signal(&sc->sc_repcv);
                sc->sc_btn_cnt = 0;
        }
        mutex_exit(&sc->sc_replk);
}

static void
isattalk(struct satmgr_softc *sc, int pow, int led, int rat, int fan,
    int fhi, int flo)
{
        char *p = sc->sc_cmd_buf;
        int i, cksum;

        /*
         * Construct the command packet. Values of -1 (0xff) will be
         * replaced later by the current values from the last status.
         */
        p[0] = pow;
        p[1] = led;
        p[2] = rat;
        p[3] = fan;
        p[4] = fhi;
        p[5] = flo;
        p[6] = 7; /* host id */
        for (i = 0, cksum = 0; i < 7; i++)
                cksum += p[i];
        p[7] = cksum & 0x7f;
        send_sat_len(sc, p, 8);

        mutex_enter(&sc->sc_replk);
        sc->sc_btn_cnt = 0;
        cv_wait(&sc->sc_repcv, &sc->sc_replk);
        mutex_exit(&sc->sc_replk);
}


static void
minit(device_t self)
{
        struct satmgr_softc *sc = device_private(self);
#if 0
        static char msg[35] = "\x20\x92NetBSD/sandpoint";
        int m, n;

        m = strlen(osrelease);
        n = (16 - m) / 2;
        memset(&msg[18], ' ', 16);
        memcpy(&msg[18 + n], osrelease, m);

        msattalk(sc, "\x00\x03");       /* boot has completed */
        msattalk(sc, msg);              /* NB banner at disp2 */
        msattalk(sc, "\x01\x32\x80");   /* select disp2 */
        msattalk(sc, "\x00\x27");       /* show disp2 */
#else
        msattalk(sc, "\x00\x03");       /* boot has completed */
#endif
}

static void
mreboot(struct satmgr_softc *sc)
{

        msattalk(sc, "\x01\x35\x00");   /* stop watchdog timer */
        msattalk(sc, "\x00\x0c");       /* shutdown in progress */
        msattalk(sc, "\x00\x03");       /* boot has completed */
        msattalk(sc, "\x00\x0e");       /* perform reboot */
}

static void
mpwroff(struct satmgr_softc *sc)
{

        msattalk(sc, "\x01\x35\x00");   /* stop watchdog timer */
        msattalk(sc, "\x00\x0c");       /* shutdown in progress */
        msattalk(sc, "\x00\x03");       /* boot has completed */
        msattalk(sc, "\x00\x06");       /* force power off */
}

static void
msattalk(struct satmgr_softc *sc, const char *cmd)
{
        int len, i;
        uint8_t pa;

        if (cmd[0] != 0x80)
                len = 2 + cmd[0]; /* cmd[0] is data portion length */
        else
                len = 2; /* read report */

        for (i = 0, pa = 0; i < len; i++)
                pa += cmd[i];
        pa = 0 - pa; /* parity formula */

        send_sat_len(sc, cmd, len);
        send_sat_len(sc, &pa, 1);

        mutex_enter(&sc->sc_replk);
        sc->sc_btn_cnt = 0;
        cv_wait(&sc->sc_repcv, &sc->sc_replk);
        mutex_exit(&sc->sc_replk);
}

static void
mbutton(struct satmgr_softc *sc, int ch)
{

        mutex_enter(&sc->sc_replk);
        if (sc->sc_btn_cnt < 4) /* record the first four */
                sc->sc_btn_buf[sc->sc_btn_cnt] = ch;
        sc->sc_btn_cnt++;
        if (sc->sc_btn_cnt == sc->sc_btn_buf[0] + 3) {
                if (sc->sc_btn_buf[1] == 0x36 && (sc->sc_btn_buf[2]&01) == 0) {
                        /* power button pressed */
                        sysmon_task_queue_sched(0, sched_sysmon_pbutton, sc);
                }
                else {
                        /* unblock the talker */
                        cv_signal(&sc->sc_repcv);
                }
                sc->sc_btn_cnt = 0;
        }
        mutex_exit(&sc->sc_replk);
}

static int
mbtnintr(void *arg)
{
        /* notified after 3 seconds guard time */
        struct satmgr_softc *sc = arg;

        send_sat(sc, "\x80\x36\x4a"); /* query button state with parity */
        mutex_enter(&sc->sc_replk);
        sc->sc_btn_cnt = 0;
        mutex_exit(&sc->sc_replk);
        return 1;
}

static void
guarded_pbutton(void *arg)
{
        struct satmgr_softc *sc = arg;

        /* we're now in callout(9) context */
        sysmon_task_queue_sched(0, sched_sysmon_pbutton, sc);
        send_sat(sc, "UU"); /* make front panel LED flashing */
}

static void
sched_sysmon_pbutton(void *arg)
{
        struct satmgr_softc *sc = arg;

        /* we're now in kthread(9) context */
        sysmon_pswitch_event(&sc->sc_sm_pbutton, PSWITCH_EVENT_PRESSED);
}