diff --git a/etc/etc.evbppc/MAKEDEV.conf b/etc/etc.evbppc/MAKEDEV.conf index e866d6ad088fd..1e868ab810a0b 100644 --- a/etc/etc.evbppc/MAKEDEV.conf +++ b/etc/etc.evbppc/MAKEDEV.conf @@ -22,6 +22,7 @@ all_md) makedev radio makedev kttcp makedev xlcom0 + makedev gcport0 gcport1 gcport2 gcport3 makedev cfs makedev virtio ;; @@ -32,3 +33,8 @@ xlcom[0-9]*) unit=${i#xlcom} mkdev xlcom$unit c 99 $unit "" "" $u_uucp ;; + +gcport[0-3]*) + unit=${i#gcport} + mkdev gcport$unit c 100 $unit "" "" $u_uucp + ;; diff --git a/sys/arch/evbppc/conf/NINTENDO b/sys/arch/evbppc/conf/NINTENDO index 03672719ce36e..dd2d6ed8e1436 100644 --- a/sys/arch/evbppc/conf/NINTENDO +++ b/sys/arch/evbppc/conf/NINTENDO @@ -132,7 +132,11 @@ options WSDISPLAY_MULTICONS ahb0 at mainbus0 irq 14 si0 at mainbus0 addr 0x0d006400 irq 3 # Serial interface -uhid* at si0 +gcport* at si0 +uhid0 at gcport0 +uhid1 at gcport1 +uhid2 at gcport2 +uhid3 at gcport3 exi0 at mainbus0 addr 0x0d006800 irq 4 # External interface rtcsram0 at exi0 # RTC/SRAM chip gecko0 at exi0 # USB Gecko diff --git a/sys/arch/evbppc/conf/majors.evbppc b/sys/arch/evbppc/conf/majors.evbppc index 10f9eb8bf8427..20dce4457788a 100644 --- a/sys/arch/evbppc/conf/majors.evbppc +++ b/sys/arch/evbppc/conf/majors.evbppc @@ -83,6 +83,8 @@ device-major twe char 76 twe #device-major obsolete char 98 obsolete (nsmb) device-major xlcom char 99 xlcom +device-major gcport char 100 + # Majors up to 143 are reserved for machine-dependent drivers. # New machine-independent driver majors are assigned in # sys/conf/majors. diff --git a/sys/arch/evbppc/nintendo/dev/files.dev b/sys/arch/evbppc/nintendo/dev/files.dev index cd053d28aadb2..6da129f30704b 100644 --- a/sys/arch/evbppc/nintendo/dev/files.dev +++ b/sys/arch/evbppc/nintendo/dev/files.dev @@ -30,11 +30,16 @@ attach gecko at exi file arch/evbppc/nintendo/dev/gecko.c gecko needs-flag define si { } -device si: si, hid +device si: si attach si at mainbus file arch/evbppc/nintendo/dev/si.c si -attach uhid at si with uhid_si +define gcport { } +device gcport: gcport, hid +attach gcport at si with gcport_si +file arch/evbppc/nintendo/dev/gcport_si.c gcport_si + +attach uhid at gcport with uhid_si file arch/evbppc/nintendo/dev/uhid_si.c uhid_si define ahb { [addr=-1], [irq=-1] } diff --git a/sys/arch/evbppc/nintendo/dev/gcport_si.c b/sys/arch/evbppc/nintendo/dev/gcport_si.c new file mode 100644 index 0000000000000..77232f81e7030 --- /dev/null +++ b/sys/arch/evbppc/nintendo/dev/gcport_si.c @@ -0,0 +1,301 @@ +/* $NetBSD: gcport_si.c,v 1.0 2026/05/18 22:54:30 gummybuns Exp $ */ + +/*- + * Copyright (c) 2026 ZacBrown + * 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 AUTHOR ``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 AUTHOR 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 +__KERNEL_RCSID(0, "$NetBSD: gcport_si.c,v 1.0 2026/05/18 22:53:30 gummybuns Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "si.h" + +struct si_payload { + uint32_t insize; /* bytes to receive. max 128 */ + uint32_t outsize; /* bytes to send. max 128 */ + uint32_t *status; /* sisr status for this channel */ + void *in; /* buffer to store response */ + void *out; /* buffer to send out to device */ +}; + +extern struct cfdriver gcport_cd; + +#define SI_SEND _IOWR(0, 1, struct si_payload) + +static int gcport_si_match(device_t, cfdata_t, void *); +static void gcport_si_attach(device_t, device_t, void *); + +static int gcport_si_print(void *, const char *); +static int siioctl_send(struct si_channel *ch, struct si_payload *sp); +static void gcport_si_work(struct work *, void *); + +dev_type_open(gcport_open); +dev_type_close(gcport_close); +dev_type_ioctl(gcport_ioctl); + +const struct cdevsw gcport_cdevsw = { + .d_open = gcport_open, + .d_close = gcport_close, + .d_ioctl = gcport_ioctl, + .d_read = noread, + .d_write = nowrite, + .d_stop = nostop, + .d_tty = notty, + .d_poll = nopoll, + .d_mmap = nommap, + .d_kqfilter = nokqfilter, + .d_discard = nodiscard, + .d_flag = D_OTHER +}; + +CFATTACH_DECL_NEW(gcport_si, sizeof(struct gcport_softc), + gcport_si_match, gcport_si_attach, NULL, NULL); + +static int +gcport_si_match(device_t parent, cfdata_t cf, void *aux) +{ + return 1; +} + +static void +gcport_si_attach(device_t parent, device_t self, void *aux) +{ + struct si_softc * const psc = device_private(parent); + struct si_attach_args * const saa = aux; + struct gcport_softc * const sc = device_private(self); + struct si_channel *ch = &psc->sc_chan[saa->saa_index]; + int err; + + sc->sc_dev = self; + sc->ch = ch; + sc->sc_bst = psc->sc_bst; + sc->sc_bsh = psc->sc_bsh; + ch->ch_gcport_dev = self; + + err = workqueue_create(&ch->ch_wqp, "gcport", gcport_si_work, sc, + PRI_NONE, IPL_VM, 0); + if (err != 0) { + aprint_normal("gcport_si: ch%d failed to create workqueue\n", + ch->ch_index); + ch->ch_wqp = NULL; + } +} + +static int +gcport_si_print(void *aux, const char *pnp) +{ + struct si_attach_args *saa = aux; + + if (pnp != NULL) { + aprint_normal("uhid at %s", pnp); + } + + /* + * The Wii Operations Manual for RVL-001 refers to the controller + * ports as "Nintendo GameCube Controller Sockets". + */ + aprint_normal(" socket %d", saa->saa_index + 1); + + return UNCONF; +} + +int +gcport_open(dev_t dev, int flags, int mode, struct lwp *l) +{ + struct gcport_softc *sc; + struct si_channel *ch; + int error; + + sc = device_lookup_private(&gcport_cd, minor(dev)); + + if (sc == NULL) { + return ENXIO; + } + + ch = sc->ch; + mutex_enter(&ch->ch_lock); + + if (ISSET(ch->ch_state, SI_STATE_OPEN)) { + error = EBUSY; + goto unlock; + } + + ch->ch_state |= SI_STATE_OPEN; + error = 0; +unlock: + mutex_exit(&ch->ch_lock); + return error; +} + +int +gcport_close(dev_t dev, int flags, int mode, struct lwp *l) +{ + struct gcport_softc *sc = device_lookup_private(&gcport_cd, minor(dev)); + struct si_channel *ch = sc->ch; + + mutex_enter(&ch->ch_lock); + ch->ch_state &= ~(SI_STATE_OPEN | SI_STATE_STOPPED); + + /* cv_init is called in parent's si_attach */ + cv_broadcast(&ch->ch_cv); + + mutex_exit(&ch->ch_lock); + return 0; +} + +static void +gcport_si_work(struct work *wk, void *arg) +{ + struct si_attach_args saa; + struct gcport_softc *sc; + struct si_channel *ch; + int res; + + sc = arg; + ch = sc->ch; + + if (ch->ch_uhid_dev == NULL) { + saa.saa_hidev = &ch->ch_hidev; + saa.saa_index = ch->ch_index; + ch->ch_uhid_dev = config_found(sc->sc_dev, &saa, + gcport_si_print, CFARGS_NONE); + } else { + res = config_detach_children(ch->ch_gcport_dev, 0); + if (res == 0) { + ch->ch_uhid_dev = NULL; + } + } +} + +int +gcport_ioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) +{ + struct gcport_softc *sc = device_lookup_private(&gcport_cd, minor(dev)); + struct si_channel *ch = sc->ch; + int err; + + switch(cmd) { + case SI_SEND: + err = siioctl_send(ch, (struct si_payload *)data); + break; + default: + err = EINVAL; + break; + } + + return err; +} + +static int +siioctl_send(struct si_channel *ch, struct si_payload *p) +{ + int err, outsize_r, insize_r; + struct si_softc *sc; + struct si_packet pk; + struct bintime bt; + struct timeval tv; + unsigned has_ch_lock = 0; + + err = 0; + sc = ch->ch_sc; + + /* siiobuf must to be written in increments of 4 bytes */ + outsize_r = roundup(p->outsize, 4); + insize_r = roundup(p->insize, 4); + + pk.out = kmem_zalloc(outsize_r, KM_SLEEP); + pk.in = kmem_zalloc(insize_r, KM_SLEEP); + pk.chan = ch->ch_index; + pk.outsize = p->outsize; + pk.insize = p->insize; + + if ((err = copyin(p->out, pk.out, pk.outsize)) != 0) { + goto si_send_cleanup; + } + + mutex_enter(&ch->ch_lock); + if (ch->ch_send_status == CH_UNAVAIL) { + err = EBUSY; + mutex_exit(&ch->ch_lock); + goto si_send_cleanup; + } + ch->ch_pkid++; + pk.id = ch->ch_pkid; + ch->ch_pk = &pk; + ch->ch_send_status = CH_UNAVAIL; + mutex_exit(&ch->ch_lock); + + if ((err = si_send(sc, &pk)) != 0) { + goto si_send_cleanup; + } + + mutex_enter(&ch->ch_lock); + tv.tv_sec = 1; + tv.tv_usec = 0; + timeval2bintime(&tv, &bt); + while (ch->ch_send_status == CH_UNAVAIL) { + err = cv_timedwaitbt(&ch->ch_cv, &ch->ch_lock, &bt, + DEFAULT_TIMEOUT_EPSILON); + if (err) { + KASSERT(err == EWOULDBLOCK); + err = ETIMEDOUT; + ch->ch_send_status = CH_AVAIL; + mutex_exit(&ch->ch_lock); + goto si_send_cleanup; + } + + } + has_ch_lock = 1; + + if ((err = copyout(pk.in, p->in, pk.insize)) != 0) { + goto si_send_cleanup; + } + + if ((err = copyout(&pk.status, p->status, sizeof(uint32_t))) != 0) { + goto si_send_cleanup; + } +si_send_cleanup: + kmem_free(pk.out, outsize_r); + kmem_free(pk.in, insize_r); + pk.in = NULL; + pk.out = NULL; + if (has_ch_lock) { + mutex_exit(&ch->ch_lock); + } + return err; +} diff --git a/sys/arch/evbppc/nintendo/dev/si.c b/sys/arch/evbppc/nintendo/dev/si.c index d55c06cfae1cf..12e616b448ffd 100644 --- a/sys/arch/evbppc/nintendo/dev/si.c +++ b/sys/arch/evbppc/nintendo/dev/si.c @@ -48,85 +48,6 @@ __KERNEL_RCSID(0, "$NetBSD: si.c,v 1.1 2026/01/09 22:54:30 jmcneill Exp $"); #include "si.h" #include "gcpad_rdesc.h" -#define SI_NUM_CHAN 4 - -#define SICOUTBUF(n) ((n) * 0xc + 0x00) -#define SICINBUFH(n) ((n) * 0xc + 0x04) -#define SICINBUFL(n) ((n) * 0xc + 0x08) -#define SIPOLL 0x30 -#define SIPOLL_X __BITS(25, 16) -#define SIPOLL_Y __BITS(15, 8) -#define SIPOLL_EN(n) (__BIT(7 - n)) -#define SICOMCSR 0x34 -#define SICOMCSR_TCINT __BIT(31) -#define SICOMCSR_TCINTMSK __BIT(30) -#define SICOMCSR_RDSTINT __BIT(28) -#define SICOMCSR_RDSTINTMSK __BIT(27) -#define SICOMCSR_OUTLNGTH __BITS(22, 16) -#define SICOMCSR_INLNGTH __BITS(14, 8) -#define SICOMCSR_TSTART __BIT(0) -#define SISR 0x38 -#define SISR_OFF(n) ((3 - (n)) * 8) -#define SISR_WR(n) __BIT(SISR_OFF(n) + 7) -#define SISR_RDST(n) __BIT(SISR_OFF(n) + 5) -#define SISR_WRST(n) __BIT(SISR_OFF(n) + 4) -#define SISR_NOREP(n) __BIT(SISR_OFF(n) + 3) -#define SISR_COLL(n) __BIT(SISR_OFF(n) + 2) -#define SISR_OVRUN(n) __BIT(SISR_OFF(n) + 1) -#define SISR_UNRUN(n) __BIT(SISR_OFF(n) + 0) -#define SISR_ERROR_MASK(n) (SISR_NOREP(n) | SISR_COLL(n) | \ - SISR_OVRUN(n) | SISR_UNRUN(n)) -#define SISR_ERROR_ACK_ALL (SISR_ERROR_MASK(0) | SISR_ERROR_MASK(1) | \ - SISR_ERROR_MASK(2) | SISR_ERROR_MASK(3)) -#define SIEXILK 0x3c -#define SIIOBUF 0x80 - -#define GCPAD_REPORT_SIZE 9 -#define GCPAD_START(_buf) ISSET((_buf)[0], 0x10) -#define GCPAD_Y(_buf) ISSET((_buf)[0], 0x08) -#define GCPAD_X(_buf) ISSET((_buf)[0], 0x04) -#define GCPAD_B(_buf) ISSET((_buf)[0], 0x02) -#define GCPAD_A(_buf) ISSET((_buf)[0], 0x01) -#define GCPAD_LCLICK(_buf) ISSET((_buf)[1], 0x40) -#define GCPAD_RCLICK(_buf) ISSET((_buf)[1], 0x20) -#define GCPAD_Z(_buf) ISSET((_buf)[1], 0x10) -#define GCPAD_UP(_buf) ISSET((_buf)[1], 0x08) -#define GCPAD_DOWN(_buf) ISSET((_buf)[1], 0x04) -#define GCPAD_RIGHT(_buf) ISSET((_buf)[1], 0x02) -#define GCPAD_LEFT(_buf) ISSET((_buf)[1], 0x01) - -struct si_softc; - -struct si_channel { - struct si_softc *ch_sc; - device_t ch_dev; - unsigned ch_index; - struct hidev_tag ch_hidev; - kmutex_t ch_lock; - kcondvar_t ch_cv; - uint8_t ch_state; -#define SI_STATE_OPEN __BIT(0) -#define SI_STATE_STOPPED __BIT(1) - void (*ch_intr)(void *, void *, u_int); - void *ch_intrarg; - uint8_t ch_buf[GCPAD_REPORT_SIZE]; - void *ch_desc; - int ch_descsize; - void *ch_si; -}; - -struct si_softc { - device_t sc_dev; - bus_space_tag_t sc_bst; - bus_space_handle_t sc_bsh; - - struct si_channel sc_chan[SI_NUM_CHAN]; -}; - -#define RD4(sc, reg) \ - bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) -#define WR4(sc, reg, val) \ - bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) static int si_match(device_t, cfdata_t, void *); static void si_attach(device_t, device_t, void *); @@ -136,6 +57,7 @@ static void si_softintr(void *); static int si_rescan(device_t, const char *, const int *); static int si_print(void *, const char *); +static void si_send_complete(struct work *, void *); static void si_get_report_desc(void *, void **, int *); static int si_open(void *, void (*)(void *, void *, unsigned), void *); @@ -145,6 +67,13 @@ static usbd_status si_set_report(void *, int, void *, int); static usbd_status si_get_report(void *, int, void *, int); static usbd_status si_write(void *, void *, int); +static kmutex_t sicomcsr_lock; +static kcondvar_t sicomcsr_cv; +static struct work sicomcsr_work; +static struct workqueue *sicomcsr_wqp; + +static int cur_chan = CH_AVAIL; + CFATTACH_DECL_NEW(si, sizeof(struct si_softc), si_match, si_attach, NULL, NULL); @@ -162,6 +91,7 @@ si_attach(device_t parent, device_t self, void *aux) struct mainbus_attach_args * const maa = aux; struct si_softc * const sc = device_private(self); unsigned chan; + int err; void *ih; KASSERT(device_unit(self) == 0); @@ -176,6 +106,15 @@ si_attach(device_t parent, device_t self, void *aux) aprint_error_dev(self, "couldn't map registers\n"); return; } + mutex_init(&sicomcsr_lock, MUTEX_DEFAULT, IPL_VM); + cv_init(&sicomcsr_cv, "sicmocsr"); + + err = workqueue_create(&sicomcsr_wqp, "si_tcint", si_send_complete, + sc, PRI_NONE, IPL_VM, 0); + if (err != 0) { + aprint_normal("si: failed to create workqueue\n"); + sicomcsr_wqp = NULL; + } for (chan = 0; chan < SI_NUM_CHAN; chan++) { struct si_channel *ch; @@ -184,6 +123,10 @@ si_attach(device_t parent, device_t self, void *aux) ch = &sc->sc_chan[chan]; ch->ch_sc = sc; ch->ch_index = chan; + ch->ch_gcport_dev = NULL; + ch->ch_uhid_dev = NULL; + ch->ch_send_status = CH_AVAIL; + ch->ch_pkid = 0; mutex_init(&ch->ch_lock, MUTEX_DEFAULT, IPL_VM); cv_init(&ch->ch_cv, "sich"); ch->ch_si = softint_establish(SOFTINT_SERIAL, @@ -199,16 +142,31 @@ si_attach(device_t parent, device_t self, void *aux) t->_set_report = si_set_report; t->_get_report = si_get_report; t->_write = si_write; + + /* Init controller */ + WR4(sc, SICOUTBUF(ch->ch_index), 0x00400300); } + ih = intr_establish_xname(maa->maa_irq, IST_LEVEL, IPL_VM, si_intr, sc, + device_xname(self)); + KASSERT(ih != NULL); + + /* write to all SICOUTBUF double buffers */ + WR4(sc, SISR, SISR_SICNOUTBUF); + WR4(sc, SIPOLL, + SIPOLL_EN(0) | + SIPOLL_EN(1) | + SIPOLL_EN(2) | + SIPOLL_EN(3) | __SHIFTIN(7, SIPOLL_X) | __SHIFTIN(1, SIPOLL_Y)); - WR4(sc, SICOMCSR, SICOMCSR_RDSTINT | SICOMCSR_RDSTINTMSK); - ih = intr_establish_xname(maa->maa_irq, IST_LEVEL, IPL_VM, si_intr, sc, - device_xname(self)); - KASSERT(ih != NULL); + WR4(sc, SICOMCSR, + RD4(sc, SICOMCSR) | + SICOMCSR_RDSTINT | + SICOMCSR_RDSTINTMSK | + SICOMCSR_TSTART); si_rescan(self, NULL, NULL); } @@ -242,7 +200,7 @@ si_print(void *aux, const char *pnp) struct si_attach_args *saa = aux; if (pnp != NULL) { - aprint_normal("uhid at %s", pnp); + aprint_normal("gcport at %s", pnp); } /* @@ -307,36 +265,59 @@ static int si_intr(void *priv) { struct si_softc *sc = priv; + struct si_channel *ch; unsigned chan; uint32_t comcsr, sr; + uint32_t inbuf[2]; + unsigned err, uhid; + int ret = 0; comcsr = RD4(sc, SICOMCSR); sr = RD4(sc, SISR); if (ISSET(comcsr, SICOMCSR_TCINT)) { - WR4(sc, SICOMCSR, comcsr | SICOMCSR_TCINT); + WR4(sc, SICOMCSR, (comcsr | SICOMCSR_TCINT) & ~SICOMCSR_TSTART); + workqueue_enqueue(sicomcsr_wqp, &sicomcsr_work, NULL); + ret = 1; } - if (ISSET(comcsr, SICOMCSR_RDSTINT)) { - for (chan = 0; chan < SI_NUM_CHAN; chan++) { - struct si_channel *ch = &sc->sc_chan[chan]; - if (ISSET(sr, SISR_RDST(chan))) { - /* Reading INBUF[HL] de-asserts RDSTINT. */ - si_make_report(sc, chan, ch->ch_buf, false); + if (!ISSET(comcsr, SICOMCSR_RDSTINT)) { + goto si_intr_done; + } + + for (chan = 0; chan < SI_NUM_CHAN; chan++) { + ch = &sc->sc_chan[chan]; + uhid = ch->ch_uhid_dev != NULL; + if (ISSET(sr, SISR_RDST(chan))) { + /* clears the sisr by reading inbuf */ + inbuf[0] = RD4(sc, SICINBUFH(chan)); + inbuf[1] = RD4(sc, SICINBUFL(chan)); + err = GCPAD_ERR(inbuf); + + if (uhid) { + si_make_report(sc, chan, ch->ch_buf, false); if (ISSET(ch->ch_state, SI_STATE_OPEN)) { softint_schedule(ch->ch_si); } } - ret = 1; + /* + * attach event: non-uhid has no errors + * detach event: hid has errors + */ + if ((err == uhid) && ch->ch_wqp != NULL) { + workqueue_enqueue(ch->ch_wqp, &ch->ch_work, + NULL); + } } + ret = 1; } +si_intr_done: WR4(sc, SISR, sr & SISR_ERROR_ACK_ALL); - return ret; } @@ -351,7 +332,6 @@ static int si_open(void *cookie, void (*intr)(void *, void *, u_int), void *arg) { struct si_channel *ch = cookie; - struct si_softc *sc = ch->ch_sc; int error; mutex_enter(&ch->ch_lock); @@ -365,18 +345,6 @@ si_open(void *cookie, void (*intr)(void *, void *, u_int), void *arg) ch->ch_intrarg = arg; ch->ch_state |= SI_STATE_OPEN; - (void)RD4(sc, SICINBUFH(ch->ch_index)); - (void)RD4(sc, SICINBUFL(ch->ch_index)); - - /* Init controller */ - WR4(sc, SICOUTBUF(ch->ch_index), 0x00400300); - - /* Enable polling */ - WR4(sc, SIPOLL, RD4(sc, SIPOLL) | SIPOLL_EN(ch->ch_index)); - - WR4(sc, SISR, SISR_WR(ch->ch_index)); - WR4(sc, SICOMCSR, RD4(sc, SICOMCSR) | SICOMCSR_TSTART); - error = 0; unlock: @@ -446,3 +414,108 @@ si_write(void *cookie, void *data, int len) { return USBD_INVAL; } + +int +si_send(struct si_softc *sc, struct si_packet *pk) +{ + unsigned chan; + uint32_t cnt; + int err; + struct bintime bt; + struct timeval tv; + + if (pk->chan > 3) { + return EINVAL; + } + + if (pk->outsize > SIIOBUF_SIZE || pk->insize > SIIOBUF_SIZE) { + return EINVAL; + } + + if (pk->out == NULL || pk->in == NULL) { + return EINVAL; + } + + chan = pk->chan; + + mutex_enter(&sicomcsr_lock); + tv.tv_sec = 0; + tv.tv_usec = 100000; + timeval2bintime(&tv, &bt); + while (cur_chan != CH_AVAIL) { + err = cv_timedwaitbt(&sicomcsr_cv, &sicomcsr_lock, &bt, + DEFAULT_TIMEOUT_EPSILON); + if (err) { + KASSERT(err == EWOULDBLOCK); + mutex_exit(&sicomcsr_lock); + return ETIMEDOUT; + } + } + cur_chan = chan; + mutex_exit(&sicomcsr_lock); + + SIIOBUF_CLEAR(sc); + pk->status = 0; + + /* outsize is number of bytes. we need number of words */ + cnt = ((pk->outsize+3)/4); + SIIOBUF_WR(sc, pk->out, cnt); + + WR4(sc, SISR, SISR_ERROR_MASK(chan)); + + WR4(sc, SICOMCSR, + SICOMCSR_CH_EN | + SICOMCSR_CMD_EN | + SICOMCSR_TCINTMSK | + SICOMCSR_RDSTINTMSK | + __SHIFTIN(pk->outsize, SICOMCSR_OUTLNGTH) | + __SHIFTIN(pk->insize, SICOMCSR_INLNGTH) | + __SHIFTIN(chan, SICOMCSR_CHANNEL) | + SICOMCSR_TSTART + ); + + return 0; +} + +static void +si_send_complete(struct work *, void *arg) +{ + struct si_softc *sc; + struct si_channel *ch; + struct si_packet *pk; + uint32_t comcsr, sisr, sisr_mask, shift_amt, status; + int completed_chan; + + mutex_enter(&sicomcsr_lock); + completed_chan = cur_chan; + if (cur_chan == CH_AVAIL) { + goto unlock_sicomcsr; + } + + sc = arg; + ch = &sc->sc_chan[completed_chan]; + + mutex_enter(&ch->ch_lock); + pk = ch->ch_pk; + if (ch->ch_send_status == CH_UNAVAIL && ch->ch_pkid == pk->id) { + SIIOBUF_RD(sc, (void *)pk->in, pk->insize); + comcsr = RD4(sc, SICOMCSR); + + if (ISSET(comcsr, SICOMCSR_COMERR)) { + sisr = RD4(sc, SISR); + shift_amt = 8 * (SI_NUM_CHAN - 1 - completed_chan); + sisr_mask = sisr & SISR_ERROR_MASK(completed_chan); + status = (sisr_mask >> shift_amt) & 0x3F; + pk->status = status; + } + + ch->ch_send_status = CH_AVAIL; + cv_signal(&ch->ch_cv); + } + mutex_exit(&ch->ch_lock); + + cur_chan = CH_AVAIL; + cv_signal(&sicomcsr_cv); +unlock_sicomcsr: + mutex_exit(&sicomcsr_lock); +} diff --git a/sys/arch/evbppc/nintendo/dev/si.h b/sys/arch/evbppc/nintendo/dev/si.h index fa7ccb037f8d3..af7c9c3704b73 100644 --- a/sys/arch/evbppc/nintendo/dev/si.h +++ b/sys/arch/evbppc/nintendo/dev/si.h @@ -30,10 +30,142 @@ #define _WII_DEV_SI_H_ #include +#include + +#define SI_NUM_CHAN 4 + +#define SICOUTBUF(n) ((n) * 0xc + 0x00) +#define SICINBUFH(n) ((n) * 0xc + 0x04) +#define SICINBUFL(n) ((n) * 0xc + 0x08) +#define SIPOLL 0x30 +#define SIPOLL_X __BITS(25, 16) +#define SIPOLL_Y __BITS(15, 8) +#define SIPOLL_EN(n) (__BIT(4 + (n))) +#define SICOMCSR 0x34 +#define SICOMCSR_TCINT __BIT(31) +#define SICOMCSR_TCINTMSK __BIT(30) +#define SICOMCSR_COMERR __BIT(29) +#define SICOMCSR_RDSTINT __BIT(28) +#define SICOMCSR_RDSTINTMSK __BIT(27) +#define SICOMCSR_CH_EN __BIT(24) +#define SICOMCSR_OUTLNGTH __BITS(22, 16) +#define SICOMCSR_INLNGTH __BITS(14, 8) +#define SICOMCSR_CMD_EN __BIT(7) +#define SICOMCSR_CHANNEL __BITS(2, 1) +#define SICOMCSR_TSTART __BIT(0) +#define SISR 0x38 +#define SISR_SICNOUTBUF __BIT(31) +#define SISR_OFF(n) ((3 - (n)) * 8) +#define SISR_WR(n) __BIT(SISR_OFF(n) + 7) +#define SISR_RDST(n) __BIT(SISR_OFF(n) + 5) +#define SISR_WRST(n) __BIT(SISR_OFF(n) + 4) +#define SISR_NOREP(n) __BIT(SISR_OFF(n) + 3) +#define SISR_COLL(n) __BIT(SISR_OFF(n) + 2) +#define SISR_OVRUN(n) __BIT(SISR_OFF(n) + 1) +#define SISR_UNRUN(n) __BIT(SISR_OFF(n) + 0) +#define SISR_ERROR_MASK(n) (SISR_NOREP(n) | SISR_COLL(n) | \ + SISR_OVRUN(n) | SISR_UNRUN(n)) +#define SISR_ERROR_ACK_ALL (SISR_ERROR_MASK(0) | SISR_ERROR_MASK(1) | \ + SISR_ERROR_MASK(2) | SISR_ERROR_MASK(3)) +#define SIEXILK 0x3c +#define SIIOBUF 0x80 +#define SIIOBUF_SIZE 128 + +#define GCPAD_REPORT_SIZE 9 +#define GCPAD_ERRSTAT(_buf) ISSET((_buf)[0], __BIT(31)) +#define GCPAD_ERRLATCH(_buf) ISSET((_buf)[0], __BIT(30)) +#define GCPAD_ERR(_buf) (GCPAD_ERRSTAT(_buf)) || (GCPAD_ERRLATCH(_buf)) +#define GCPAD_START(_buf) ISSET((_buf)[0], 0x10) +#define GCPAD_Y(_buf) ISSET((_buf)[0], 0x08) +#define GCPAD_X(_buf) ISSET((_buf)[0], 0x04) +#define GCPAD_B(_buf) ISSET((_buf)[0], 0x02) +#define GCPAD_A(_buf) ISSET((_buf)[0], 0x01) +#define GCPAD_LCLICK(_buf) ISSET((_buf)[1], 0x40) +#define GCPAD_RCLICK(_buf) ISSET((_buf)[1], 0x20) +#define GCPAD_Z(_buf) ISSET((_buf)[1], 0x10) +#define GCPAD_UP(_buf) ISSET((_buf)[1], 0x08) +#define GCPAD_DOWN(_buf) ISSET((_buf)[1], 0x04) +#define GCPAD_RIGHT(_buf) ISSET((_buf)[1], 0x02) +#define GCPAD_LEFT(_buf) ISSET((_buf)[1], 0x01) + +#define RD4(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define WR4(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) +#define SIIOBUF_CLEAR(sc) \ + bus_space_set_region_4((sc)->sc_bst, (sc)->sc_bsh, SIIOBUF, 0, \ + (SIIOBUF_SIZE / 4)) +#define SIIOBUF_WR(sc, buf, cnt) \ + bus_space_write_region_4((sc)->sc_bst, (sc)->sc_bsh, SIIOBUF, buf, cnt) +#define SIIOBUF_RD(sc, buf, cnt) \ + bus_space_read_region_1((sc)->sc_bst, (sc)->sc_bsh, SIIOBUF, buf, cnt) + +#define AWAIT_SISR(sc, chan) \ + do { while (RD4(sc, SISR) & SISR_WRST(chan)); } while (0) +#define AWAIT_SICOMCSR(sc) \ + do { while (RD4(sc, SICOMCSR) & SICOMCSR_TSTART); } while (0) + +#define CH_AVAIL -1 +#define CH_UNAVAIL 0 + +struct si_softc; +struct si_packet; + +struct si_channel { + struct si_softc *ch_sc; + device_t ch_dev; + device_t ch_gcport_dev; + device_t ch_uhid_dev; + unsigned ch_index; + struct hidev_tag ch_hidev; + kmutex_t ch_lock; + kcondvar_t ch_cv; + uint8_t ch_state; +#define SI_STATE_OPEN __BIT(0) +#define SI_STATE_STOPPED __BIT(1) + void (*ch_intr)(void *, void *, u_int); + void *ch_intrarg; + uint8_t ch_buf[GCPAD_REPORT_SIZE]; + void *ch_desc; + int ch_descsize; + void *ch_si; + struct si_packet *ch_pk; + unsigned ch_pkid; + int ch_send_status; + struct workqueue *ch_wqp; + struct work ch_work; +}; + +struct si_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + + struct si_channel sc_chan[SI_NUM_CHAN]; +}; struct si_attach_args { struct hidev_tag *saa_hidev; int saa_index; }; +struct gcport_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + + struct si_channel *ch; +}; + +struct si_packet { + unsigned id; /* identifier for the packet */ + unsigned chan; /* which controller port */ + uint32_t status; /* the sisr result for this channel */ + uint32_t insize; /* number of bytes for in buffer */ + uint32_t outsize; /* number of bytes for out buffer */ + uint32_t *in; /* buffer to store response */ + uint32_t *out; /* buffer to send out to ext device */ +}; + +int si_send(struct si_softc *sc, struct si_packet*); #endif /* _WII_DEV_SI_H_ */ diff --git a/sys/arch/evbppc/nintendo/dev/uhid_si.c b/sys/arch/evbppc/nintendo/dev/uhid_si.c index 61ff6914aec8b..21aee73f477fe 100644 --- a/sys/arch/evbppc/nintendo/dev/uhid_si.c +++ b/sys/arch/evbppc/nintendo/dev/uhid_si.c @@ -45,12 +45,13 @@ __KERNEL_RCSID(0, "$NetBSD: uhid_si.c,v 1.1 2026/01/09 22:54:30 jmcneill Exp $") static int uhid_si_match(device_t, cfdata_t, void *); static void uhid_si_attach(device_t, device_t, void *); +static int uhid_si_detach(device_t, int flags); static int uhid_si_ioctl(struct uhid_softc *, u_long, void *, int, struct lwp *); CFATTACH_DECL_NEW(uhid_si, sizeof(struct uhid_softc), - uhid_si_match, uhid_si_attach, NULL, NULL); + uhid_si_match, uhid_si_attach, uhid_si_detach, NULL); static int uhid_si_match(device_t parent, cfdata_t cf, void *aux) @@ -72,6 +73,14 @@ uhid_si_attach(device_t parent, device_t self, void *aux) uhid_attach_common(sc); } +static int +uhid_si_detach(device_t self, int flags) +{ + struct uhid_softc * const sc = device_private(self); + + return uhid_detach_common(sc); +} + static int uhid_si_ioctl(struct uhid_softc *sc, u_long cmd, void *addr, int flag, struct lwp *l)