--- sys/conf/files.amd64.orig Tue Oct 17 11:39:31 2006 +++ sys/conf/files.amd64 Tue Oct 17 11:40:25 2006 @@ -183,6 +183,7 @@ dev/kbd/kbd.c optional sc dev/kbd/kbd.c optional ukbd dev/mem/memutil.c optional mem +dev/nve/if_nfe.c optional nfe pci dev/nve/if_nve.c optional nve pci dev/rr232x/os_bsd.c optional rr232x dev/rr232x/osm_bsd.c optional rr232x --- sys/conf/files.i386.orig Tue Oct 17 11:39:24 2006 +++ sys/conf/files.i386 Tue Oct 17 11:40:07 2006 @@ -217,6 +217,7 @@ dev/mem/memutil.c optional mem dev/mse/mse.c optional mse dev/mse/mse_isa.c optional mse isa +dev/nve/if_nfe.c optional nfe pci dev/nve/if_nve.c optional nve pci dev/ppc/ppc.c optional ppc dev/ppc/ppc_puc.c optional ppc puc pci --- sys/dev/nfe/if_nfe.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/nfe/if_nfe.c Tue Oct 17 11:41:45 2006 @@ -0,0 +1,2084 @@ +/* $OpenBSD: if_nfe.c,v 1.54 2006/04/07 12:38:12 jsg Exp $ */ + +/*- + * Copyright (c) 2006 Shigeaki Tagashira + * Copyright (c) 2006 Damien Bergamini + * Copyright (c) 2005, 2006 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Driver for NVIDIA nForce MCP Fast Ethernet and Gigabit Ethernet */ + +#include +__FBSDID("$FreeBSD: src/sys/dev/nfe/if_nfe.c,v 1.14 2006/11/27 04:47:26 obrien Exp $"); + +/* Uncomment the following line to enable polling. */ +/* #define DEVICE_POLLING */ + +#define NFE_JUMBO +#define NFE_CSUM +#define NVLAN 0 + +#ifdef HAVE_KERNEL_OPTION_HEADERS +#include "opt_device_polling.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +MODULE_DEPEND(nfe, pci, 1, 1, 1); +MODULE_DEPEND(nfe, ether, 1, 1, 1); +MODULE_DEPEND(nfe, miibus, 1, 1, 1); +#include "miibus_if.h" + +static int nfe_probe(device_t); +static int nfe_attach(device_t); +static int nfe_detach(device_t); +static void nfe_shutdown(device_t); +static int nfe_miibus_readreg(device_t, int, int); +static int nfe_miibus_writereg(device_t, int, int, int); +static void nfe_miibus_statchg(device_t); +static int nfe_ioctl(struct ifnet *, u_long, caddr_t); +static void nfe_intr(void *); +static void nfe_txdesc32_sync(struct nfe_softc *, struct nfe_desc32 *, int); +static void nfe_txdesc64_sync(struct nfe_softc *, struct nfe_desc64 *, int); +static void nfe_txdesc32_rsync(struct nfe_softc *, int, int, int); +static void nfe_txdesc64_rsync(struct nfe_softc *, int, int, int); +static void nfe_rxdesc32_sync(struct nfe_softc *, struct nfe_desc32 *, int); +static void nfe_rxdesc64_sync(struct nfe_softc *, struct nfe_desc64 *, int); +static void nfe_rxeof(struct nfe_softc *); +static void nfe_txeof(struct nfe_softc *); +static int nfe_encap(struct nfe_softc *, struct mbuf *); +static void nfe_setmulti(struct nfe_softc *); +static void nfe_start(struct ifnet *); +static void nfe_start_locked(struct ifnet *); +static void nfe_watchdog(struct ifnet *); +static void nfe_init(void *); +static void nfe_init_locked(void *); +static void nfe_stop(struct ifnet *, int); +static int nfe_alloc_rx_ring(struct nfe_softc *, struct nfe_rx_ring *); +static void nfe_reset_rx_ring(struct nfe_softc *, struct nfe_rx_ring *); +static void nfe_free_rx_ring(struct nfe_softc *, struct nfe_rx_ring *); +static int nfe_alloc_tx_ring(struct nfe_softc *, struct nfe_tx_ring *); +static void nfe_reset_tx_ring(struct nfe_softc *, struct nfe_tx_ring *); +static void nfe_free_tx_ring(struct nfe_softc *, struct nfe_tx_ring *); +static int nfe_ifmedia_upd(struct ifnet *); +static int nfe_ifmedia_upd_locked(struct ifnet *); +static void nfe_ifmedia_sts(struct ifnet *, struct ifmediareq *); +static void nfe_tick(void *); +static void nfe_tick_locked(struct nfe_softc *); +static void nfe_get_macaddr(struct nfe_softc *, u_char *); +static void nfe_set_macaddr(struct nfe_softc *, u_char *); +static void nfe_dma_map_segs (void *, bus_dma_segment_t *, int, int); +#ifdef DEVICE_POLLING +static void nfe_poll_locked(struct ifnet *, enum poll_cmd, int); +#endif + +#ifdef NFE_DEBUG +int nfedebug = 0; +#define DPRINTF(x) do { if (nfedebug) printf x; } while (0) +#define DPRINTFN(n,x) do { if (nfedebug >= (n)) printf x; } while (0) +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define NFE_LOCK(_sc) mtx_lock(&(_sc)->nfe_mtx) +#define NFE_UNLOCK(_sc) mtx_unlock(&(_sc)->nfe_mtx) +#define NFE_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->nfe_mtx, MA_OWNED) + +#define letoh16(x) le16toh(x) + +#define NV_RID 0x10 + +static device_method_t nfe_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, nfe_probe), + DEVMETHOD(device_attach, nfe_attach), + DEVMETHOD(device_detach, nfe_detach), + DEVMETHOD(device_shutdown, nfe_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, nfe_miibus_readreg), + DEVMETHOD(miibus_writereg, nfe_miibus_writereg), + DEVMETHOD(miibus_statchg, nfe_miibus_statchg), + + { 0, 0 } +}; + +static driver_t nfe_driver = { + "nfe", + nfe_methods, + sizeof(struct nfe_softc) +}; + +static devclass_t nfe_devclass; + +DRIVER_MODULE(nfe, pci, nfe_driver, nfe_devclass, 0, 0); +DRIVER_MODULE(miibus, nfe, miibus_driver, miibus_devclass, 0, 0); + +static struct nfe_type nfe_devs[] = { + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE_LAN, + "NVIDIA nForce MCP Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_LAN, + "NVIDIA nForce2 MCP2 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN1, + "NVIDIA nForce2 400 MCP4 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN2, + "NVIDIA nForce2 400 MCP5 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_LAN1, + "NVIDIA nForce3 MCP3 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_250_LAN, + "NVIDIA nForce3 250 MCP6 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_LAN4, + "NVIDIA nForce3 MCP7 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE4_LAN1, + "NVIDIA nForce4 CK804 MCP8 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE4_LAN2, + "NVIDIA nForce4 CK804 MCP9 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP04_LAN1, + "NVIDIA nForce MCP04 Networking Adapter"}, // MCP10 + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP04_LAN2, + "NVIDIA nForce MCP04 Networking Adapter"}, // MCP11 + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE430_LAN1, + "NVIDIA nForce 430 MCP12 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE430_LAN2, + "NVIDIA nForce 430 MCP13 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP55_LAN1, + "NVIDIA nForce MCP55 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP55_LAN2, + "NVIDIA nForce MCP55 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN1, + "NVIDIA nForce MCP61 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN2, + "NVIDIA nForce MCP61 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN3, + "NVIDIA nForce MCP61 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP61_LAN2, + "NVIDIA nForce MCP61 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN1, + "NVIDIA nForce MCP65 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN2, + "NVIDIA nForce MCP65 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN3, + "NVIDIA nForce MCP65 Networking Adapter"}, + {PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP65_LAN2, + "NVIDIA nForce MCP65 Networking Adapter"}, + {0, 0, NULL} +}; + + +/* Probe for supported hardware ID's */ +static int +nfe_probe(device_t dev) +{ + struct nfe_type *t; + + t = nfe_devs; + /* Check for matching PCI DEVICE ID's */ + while (t->name != NULL) { + if ((pci_get_vendor(dev) == t->vid_id) && + (pci_get_device(dev) == t->dev_id)) { + device_set_desc(dev, t->name); + return (0); + } + t++; + } + + return (ENXIO); +} + + +static int +nfe_attach(device_t dev) +{ + struct nfe_softc *sc; + struct ifnet *ifp; + int unit, error = 0, rid; + + sc = device_get_softc(dev); + unit = device_get_unit(dev); + sc->nfe_dev = dev; + sc->nfe_unit = unit; + + mtx_init(&sc->nfe_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + callout_init_mtx(&sc->nfe_stat_ch, &sc->nfe_mtx, 0); + + pci_enable_busmaster(dev); + + rid = NV_RID; + sc->nfe_res = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, + 0, ~0, 1, RF_ACTIVE); + + if (sc->nfe_res == NULL) { + printf ("nfe%d: couldn't map ports/memory\n", unit); + error = ENXIO; + goto fail; + } + + sc->nfe_memt = rman_get_bustag(sc->nfe_res); + sc->nfe_memh = rman_get_bushandle(sc->nfe_res); + + /* Allocate interrupt */ + rid = 0; + sc->nfe_irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, + 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); + + if (sc->nfe_irq == NULL) { + printf("nfe%d: couldn't map interrupt\n", unit); + error = ENXIO; + goto fail; + } + + nfe_get_macaddr(sc, sc->eaddr); + + sc->nfe_flags = 0; + + switch (pci_get_device(dev)) { + case PCI_PRODUCT_NVIDIA_NFORCE3_LAN2: + case PCI_PRODUCT_NVIDIA_NFORCE3_LAN3: + case PCI_PRODUCT_NVIDIA_NFORCE3_LAN4: + case PCI_PRODUCT_NVIDIA_NFORCE3_LAN5: + sc->nfe_flags |= NFE_JUMBO_SUP | NFE_HW_CSUM; + break; + case PCI_PRODUCT_NVIDIA_MCP51_LAN1: + case PCI_PRODUCT_NVIDIA_MCP51_LAN2: + sc->nfe_flags |= NFE_40BIT_ADDR; + break; + case PCI_PRODUCT_NVIDIA_CK804_LAN1: + case PCI_PRODUCT_NVIDIA_CK804_LAN2: + case PCI_PRODUCT_NVIDIA_MCP04_LAN1: + case PCI_PRODUCT_NVIDIA_MCP04_LAN2: + sc->nfe_flags |= NFE_JUMBO_SUP | NFE_40BIT_ADDR | NFE_HW_CSUM; + break; + case PCI_PRODUCT_NVIDIA_MCP55_LAN1: + case PCI_PRODUCT_NVIDIA_MCP55_LAN2: + sc->nfe_flags |= NFE_JUMBO_SUP | NFE_40BIT_ADDR | NFE_HW_CSUM | + NFE_HW_VLAN; + break; + case PCI_PRODUCT_NVIDIA_MCP61_LAN1: + case PCI_PRODUCT_NVIDIA_MCP61_LAN2: + case PCI_PRODUCT_NVIDIA_MCP61_LAN3: + case PCI_PRODUCT_NVIDIA_MCP61_LAN4: + sc->nfe_flags |= NFE_40BIT_ADDR; + break; + case PCI_PRODUCT_NVIDIA_MCP65_LAN1: + case PCI_PRODUCT_NVIDIA_MCP65_LAN2: + case PCI_PRODUCT_NVIDIA_MCP65_LAN3: + case PCI_PRODUCT_NVIDIA_MCP65_LAN4: + sc->nfe_flags |= NFE_JUMBO_SUP | NFE_40BIT_ADDR | NFE_HW_CSUM; + break; + } + + /* + * Allocate the parent bus DMA tag appropriate for PCI. + */ +#define NFE_NSEG_NEW 32 + error = bus_dma_tag_create(NULL, /* parent */ + 1, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MAXBSIZE, NFE_NSEG_NEW, /* maxsize, nsegments */ + BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->nfe_parent_tag); + if (error) + goto fail; + + ifp = sc->nfe_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + printf("nfe%d: can not if_alloc()\n", unit); + error = ENOSPC; + goto fail; + } + sc->nfe_mtu = ifp->if_mtu = ETHERMTU; + + /* + * Allocate Tx and Rx rings. + */ + if (nfe_alloc_tx_ring(sc, &sc->txq) != 0) { + printf("nfe%d: could not allocate Tx ring\n", unit); + error = ENXIO; + goto fail; + } + + if (nfe_alloc_rx_ring(sc, &sc->rxq) != 0) { + printf("nfe%d: could not allocate Rx ring\n", unit); + nfe_free_tx_ring(sc, &sc->txq); + error = ENXIO; + goto fail; + } + + ifp->if_softc = sc; + if_initname(ifp, device_get_name(dev), device_get_unit(dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = nfe_ioctl; + ifp->if_start = nfe_start; + /* ifp->if_hwassist = NFE_CSUM_FEATURES; */ + ifp->if_watchdog = nfe_watchdog; + ifp->if_init = nfe_init; + ifp->if_baudrate = IF_Gbps(1); + ifp->if_snd.ifq_maxlen = NFE_IFQ_MAXLEN; + + ifp->if_capabilities = IFCAP_VLAN_MTU; + +#ifdef NFE_JUMBO + ifp->if_capabilities |= IFCAP_JUMBO_MTU; +#else + ifp->if_capabilities &= ~IFCAP_JUMBO_MTU; + sc->nfe_flags &= ~NFE_JUMBO_SUP; +#endif + +#if NVLAN > 0 + if (sc->nfe_flags & NFE_HW_VLAN) + ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING; +#endif +#ifdef NFE_CSUM + if (sc->nfe_flags & NFE_HW_CSUM) { + ifp->if_capabilities |= IFCAP_HWCSUM; + ifp->if_capenable |= IFCAP_HWCSUM; + ifp->if_hwassist = NFE_CSUM_FEATURES; + } +#else + sc->nfe_flags &= ~NFE_HW_CSUM; +#endif + ifp->if_capenable = ifp->if_capabilities; + +#ifdef DEVICE_POLLING + ifp->if_capabilities |= IFCAP_POLLING; +#endif + + /* Do MII setup */ + if (mii_phy_probe(dev, &sc->nfe_miibus, nfe_ifmedia_upd, + nfe_ifmedia_sts)) { + printf("nfe%d: MII without any phy!\n", unit); + error = ENXIO; + goto fail; + } + + ether_ifattach(ifp, sc->eaddr); + + error = bus_setup_intr(dev, sc->nfe_irq, INTR_TYPE_NET | INTR_MPSAFE, + nfe_intr, sc, &sc->nfe_intrhand); + + if (error) { + printf("nfe%d: couldn't set up irq\n", unit); + ether_ifdetach(ifp); + goto fail; + } + +fail: + if (error) + nfe_detach(dev); + + return (error); +} + + +static int +nfe_detach(device_t dev) +{ + struct nfe_softc *sc; + struct ifnet *ifp; + u_char eaddr[ETHER_ADDR_LEN]; + int i; + + sc = device_get_softc(dev); + KASSERT(mtx_initialized(&sc->nfe_mtx), ("nfe mutex not initialized")); + ifp = sc->nfe_ifp; + +#ifdef DEVICE_POLLING + if (ifp->if_capenable & IFCAP_POLLING) + ether_poll_deregister(ifp); +#endif + + for (i = 0; i < ETHER_ADDR_LEN; i++) { + eaddr[i] = sc->eaddr[5 - i]; + } + nfe_set_macaddr(sc, eaddr); + + if (device_is_attached(dev)) { + NFE_LOCK(sc); + nfe_stop(ifp, 1); + ifp->if_flags &= ~IFF_UP; + NFE_UNLOCK(sc); + callout_drain(&sc->nfe_stat_ch); + ether_ifdetach(ifp); + } + + if (ifp) + if_free(ifp); + if (sc->nfe_miibus) + device_delete_child(dev, sc->nfe_miibus); + bus_generic_detach(dev); + + if (sc->nfe_intrhand) + bus_teardown_intr(dev, sc->nfe_irq, sc->nfe_intrhand); + if (sc->nfe_irq) + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->nfe_irq); + if (sc->nfe_res) + bus_release_resource(dev, SYS_RES_MEMORY, NV_RID, sc->nfe_res); + + nfe_free_tx_ring(sc, &sc->txq); + nfe_free_rx_ring(sc, &sc->rxq); + + if (sc->nfe_parent_tag) + bus_dma_tag_destroy(sc->nfe_parent_tag); + + mtx_destroy(&sc->nfe_mtx); + + return (0); +} + + +static void +nfe_miibus_statchg(device_t dev) +{ + struct nfe_softc *sc; + struct mii_data *mii; + u_int32_t phy, seed, misc = NFE_MISC1_MAGIC, link = NFE_MEDIA_SET; + + sc = device_get_softc(dev); + mii = device_get_softc(sc->nfe_miibus); + + phy = NFE_READ(sc, NFE_PHY_IFACE); + phy &= ~(NFE_PHY_HDX | NFE_PHY_100TX | NFE_PHY_1000T); + + seed = NFE_READ(sc, NFE_RNDSEED); + seed &= ~NFE_SEED_MASK; + + if ((mii->mii_media_active & IFM_GMASK) == IFM_HDX) { + phy |= NFE_PHY_HDX; /* half-duplex */ + misc |= NFE_MISC1_HDX; + } + + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_1000_T: /* full-duplex only */ + link |= NFE_MEDIA_1000T; + seed |= NFE_SEED_1000T; + phy |= NFE_PHY_1000T; + break; + case IFM_100_TX: + link |= NFE_MEDIA_100TX; + seed |= NFE_SEED_100TX; + phy |= NFE_PHY_100TX; + break; + case IFM_10_T: + link |= NFE_MEDIA_10T; + seed |= NFE_SEED_10T; + break; + } + + NFE_WRITE(sc, NFE_RNDSEED, seed); /* XXX: gigabit NICs only? */ + + NFE_WRITE(sc, NFE_PHY_IFACE, phy); + NFE_WRITE(sc, NFE_MISC1, misc); + NFE_WRITE(sc, NFE_LINKSPEED, link); +} + + +static int +nfe_miibus_readreg(device_t dev, int phy, int reg) +{ + struct nfe_softc *sc = device_get_softc(dev); + u_int32_t val; + int ntries; + + NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); + + if (NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY) { + NFE_WRITE(sc, NFE_PHY_CTL, NFE_PHY_BUSY); + DELAY(100); + } + + NFE_WRITE(sc, NFE_PHY_CTL, (phy << NFE_PHYADD_SHIFT) | reg); + + for (ntries = 0; ntries < 1000; ntries++) { + DELAY(100); + if (!(NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY)) + break; + } + if (ntries == 1000) { + DPRINTFN(2, ("nfe%d: timeout waiting for PHY\n", sc->nfe_unit)); + return 0; + } + + if (NFE_READ(sc, NFE_PHY_STATUS) & NFE_PHY_ERROR) { + DPRINTFN(2, ("nfe%d: could not read PHY\n", sc->nfe_unit)); + return 0; + } + + val = NFE_READ(sc, NFE_PHY_DATA); + if (val != 0xffffffff && val != 0) + sc->mii_phyaddr = phy; + + DPRINTFN(2, ("nfe%d: mii read phy %d reg 0x%x ret 0x%x\n", + sc->nfe_unit, phy, reg, val)); + + return val; +} + + +static int +nfe_miibus_writereg(device_t dev, int phy, int reg, int val) +{ + struct nfe_softc *sc = device_get_softc(dev); + u_int32_t ctl; + int ntries; + + NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); + + if (NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY) { + NFE_WRITE(sc, NFE_PHY_CTL, NFE_PHY_BUSY); + DELAY(100); + } + + NFE_WRITE(sc, NFE_PHY_DATA, val); + ctl = NFE_PHY_WRITE | (phy << NFE_PHYADD_SHIFT) | reg; + NFE_WRITE(sc, NFE_PHY_CTL, ctl); + + for (ntries = 0; ntries < 1000; ntries++) { + DELAY(100); + if (!(NFE_READ(sc, NFE_PHY_CTL) & NFE_PHY_BUSY)) + break; + } +#ifdef NFE_DEBUG + if (nfedebug >= 2 && ntries == 1000) + printf("could not write to PHY\n"); +#endif + return 0; +} + + +static int +nfe_alloc_rx_ring(struct nfe_softc *sc, struct nfe_rx_ring *ring) +{ + struct nfe_desc32 *desc32; + struct nfe_desc64 *desc64; + struct nfe_rx_data *data; + void **desc; + bus_addr_t physaddr; + int i, error, descsize; + + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc = (void **)&ring->desc64; + descsize = sizeof (struct nfe_desc64); + } else { + desc = (void **)&ring->desc32; + descsize = sizeof (struct nfe_desc32); + } + + ring->cur = ring->next = 0; + ring->bufsz = (sc->nfe_mtu + NFE_RX_HEADERS <= MCLBYTES) ? + MCLBYTES : MJUM9BYTES; + + error = bus_dma_tag_create(sc->nfe_parent_tag, + PAGE_SIZE, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + NFE_RX_RING_COUNT * descsize, 1, /* maxsize, nsegments */ + NFE_RX_RING_COUNT * descsize, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &ring->rx_desc_tag); + if (error != 0) { + printf("nfe%d: could not create desc DMA tag\n", sc->nfe_unit); + goto fail; + } + + /* allocate memory to desc */ + error = bus_dmamem_alloc(ring->rx_desc_tag, (void **)desc, + BUS_DMA_NOWAIT, &ring->rx_desc_map); + if (error != 0) { + printf("nfe%d: could not create desc DMA map\n", sc->nfe_unit); + goto fail; + } + + /* map desc to device visible address space */ + error = bus_dmamap_load(ring->rx_desc_tag, ring->rx_desc_map, *desc, + NFE_RX_RING_COUNT * descsize, nfe_dma_map_segs, + &ring->rx_desc_segs, BUS_DMA_NOWAIT); + if (error != 0) { + printf("nfe%d: could not load desc DMA map\n", sc->nfe_unit); + goto fail; + } + + bzero(*desc, NFE_RX_RING_COUNT * descsize); + ring->rx_desc_addr = ring->rx_desc_segs.ds_addr; + ring->physaddr = ring->rx_desc_addr; + + /* + * Pre-allocate Rx buffers and populate Rx ring. + */ + for (i = 0; i < NFE_RX_RING_COUNT; i++) { + data = &sc->rxq.data[i]; + + MGETHDR(data->m, M_DONTWAIT, MT_DATA); + if (data->m == NULL) { + printf("nfe%d: could not allocate rx mbuf\n", + sc->nfe_unit); + error = ENOMEM; + goto fail; + } + + error = bus_dma_tag_create(sc->nfe_parent_tag, + ETHER_ALIGN, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MCLBYTES, 1, /* maxsize, nsegments */ + MCLBYTES, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &data->rx_data_tag); + if (error != 0) { + printf("nfe%d: could not create DMA map\n", + sc->nfe_unit); + goto fail; + } + + error = bus_dmamap_create(data->rx_data_tag, 0, + &data->rx_data_map); + if (error != 0) { + printf("nfe%d: could not allocate mbuf cluster\n", + sc->nfe_unit); + goto fail; + } + + MCLGET(data->m, M_DONTWAIT); + if (!(data->m->m_flags & M_EXT)) { + error = ENOMEM; + goto fail; + } + + error = bus_dmamap_load(data->rx_data_tag, + data->rx_data_map, mtod(data->m, void *), + ring->bufsz, nfe_dma_map_segs, &data->rx_data_segs, + BUS_DMA_NOWAIT); + if (error != 0) { + printf("nfe%d: could not load rx buf DMA map\n", + sc->nfe_unit); + goto fail; + } + + data->rx_data_addr = data->rx_data_segs.ds_addr; + physaddr = data->rx_data_addr; + + + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc64 = &sc->rxq.desc64[i]; +#if defined(__LP64__) + desc64->physaddr[0] = htole32(physaddr >> 32); +#endif + desc64->physaddr[1] = htole32(physaddr & 0xffffffff); + desc64->length = htole16(sc->rxq.bufsz); + desc64->flags = htole16(NFE_RX_READY); + } else { + desc32 = &sc->rxq.desc32[i]; + desc32->physaddr = htole32(physaddr); + desc32->length = htole16(sc->rxq.bufsz); + desc32->flags = htole16(NFE_RX_READY); + } + + } + + bus_dmamap_sync(ring->rx_desc_tag, ring->rx_desc_map, + BUS_DMASYNC_PREWRITE); + + return 0; + +fail: nfe_free_rx_ring(sc, ring); + + return error; +} + + +static void +nfe_reset_rx_ring(struct nfe_softc *sc, struct nfe_rx_ring *ring) +{ + int i; + + for (i = 0; i < NFE_RX_RING_COUNT; i++) { + if (sc->nfe_flags & NFE_40BIT_ADDR) { + ring->desc64[i].length = htole16(ring->bufsz); + ring->desc64[i].flags = htole16(NFE_RX_READY); + } else { + ring->desc32[i].length = htole16(ring->bufsz); + ring->desc32[i].flags = htole16(NFE_RX_READY); + } + } + + bus_dmamap_sync(ring->rx_desc_tag, ring->rx_desc_map, + BUS_DMASYNC_PREWRITE); + + ring->cur = ring->next = 0; +} + + +static void +nfe_free_rx_ring(struct nfe_softc *sc, struct nfe_rx_ring *ring) +{ + struct nfe_rx_data *data; + void *desc; + int i, descsize; + + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc = ring->desc64; + descsize = sizeof (struct nfe_desc64); + } else { + desc = ring->desc32; + descsize = sizeof (struct nfe_desc32); + } + + if (desc != NULL) { + bus_dmamap_sync(ring->rx_desc_tag, ring->rx_desc_map, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(ring->rx_desc_tag, ring->rx_desc_map); + bus_dmamem_free(ring->rx_desc_tag, desc, ring->rx_desc_map); + bus_dma_tag_destroy(ring->rx_desc_tag); + } + + for (i = 0; i < NFE_RX_RING_COUNT; i++) { + data = &ring->data[i]; + + if (data->rx_data_map != NULL) { + bus_dmamap_sync(data->rx_data_tag, + data->rx_data_map, BUS_DMASYNC_POSTREAD); + bus_dmamap_unload(data->rx_data_tag, + data->rx_data_map); + bus_dmamap_destroy(data->rx_data_tag, + data->rx_data_map); + bus_dma_tag_destroy(data->rx_data_tag); + } + + if (data->m != NULL) + m_freem(data->m); + } +} + + +static int +nfe_alloc_tx_ring(struct nfe_softc *sc, struct nfe_tx_ring *ring) +{ + int i, error; + void **desc; + int descsize; + + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc = (void **)&ring->desc64; + descsize = sizeof (struct nfe_desc64); + } else { + desc = (void **)&ring->desc32; + descsize = sizeof (struct nfe_desc32); + } + + ring->queued = 0; + ring->cur = ring->next = 0; + + error = bus_dma_tag_create(sc->nfe_parent_tag, + PAGE_SIZE, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + NFE_TX_RING_COUNT * descsize, 1, /* maxsize, nsegments */ + NFE_TX_RING_COUNT * descsize, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &ring->tx_desc_tag); + if (error != 0) { + printf("nfe%d: could not create desc DMA tag\n", sc->nfe_unit); + goto fail; + } + + error = bus_dmamem_alloc(ring->tx_desc_tag, (void **)desc, + BUS_DMA_NOWAIT, &ring->tx_desc_map); + if (error != 0) { + printf("nfe%d: could not create desc DMA map\n", sc->nfe_unit); + goto fail; + } + + error = bus_dmamap_load(ring->tx_desc_tag, ring->tx_desc_map, *desc, + NFE_TX_RING_COUNT * descsize, nfe_dma_map_segs, &ring->tx_desc_segs, + BUS_DMA_NOWAIT); + if (error != 0) { + printf("nfe%d: could not load desc DMA map\n", sc->nfe_unit); + goto fail; + } + + bzero(*desc, NFE_TX_RING_COUNT * descsize); + + ring->tx_desc_addr = ring->tx_desc_segs.ds_addr; + ring->physaddr = ring->tx_desc_addr; + + error = bus_dma_tag_create(sc->nfe_parent_tag, + ETHER_ALIGN, 0, + BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, + NULL, NULL, + NFE_JBYTES, NFE_MAX_SCATTER, + NFE_JBYTES, + BUS_DMA_ALLOCNOW, + NULL, NULL, + &ring->tx_data_tag); + if (error != 0) { + printf("nfe%d: could not create DMA tag\n", sc->nfe_unit); + goto fail; + } + + for (i = 0; i < NFE_TX_RING_COUNT; i++) { + error = bus_dmamap_create(ring->tx_data_tag, 0, + &ring->data[i].tx_data_map); + if (error != 0) { + printf("nfe%d: could not create DMA map\n", + sc->nfe_unit); + goto fail; + } + } + + return 0; + +fail: nfe_free_tx_ring(sc, ring); + return error; +} + + +static void +nfe_reset_tx_ring(struct nfe_softc *sc, struct nfe_tx_ring *ring) +{ + struct nfe_tx_data *data; + int i; + + for (i = 0; i < NFE_TX_RING_COUNT; i++) { + if (sc->nfe_flags & NFE_40BIT_ADDR) + ring->desc64[i].flags = 0; + else + ring->desc32[i].flags = 0; + + data = &ring->data[i]; + + if (data->m != NULL) { + bus_dmamap_sync(ring->tx_data_tag, data->active, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(ring->tx_data_tag, data->active); + m_freem(data->m); + data->m = NULL; + } + } + + bus_dmamap_sync(ring->tx_desc_tag, ring->tx_desc_map, + BUS_DMASYNC_PREWRITE); + + ring->queued = 0; + ring->cur = ring->next = 0; +} + + +static void +nfe_free_tx_ring(struct nfe_softc *sc, struct nfe_tx_ring *ring) +{ + struct nfe_tx_data *data; + void *desc; + int i, descsize; + + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc = ring->desc64; + descsize = sizeof (struct nfe_desc64); + } else { + desc = ring->desc32; + descsize = sizeof (struct nfe_desc32); + } + + if (desc != NULL) { + bus_dmamap_sync(ring->tx_desc_tag, ring->tx_desc_map, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(ring->tx_desc_tag, ring->tx_desc_map); + bus_dmamem_free(ring->tx_desc_tag, desc, ring->tx_desc_map); + bus_dma_tag_destroy(ring->tx_desc_tag); + } + + for (i = 0; i < NFE_TX_RING_COUNT; i++) { + data = &ring->data[i]; + + if (data->m != NULL) { + bus_dmamap_sync(ring->tx_data_tag, data->active, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(ring->tx_data_tag, data->active); + m_freem(data->m); + } + } + + /* ..and now actually destroy the DMA mappings */ + for (i = 0; i < NFE_TX_RING_COUNT; i++) { + data = &ring->data[i]; + if (data->tx_data_map == NULL) + continue; + bus_dmamap_destroy(ring->tx_data_tag, data->tx_data_map); + } + + bus_dma_tag_destroy(ring->tx_data_tag); +} + +#ifdef DEVICE_POLLING +static poll_handler_t nfe_poll; + + +static void +nfe_poll(struct ifnet *ifp, enum poll_cmd cmd, int count) +{ + struct nfe_softc *sc = ifp->if_softc; + + NFE_LOCK(sc); + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + nfe_poll_locked(ifp, cmd, count); + NFE_UNLOCK(sc); +} + + +static void +nfe_poll_locked(struct ifnet *ifp, enum poll_cmd cmd, int count) +{ + struct nfe_softc *sc = ifp->if_softc; + u_int32_t r; + + NFE_LOCK_ASSERT(sc); + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + return; + } + + sc->rxcycles = count; + nfe_rxeof(sc); + nfe_txeof(sc); + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + nfe_start_locked(ifp); + + if (cmd == POLL_AND_CHECK_STATUS) { + if ((r = NFE_READ(sc, NFE_IRQ_STATUS)) == 0) { + return; + } + NFE_WRITE(sc, NFE_IRQ_STATUS, r); + + if (r & NFE_IRQ_LINK) { + NFE_READ(sc, NFE_PHY_STATUS); + NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); + DPRINTF(("nfe%d: link state changed\n", sc->nfe_unit)); + } + } +} +#endif /* DEVICE_POLLING */ + + +static int +nfe_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct nfe_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *) data; + struct mii_data *mii; + int error = 0; + + switch (cmd) { + case SIOCSIFMTU: + if (ifr->ifr_mtu == ifp->if_mtu) { + error = EINVAL; + break; + } + if ((sc->nfe_flags & NFE_JUMBO_SUP) && (ifr->ifr_mtu >= + ETHERMIN && ifr->ifr_mtu <= NV_PKTLIMIT_2)) { + NFE_LOCK(sc); + sc->nfe_mtu = ifp->if_mtu = ifr->ifr_mtu; + nfe_stop(ifp, 1); + nfe_free_tx_ring(sc, &sc->txq); + nfe_free_rx_ring(sc, &sc->rxq); + NFE_UNLOCK(sc); + + /* Reallocate Tx and Rx rings. */ + if (nfe_alloc_tx_ring(sc, &sc->txq) != 0) { + printf("nfe%d: could not allocate Tx ring\n", + sc->nfe_unit); + error = ENXIO; + break; + } + + if (nfe_alloc_rx_ring(sc, &sc->rxq) != 0) { + printf("nfe%d: could not allocate Rx ring\n", + sc->nfe_unit); + nfe_free_tx_ring(sc, &sc->txq); + error = ENXIO; + break; + } + NFE_LOCK(sc); + nfe_init_locked(sc); + NFE_UNLOCK(sc); + } else { + error = EINVAL; + } + break; + case SIOCSIFFLAGS: + NFE_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + /* + * If only the PROMISC or ALLMULTI flag changes, then + * don't do a full re-init of the chip, just update + * the Rx filter. + */ + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) && + ((ifp->if_flags ^ sc->nfe_if_flags) & + (IFF_ALLMULTI | IFF_PROMISC)) != 0) + nfe_setmulti(sc); + else + nfe_init_locked(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + nfe_stop(ifp, 1); + } + sc->nfe_if_flags = ifp->if_flags; + NFE_UNLOCK(sc); + error = 0; + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + NFE_LOCK(sc); + nfe_setmulti(sc); + NFE_UNLOCK(sc); + error = 0; + } + break; + case SIOCSIFMEDIA: + case SIOCGIFMEDIA: + mii = device_get_softc(sc->nfe_miibus); + error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); + break; + case SIOCSIFCAP: + { + int init = 0; + int mask = ifr->ifr_reqcap ^ ifp->if_capenable; +#ifdef DEVICE_POLLING + if (mask & IFCAP_POLLING) { + if (ifr->ifr_reqcap & IFCAP_POLLING) { + error = ether_poll_register(nfe_poll, ifp); + if (error) + return(error); + NFE_LOCK(sc); + NFE_WRITE(sc, NFE_IRQ_MASK, 0); + ifp->if_capenable |= IFCAP_POLLING; + NFE_UNLOCK(sc); + } else { + error = ether_poll_deregister(ifp); + /* Enable interrupt even in error case */ + NFE_LOCK(sc); + NFE_WRITE(sc, NFE_IRQ_MASK, NFE_IRQ_WANTED); + ifp->if_capenable &= ~IFCAP_POLLING; + NFE_UNLOCK(sc); + } + } +#endif /* DEVICE_POLLING */ +#ifdef NFE_CSUM + if (mask & IFCAP_HWCSUM) { + ifp->if_capenable ^= IFCAP_HWCSUM; + if (IFCAP_HWCSUM & ifp->if_capenable && + IFCAP_HWCSUM & ifp->if_capabilities) + ifp->if_hwassist = NFE_CSUM_FEATURES; + else + ifp->if_hwassist = 0; + sc->nfe_flags ^= NFE_HW_CSUM; + init = 1; + } +#endif + if (init && ifp->if_drv_flags & IFF_DRV_RUNNING) + nfe_init(sc); + } + break; + + default: + error = ether_ioctl(ifp, cmd, data); + break; + } + + return error; +} + + +static void +nfe_intr(void *arg) +{ + struct nfe_softc *sc = arg; + struct ifnet *ifp = sc->nfe_ifp; + u_int32_t r; + + NFE_LOCK(sc); + +#ifdef DEVICE_POLLING + if (ifp->if_capenable & IFCAP_POLLING) { + NFE_UNLOCK(sc); + return; + } +#endif + + if ((r = NFE_READ(sc, NFE_IRQ_STATUS)) == 0) { + NFE_UNLOCK(sc); + return; /* not for us */ + } + NFE_WRITE(sc, NFE_IRQ_STATUS, r); + + DPRINTFN(5, ("nfe_intr: interrupt register %x\n", r)); + + NFE_WRITE(sc, NFE_IRQ_MASK, 0); + + if (r & NFE_IRQ_LINK) { + NFE_READ(sc, NFE_PHY_STATUS); + NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); + DPRINTF(("nfe%d: link state changed\n", sc->nfe_unit)); + } + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + /* check Rx ring */ + nfe_rxeof(sc); + /* check Tx ring */ + nfe_txeof(sc); + } + + NFE_WRITE(sc, NFE_IRQ_MASK, NFE_IRQ_WANTED); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + !IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + nfe_start_locked(ifp); + + NFE_UNLOCK(sc); + + return; +} + + +static void +nfe_txdesc32_sync(struct nfe_softc *sc, struct nfe_desc32 *desc32, int ops) +{ + + bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); +} + + +static void +nfe_txdesc64_sync(struct nfe_softc *sc, struct nfe_desc64 *desc64, int ops) +{ + + bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); +} + + +static void +nfe_txdesc32_rsync(struct nfe_softc *sc, int start, int end, int ops) +{ + + bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); +} + + +static void +nfe_txdesc64_rsync(struct nfe_softc *sc, int start, int end, int ops) +{ + + bus_dmamap_sync(sc->txq.tx_desc_tag, sc->txq.tx_desc_map, ops); +} + + +static void +nfe_rxdesc32_sync(struct nfe_softc *sc, struct nfe_desc32 *desc32, int ops) +{ + + bus_dmamap_sync(sc->rxq.rx_desc_tag, sc->rxq.rx_desc_map, ops); +} + + +static void +nfe_rxdesc64_sync(struct nfe_softc *sc, struct nfe_desc64 *desc64, int ops) +{ + + bus_dmamap_sync(sc->rxq.rx_desc_tag, sc->rxq.rx_desc_map, ops); +} + + +static void +nfe_rxeof(struct nfe_softc *sc) +{ + struct ifnet *ifp = sc->nfe_ifp; + struct nfe_desc32 *desc32=NULL; + struct nfe_desc64 *desc64=NULL; + struct nfe_rx_data *data; + struct mbuf *m, *mnew; + bus_addr_t physaddr; + u_int16_t flags; + int error, len; +#if NVLAN > 1 + u_int16_t vlan_tag = 0; + int have_tag = 0; +#endif + + NFE_LOCK_ASSERT(sc); + + for (;;) { + +#ifdef DEVICE_POLLING + if (ifp->if_capenable & IFCAP_POLLING) { + if (sc->rxcycles <= 0) + break; + sc->rxcycles--; + } +#endif + + data = &sc->rxq.data[sc->rxq.cur]; + + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc64 = &sc->rxq.desc64[sc->rxq.cur]; + nfe_rxdesc64_sync(sc, desc64, BUS_DMASYNC_POSTREAD); + + flags = letoh16(desc64->flags); + len = letoh16(desc64->length) & 0x3fff; + +#if NVLAN > 1 + if (flags & NFE_TX_VLAN_TAG) { + have_tag = 1; + vlan_tag = desc64->vtag; + } +#endif + + } else { + desc32 = &sc->rxq.desc32[sc->rxq.cur]; + nfe_rxdesc32_sync(sc, desc32, BUS_DMASYNC_POSTREAD); + + flags = letoh16(desc32->flags); + len = letoh16(desc32->length) & 0x3fff; + } + + if (flags & NFE_RX_READY) + break; + + if ((sc->nfe_flags & (NFE_JUMBO_SUP | NFE_40BIT_ADDR)) == 0) { + if (!(flags & NFE_RX_VALID_V1)) + goto skip; + if ((flags & NFE_RX_FIXME_V1) == NFE_RX_FIXME_V1) { + flags &= ~NFE_RX_ERROR; + len--; /* fix buffer length */ + } + } else { + if (!(flags & NFE_RX_VALID_V2)) + goto skip; + + if ((flags & NFE_RX_FIXME_V2) == NFE_RX_FIXME_V2) { + flags &= ~NFE_RX_ERROR; + len--; /* fix buffer length */ + } + } + + if (flags & NFE_RX_ERROR) { + ifp->if_ierrors++; + goto skip; + } + + /* + * Try to allocate a new mbuf for this ring element and load + * it before processing the current mbuf. If the ring element + * cannot be loaded, drop the received packet and reuse the + * old mbuf. In the unlikely case that the old mbuf can't be + * reloaded either, explicitly panic. + */ + MGETHDR(mnew, M_DONTWAIT, MT_DATA); + if (mnew == NULL) { + ifp->if_ierrors++; + goto skip; + } + + MCLGET(mnew, M_DONTWAIT); + if (!(mnew->m_flags & M_EXT)) { + m_freem(mnew); + ifp->if_ierrors++; + goto skip; + } + + bus_dmamap_sync(data->rx_data_tag, data->rx_data_map, + BUS_DMASYNC_POSTREAD); + bus_dmamap_unload(data->rx_data_tag, data->rx_data_map); + error = bus_dmamap_load(data->rx_data_tag, + data->rx_data_map, mtod(mnew, void *), MCLBYTES, + nfe_dma_map_segs, &data->rx_data_segs, + BUS_DMA_NOWAIT); + if (error != 0) { + m_freem(mnew); + + /* try to reload the old mbuf */ + error = bus_dmamap_load(data->rx_data_tag, + data->rx_data_map, mtod(data->m, void *), + MCLBYTES, nfe_dma_map_segs, + &data->rx_data_segs, BUS_DMA_NOWAIT); + if (error != 0) { + /* very unlikely that it will fail.. */ + panic("nfe%d: could not load old rx mbuf", + sc->nfe_unit); + } + ifp->if_ierrors++; + goto skip; + } + data->rx_data_addr = data->rx_data_segs.ds_addr; + physaddr = data->rx_data_addr; + + /* + * New mbuf successfully loaded, update Rx ring and continue + * processing. + */ + m = data->m; + data->m = mnew; + + /* finalize mbuf */ + m->m_pkthdr.len = m->m_len = len; + m->m_pkthdr.rcvif = ifp; + + if ((sc->nfe_flags & NFE_HW_CSUM) && (flags & NFE_RX_CSUMOK)) { + m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED; + if (flags & NFE_RX_IP_CSUMOK_V2) { + m->m_pkthdr.csum_flags |= CSUM_IP_VALID; + } + if (flags & NFE_RX_UDP_CSUMOK_V2 || + flags & NFE_RX_TCP_CSUMOK_V2) { + m->m_pkthdr.csum_flags |= + CSUM_DATA_VALID | CSUM_PSEUDO_HDR; + m->m_pkthdr.csum_data = 0xffff; + } + } + +#if NVLAN > 1 + if (have_tag) { + m->m_pkthdr.ether_vtag = vlan_tag; + m->m_flags |= M_VLANTAG; + } +#endif + ifp->if_ipackets++; + + NFE_UNLOCK(sc); + (*ifp->if_input)(ifp, m); + NFE_LOCK(sc); + + /* update mapping address in h/w descriptor */ + if (sc->nfe_flags & NFE_40BIT_ADDR) { +#if defined(__LP64__) + desc64->physaddr[0] = htole32(physaddr >> 32); +#endif + desc64->physaddr[1] = htole32(physaddr & 0xffffffff); + } else { + desc32->physaddr = htole32(physaddr); + } + +skip: if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc64->length = htole16(sc->rxq.bufsz); + desc64->flags = htole16(NFE_RX_READY); + nfe_rxdesc64_sync(sc, desc64, BUS_DMASYNC_PREWRITE); + } else { + desc32->length = htole16(sc->rxq.bufsz); + desc32->flags = htole16(NFE_RX_READY); + nfe_rxdesc32_sync(sc, desc32, BUS_DMASYNC_PREWRITE); + } + + sc->rxq.cur = (sc->rxq.cur + 1) % NFE_RX_RING_COUNT; + } //end for(;;) +} + + +static void +nfe_txeof(struct nfe_softc *sc) +{ + struct ifnet *ifp = sc->nfe_ifp; + struct nfe_desc32 *desc32; + struct nfe_desc64 *desc64; + struct nfe_tx_data *data = NULL; + u_int16_t flags; + + NFE_LOCK_ASSERT(sc); + + while (sc->txq.next != sc->txq.cur) { + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc64 = &sc->txq.desc64[sc->txq.next]; + nfe_txdesc64_sync(sc, desc64, BUS_DMASYNC_POSTREAD); + + flags = letoh16(desc64->flags); + } else { + desc32 = &sc->txq.desc32[sc->txq.next]; + nfe_txdesc32_sync(sc, desc32, BUS_DMASYNC_POSTREAD); + + flags = letoh16(desc32->flags); + } + + if (flags & NFE_TX_VALID) + break; + + data = &sc->txq.data[sc->txq.next]; + + if ((sc->nfe_flags & (NFE_JUMBO_SUP | NFE_40BIT_ADDR)) == 0) { + if (!(flags & NFE_TX_LASTFRAG_V1) && data->m == NULL) + goto skip; + + if ((flags & NFE_TX_ERROR_V1) != 0) { + printf("nfe%d: tx v1 error 0x%4b\n", + sc->nfe_unit, flags, NFE_V1_TXERR); + + ifp->if_oerrors++; + } else + ifp->if_opackets++; + } else { + if (!(flags & NFE_TX_LASTFRAG_V2) && data->m == NULL) + goto skip; + + if ((flags & NFE_TX_ERROR_V2) != 0) { + printf("nfe%d: tx v1 error 0x%4b\n", + sc->nfe_unit, flags, NFE_V2_TXERR); + + ifp->if_oerrors++; + } else + ifp->if_opackets++; + } + + if (data->m == NULL) { /* should not get there */ + printf("nfe%d: last fragment bit w/o associated mbuf!\n", + sc->nfe_unit); + goto skip; + } + + /* last fragment of the mbuf chain transmitted */ + bus_dmamap_sync(sc->txq.tx_data_tag, data->active, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->txq.tx_data_tag, data->active); + m_freem(data->m); + data->m = NULL; + + ifp->if_timer = 0; + +skip: sc->txq.queued--; + sc->txq.next = (sc->txq.next + 1) % NFE_TX_RING_COUNT; + } + + if (data != NULL) { /* at least one slot freed */ + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + nfe_start_locked(ifp); + } +} + + +static int +nfe_encap(struct nfe_softc *sc, struct mbuf *m0) +{ + struct nfe_desc32 *desc32=NULL; + struct nfe_desc64 *desc64=NULL; + struct nfe_tx_data *data=NULL; + bus_dmamap_t map; + bus_dma_segment_t segs[NFE_MAX_SCATTER]; + int error, i, nsegs; + u_int16_t flags = NFE_TX_VALID; + + map = sc->txq.data[sc->txq.cur].tx_data_map; + + error = bus_dmamap_load_mbuf_sg(sc->txq.tx_data_tag, map, m0, segs, + &nsegs, BUS_DMA_NOWAIT); + + if (error != 0) { + printf("nfe%d: could not map mbuf (error %d)\n", sc->nfe_unit, + error); + return error; + } + + if (sc->txq.queued + nsegs >= NFE_TX_RING_COUNT - 1) { + bus_dmamap_unload(sc->txq.tx_data_tag, map); + return ENOBUFS; + } + + if(sc->nfe_flags & NFE_HW_CSUM){ + if (m0->m_pkthdr.csum_flags & CSUM_IP) + flags |= NFE_TX_IP_CSUM; + if (m0->m_pkthdr.csum_flags & CSUM_TCP) + flags |= NFE_TX_TCP_CSUM; + if (m0->m_pkthdr.csum_flags & CSUM_UDP) + flags |= NFE_TX_TCP_CSUM; + } + + for (i = 0; i < nsegs; i++) { + data = &sc->txq.data[sc->txq.cur]; + + if (sc->nfe_flags & NFE_40BIT_ADDR) { + desc64 = &sc->txq.desc64[sc->txq.cur]; +#if defined(__LP64__) + desc64->physaddr[0] = htole32(segs[i].ds_addr >> 32); +#endif + desc64->physaddr[1] = htole32(segs[i].ds_addr & + 0xffffffff); + desc64->length = htole16(segs[i].ds_len - 1); + desc64->flags = htole16(flags); +#if NVLAN > 0 + if (m0->m_flags & M_VLANTAG) + desc64->vtag = htole32(NFE_TX_VTAG | + m0->m_pkthdr.ether_vtag); +#endif + } else { + desc32 = &sc->txq.desc32[sc->txq.cur]; + + desc32->physaddr = htole32(segs[i].ds_addr); + desc32->length = htole16(segs[i].ds_len - 1); + desc32->flags = htole16(flags); + } + + /* csum flags and vtag belong to the first fragment only */ + if (nsegs > 1) { + flags &= ~(NFE_TX_IP_CSUM | NFE_TX_TCP_CSUM); + } + + sc->txq.queued++; + sc->txq.cur = (sc->txq.cur + 1) % NFE_TX_RING_COUNT; + } + + /* the whole mbuf chain has been DMA mapped, fix last descriptor */ + if (sc->nfe_flags & NFE_40BIT_ADDR) { + flags |= NFE_TX_LASTFRAG_V2; + desc64->flags = htole16(flags); + } else { + if (sc->nfe_flags & NFE_JUMBO_SUP) + flags |= NFE_TX_LASTFRAG_V2; + else + flags |= NFE_TX_LASTFRAG_V1; + desc32->flags = htole16(flags); + } + + data->m = m0; + data->active = map; + data->nsegs = nsegs; + + bus_dmamap_sync(sc->txq.tx_data_tag, map, BUS_DMASYNC_PREWRITE); + + return 0; +} + + +static void +nfe_setmulti(struct nfe_softc *sc) +{ + struct ifnet *ifp = sc->nfe_ifp; + struct ifmultiaddr *ifma; + int i; + u_int32_t filter = NFE_RXFILTER_MAGIC; + u_int8_t addr[ETHER_ADDR_LEN], mask[ETHER_ADDR_LEN]; + u_int8_t etherbroadcastaddr[ETHER_ADDR_LEN] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + NFE_LOCK_ASSERT(sc); + + if ((ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) != 0) { + bzero(addr, ETHER_ADDR_LEN); + bzero(mask, ETHER_ADDR_LEN); + goto done; + } + + bcopy(etherbroadcastaddr, addr, ETHER_ADDR_LEN); + bcopy(etherbroadcastaddr, mask, ETHER_ADDR_LEN); + + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + u_char *addrp; + + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + + addrp = LLADDR((struct sockaddr_dl *) ifma->ifma_addr); + for (i = 0; i < ETHER_ADDR_LEN; i++) { + u_int8_t mcaddr = addrp[i]; + addr[i] &= mcaddr; + mask[i] &= ~mcaddr; + } + } + IF_ADDR_UNLOCK(ifp); + + for (i = 0; i < ETHER_ADDR_LEN; i++) { + mask[i] |= addr[i]; + } + +done: + addr[0] |= 0x01; /* make sure multicast bit is set */ + + NFE_WRITE(sc, NFE_MULTIADDR_HI, + addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]); + NFE_WRITE(sc, NFE_MULTIADDR_LO, + addr[5] << 8 | addr[4]); + NFE_WRITE(sc, NFE_MULTIMASK_HI, + mask[3] << 24 | mask[2] << 16 | mask[1] << 8 | mask[0]); + NFE_WRITE(sc, NFE_MULTIMASK_LO, + mask[5] << 8 | mask[4]); + + filter |= (ifp->if_flags & IFF_PROMISC) ? NFE_PROMISC : NFE_U2M; + NFE_WRITE(sc, NFE_RXFILTER, filter); +} + + +static void +nfe_start(struct ifnet *ifp) +{ + struct nfe_softc *sc; + + sc = ifp->if_softc; + NFE_LOCK(sc); + nfe_start_locked(ifp); + NFE_UNLOCK(sc); +} + + +static void +nfe_start_locked(struct ifnet *ifp) +{ + struct nfe_softc *sc = ifp->if_softc; + struct mbuf *m0; + int old = sc->txq.cur; + + if (!sc->nfe_link || ifp->if_drv_flags & IFF_DRV_OACTIVE) { + return; + } + + for (;;) { + IFQ_POLL(&ifp->if_snd, m0); + if (m0 == NULL) + break; + + if (nfe_encap(sc, m0) != 0) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + + /* packet put in h/w queue, remove from s/w queue */ + IFQ_DEQUEUE(&ifp->if_snd, m0); + + BPF_MTAP(ifp, m0); + } + if (sc->txq.cur == old) { /* nothing sent */ + return; + } + + if (sc->nfe_flags & NFE_40BIT_ADDR) + nfe_txdesc64_rsync(sc, old, sc->txq.cur, BUS_DMASYNC_PREWRITE); + else + nfe_txdesc32_rsync(sc, old, sc->txq.cur, BUS_DMASYNC_PREWRITE); + + /* kick Tx */ + NFE_WRITE(sc, NFE_RXTX_CTL, NFE_RXTX_KICKTX | sc->rxtxctl); + + /* + * Set a timeout in case the chip goes out to lunch. + */ + ifp->if_timer = 5; + + return; +} + + +static void +nfe_watchdog(struct ifnet *ifp) +{ + struct nfe_softc *sc = ifp->if_softc; + + printf("nfe%d: watchdog timeout\n", sc->nfe_unit); + + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + nfe_init(sc); + ifp->if_oerrors++; + + return; +} + + +static void +nfe_init(void *xsc) +{ + struct nfe_softc *sc = xsc; + + NFE_LOCK(sc); + nfe_init_locked(sc); + NFE_UNLOCK(sc); + + return; +} + + +static void +nfe_init_locked(void *xsc) +{ + struct nfe_softc *sc = xsc; + struct ifnet *ifp = sc->nfe_ifp; + struct mii_data *mii; + u_int32_t tmp; + + NFE_LOCK_ASSERT(sc); + + mii = device_get_softc(sc->nfe_miibus); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + return; + } + + nfe_stop(ifp, 0); + + NFE_WRITE(sc, NFE_TX_UNK, 0); + NFE_WRITE(sc, NFE_STATUS, 0); + + sc->rxtxctl = NFE_RXTX_BIT2; + if (sc->nfe_flags & NFE_40BIT_ADDR) + sc->rxtxctl |= NFE_RXTX_V3MAGIC; + else if (sc->nfe_flags & NFE_JUMBO_SUP) + sc->rxtxctl |= NFE_RXTX_V2MAGIC; + + if (sc->nfe_flags & NFE_HW_CSUM) + sc->rxtxctl |= NFE_RXTX_RXCSUM; + +#if NVLAN > 0 + /* + * Although the adapter is capable of stripping VLAN tags from received + * frames (NFE_RXTX_VTAG_STRIP), we do not enable this functionality on + * purpose. This will be done in software by our network stack. + */ + if (sc->nfe_flags & NFE_HW_VLAN) + sc->rxtxctl |= NFE_RXTX_VTAG_INSERT; +#endif + + NFE_WRITE(sc, NFE_RXTX_CTL, NFE_RXTX_RESET | sc->rxtxctl); + DELAY(10); + NFE_WRITE(sc, NFE_RXTX_CTL, sc->rxtxctl); + +#if NVLAN + if (sc->nfe_flags & NFE_HW_VLAN) + NFE_WRITE(sc, NFE_VTAG_CTL, NFE_VTAG_ENABLE); +#endif + + NFE_WRITE(sc, NFE_SETUP_R6, 0); + + /* set MAC address */ + nfe_set_macaddr(sc, sc->eaddr); + + /* tell MAC where rings are in memory */ +#ifdef __LP64__ + NFE_WRITE(sc, NFE_RX_RING_ADDR_HI, sc->rxq.physaddr >> 32); +#endif + NFE_WRITE(sc, NFE_RX_RING_ADDR_LO, sc->rxq.physaddr & 0xffffffff); +#ifdef __LP64__ + NFE_WRITE(sc, NFE_TX_RING_ADDR_HI, sc->txq.physaddr >> 32); +#endif + NFE_WRITE(sc, NFE_TX_RING_ADDR_LO, sc->txq.physaddr & 0xffffffff); + + NFE_WRITE(sc, NFE_RING_SIZE, + (NFE_RX_RING_COUNT - 1) << 16 | + (NFE_TX_RING_COUNT - 1)); + + NFE_WRITE(sc, NFE_RXBUFSZ, sc->rxq.bufsz); + + /* force MAC to wakeup */ + tmp = NFE_READ(sc, NFE_PWR_STATE); + NFE_WRITE(sc, NFE_PWR_STATE, tmp | NFE_PWR_WAKEUP); + DELAY(10); + tmp = NFE_READ(sc, NFE_PWR_STATE); + NFE_WRITE(sc, NFE_PWR_STATE, tmp | NFE_PWR_VALID); + +#if 1 + /* configure interrupts coalescing/mitigation */ + NFE_WRITE(sc, NFE_IMTIMER, NFE_IM_DEFAULT); +#else + /* no interrupt mitigation: one interrupt per packet */ + NFE_WRITE(sc, NFE_IMTIMER, 970); +#endif + + NFE_WRITE(sc, NFE_SETUP_R1, NFE_R1_MAGIC); + NFE_WRITE(sc, NFE_SETUP_R2, NFE_R2_MAGIC); + NFE_WRITE(sc, NFE_SETUP_R6, NFE_R6_MAGIC); + + /* update MAC knowledge of PHY; generates a NFE_IRQ_LINK interrupt */ + NFE_WRITE(sc, NFE_STATUS, sc->mii_phyaddr << 24 | NFE_STATUS_MAGIC); + + NFE_WRITE(sc, NFE_SETUP_R4, NFE_R4_MAGIC); + NFE_WRITE(sc, NFE_WOL_CTL, NFE_WOL_MAGIC); + + sc->rxtxctl &= ~NFE_RXTX_BIT2; + NFE_WRITE(sc, NFE_RXTX_CTL, sc->rxtxctl); + DELAY(10); + NFE_WRITE(sc, NFE_RXTX_CTL, NFE_RXTX_BIT1 | sc->rxtxctl); + + /* set Rx filter */ + nfe_setmulti(sc); + + nfe_ifmedia_upd(ifp); + + nfe_tick_locked(sc); + + /* enable Rx */ + NFE_WRITE(sc, NFE_RX_CTL, NFE_RX_START); + + /* enable Tx */ + NFE_WRITE(sc, NFE_TX_CTL, NFE_TX_START); + + NFE_WRITE(sc, NFE_PHY_STATUS, 0xf); + +#ifdef DEVICE_POLLING + if (ifp->if_capenable & IFCAP_POLLING) + NFE_WRITE(sc, NFE_IRQ_MASK, 0); + else +#endif + NFE_WRITE(sc, NFE_IRQ_MASK, NFE_IRQ_WANTED); /* enable interrupts */ + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + sc->nfe_link = 0; + + return; +} + + +static void +nfe_stop(struct ifnet *ifp, int disable) +{ + struct nfe_softc *sc = ifp->if_softc; + struct mii_data *mii; + + NFE_LOCK_ASSERT(sc); + + ifp->if_timer = 0; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + mii = device_get_softc(sc->nfe_miibus); + + callout_stop(&sc->nfe_stat_ch); + + /* abort Tx */ + NFE_WRITE(sc, NFE_TX_CTL, 0); + + /* disable Rx */ + NFE_WRITE(sc, NFE_RX_CTL, 0); + + /* disable interrupts */ + NFE_WRITE(sc, NFE_IRQ_MASK, 0); + + sc->nfe_link = 0; + + /* reset Tx and Rx rings */ + nfe_reset_tx_ring(sc, &sc->txq); + nfe_reset_rx_ring(sc, &sc->rxq); + + return; +} + + +static int +nfe_ifmedia_upd(struct ifnet *ifp) +{ + struct nfe_softc *sc = ifp->if_softc; + + NFE_LOCK(sc); + nfe_ifmedia_upd_locked(ifp); + NFE_UNLOCK(sc); + return (0); +} + + +static int +nfe_ifmedia_upd_locked(struct ifnet *ifp) +{ + struct nfe_softc *sc = ifp->if_softc; + struct mii_data *mii; + + NFE_LOCK_ASSERT(sc); + + mii = device_get_softc(sc->nfe_miibus); + + if (mii->mii_instance) { + struct mii_softc *miisc; + for (miisc = LIST_FIRST(&mii->mii_phys); miisc != NULL; + miisc = LIST_NEXT(miisc, mii_list)) { + mii_phy_reset(miisc); + } + } + mii_mediachg(mii); + + return (0); +} + + +static void +nfe_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct nfe_softc *sc; + struct mii_data *mii; + + sc = ifp->if_softc; + + NFE_LOCK(sc); + mii = device_get_softc(sc->nfe_miibus); + mii_pollstat(mii); + NFE_UNLOCK(sc); + + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + + return; +} + + +static void +nfe_tick(void *xsc) +{ + struct nfe_softc *sc; + + sc = xsc; + + NFE_LOCK(sc); + nfe_tick_locked(sc); + NFE_UNLOCK(sc); +} + + +void +nfe_tick_locked(struct nfe_softc *arg) +{ + struct nfe_softc *sc; + struct mii_data *mii; + struct ifnet *ifp; + + sc = arg; + + NFE_LOCK_ASSERT(sc); + + ifp = sc->nfe_ifp; + + mii = device_get_softc(sc->nfe_miibus); + mii_tick(mii); + + if (!sc->nfe_link) { + if (mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + sc->nfe_link++; + if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T + && bootverbose) + if_printf(sc->nfe_ifp, "gigabit link up\n"); + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + nfe_start_locked(ifp); + } + } + callout_reset(&sc->nfe_stat_ch, hz, nfe_tick, sc); + + return; +} + + +static void +nfe_shutdown(device_t dev) +{ + struct nfe_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + + NFE_LOCK(sc); + ifp = sc->nfe_ifp; + nfe_stop(ifp,0); + /* nfe_reset(sc); */ + NFE_UNLOCK(sc); + + return; +} + + +static void +nfe_get_macaddr(struct nfe_softc *sc, u_char *addr) +{ + uint32_t tmp; + + tmp = NFE_READ(sc, NFE_MACADDR_LO); + addr[0] = (tmp >> 8) & 0xff; + addr[1] = (tmp & 0xff); + + tmp = NFE_READ(sc, NFE_MACADDR_HI); + addr[2] = (tmp >> 24) & 0xff; + addr[3] = (tmp >> 16) & 0xff; + addr[4] = (tmp >> 8) & 0xff; + addr[5] = (tmp & 0xff); +} + + +static void +nfe_set_macaddr(struct nfe_softc *sc, u_char *addr) +{ + + NFE_WRITE(sc, NFE_MACADDR_LO, addr[5] << 8 | addr[4]); + NFE_WRITE(sc, NFE_MACADDR_HI, addr[3] << 24 | addr[2] << 16 | + addr[1] << 8 | addr[0]); +} + + +/* + * Map a single buffer address. + */ + +static void +nfe_dma_map_segs(arg, segs, nseg, error) + void *arg; + bus_dma_segment_t *segs; + int error, nseg; +{ + + if (error) + return; + + KASSERT(nseg == 1, ("too many DMA segments, %d should be 1", nseg)); + + *(bus_dma_segment_t *)arg = *segs; + + return; +} --- sys/dev/nfe/if_nfereg.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/nfe/if_nfereg.h Tue Oct 17 11:41:28 2006 @@ -0,0 +1,254 @@ +/* $OpenBSD: if_nfereg.h,v 1.16 2006/02/22 19:23:44 damien Exp $ */ + +/*- + * Copyright (c) 2005 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD: src/sys/dev/nfe/if_nfereg.h,v 1.7 2006/11/27 04:47:27 obrien Exp $ + */ + +#define NFE_PCI_BA 0x10 + +#define NFE_RX_RING_COUNT 128 +#define NFE_TX_RING_COUNT 256 + +/* RX/TX MAC addr + type + VLAN + align + slack */ +#define NFE_RX_HEADERS 64 + +/* Maximum MTU size. */ +#define NV_PKTLIMIT_1 ETH_DATA_LEN /* Hard limit not known. */ +#define NV_PKTLIMIT_2 9100 /* Actual limit according to NVidia:9202 */ +#define NFE_JBYTES (ETHER_MAX_LEN_JUMBO + ETHER_ALIGN) +#define NFE_JPOOL_COUNT (NFE_RX_RING_COUNT + NFE_RX_HEADERS) + +#define NFE_MAX_SCATTER (NFE_TX_RING_COUNT - 2) + +#define NFE_IRQ_STATUS 0x000 +#define NFE_IRQ_MASK 0x004 +#define NFE_SETUP_R6 0x008 +#define NFE_IMTIMER 0x00c +#define NFE_MISC1 0x080 +#define NFE_TX_CTL 0x084 +#define NFE_TX_STATUS 0x088 +#define NFE_RXFILTER 0x08c +#define NFE_RXBUFSZ 0x090 +#define NFE_RX_CTL 0x094 +#define NFE_RX_STATUS 0x098 +#define NFE_RNDSEED 0x09c +#define NFE_SETUP_R1 0x0a0 +#define NFE_SETUP_R2 0x0a4 +#define NFE_MACADDR_HI 0x0a8 +#define NFE_MACADDR_LO 0x0ac +#define NFE_MULTIADDR_HI 0x0b0 +#define NFE_MULTIADDR_LO 0x0b4 +#define NFE_MULTIMASK_HI 0x0b8 +#define NFE_MULTIMASK_LO 0x0bc +#define NFE_PHY_IFACE 0x0c0 +#define NFE_TX_RING_ADDR_LO 0x100 +#define NFE_RX_RING_ADDR_LO 0x104 +#define NFE_RING_SIZE 0x108 +#define NFE_TX_UNK 0x10c +#define NFE_LINKSPEED 0x110 +#define NFE_SETUP_R5 0x130 +#define NFE_SETUP_R3 0x13C +#define NFE_SETUP_R7 0x140 +#define NFE_RXTX_CTL 0x144 +#define NFE_TX_RING_ADDR_HI 0x148 +#define NFE_RX_RING_ADDR_HI 0x14c +#define NFE_PHY_STATUS 0x180 +#define NFE_SETUP_R4 0x184 +#define NFE_STATUS 0x188 +#define NFE_PHY_SPEED 0x18c +#define NFE_PHY_CTL 0x190 +#define NFE_PHY_DATA 0x194 +#define NFE_WOL_CTL 0x200 +#define NFE_PATTERN_CRC 0x204 +#define NFE_PATTERN_MASK 0x208 +#define NFE_PWR_CAP 0x268 +#define NFE_PWR_STATE 0x26c +#define NFE_VTAG_CTL 0x300 + +#define NFE_PHY_ERROR 0x00001 +#define NFE_PHY_WRITE 0x00400 +#define NFE_PHY_BUSY 0x08000 +#define NFE_PHYADD_SHIFT 5 + +#define NFE_STATUS_MAGIC 0x140000 + +#define NFE_R1_MAGIC 0x16070f +#define NFE_R2_MAGIC 0x16 +#define NFE_R4_MAGIC 0x08 +#define NFE_R6_MAGIC 0x03 +#define NFE_WOL_MAGIC 0x1111 +#define NFE_RX_START 0x01 +#define NFE_TX_START 0x01 + +#define NFE_IRQ_RXERR 0x0001 +#define NFE_IRQ_RX 0x0002 +#define NFE_IRQ_RX_NOBUF 0x0004 +#define NFE_IRQ_TXERR 0x0008 +#define NFE_IRQ_TX_DONE 0x0010 +#define NFE_IRQ_TIMER 0x0020 +#define NFE_IRQ_LINK 0x0040 +#define NFE_IRQ_TXERR2 0x0080 +#define NFE_IRQ_TX1 0x0100 + +#define NFE_IRQ_WANTED \ + (NFE_IRQ_RXERR | NFE_IRQ_RX_NOBUF | NFE_IRQ_RX | \ + NFE_IRQ_TXERR | NFE_IRQ_TXERR2 | NFE_IRQ_TX_DONE | \ + NFE_IRQ_LINK) + +#define NFE_RXTX_KICKTX 0x0001 +#define NFE_RXTX_BIT1 0x0002 +#define NFE_RXTX_BIT2 0x0004 +#define NFE_RXTX_RESET 0x0010 +#define NFE_RXTX_VTAG_STRIP 0x0040 +#define NFE_RXTX_VTAG_INSERT 0x0080 +#define NFE_RXTX_RXCSUM 0x0400 +#define NFE_RXTX_V2MAGIC 0x2100 +#define NFE_RXTX_V3MAGIC 0x2200 +#define NFE_RXFILTER_MAGIC 0x007f0008 +#define NFE_U2M (1 << 5) +#define NFE_PROMISC (1 << 7) +#define NFE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP) + +/* default interrupt moderation timer of 128us */ +#define NFE_IM_DEFAULT ((128 * 100) / 1024) + +#define NFE_VTAG_ENABLE (1 << 13) + +#define NFE_PWR_VALID (1 << 8) +#define NFE_PWR_WAKEUP (1 << 15) + +#define NFE_MEDIA_SET 0x10000 +#define NFE_MEDIA_1000T 0x00032 +#define NFE_MEDIA_100TX 0x00064 +#define NFE_MEDIA_10T 0x003e8 + +#define NFE_PHY_100TX (1 << 0) +#define NFE_PHY_1000T (1 << 1) +#define NFE_PHY_HDX (1 << 8) + +#define NFE_MISC1_MAGIC 0x003b0f3c +#define NFE_MISC1_HDX (1 << 1) + +#define NFE_SEED_MASK 0x0003ff00 +#define NFE_SEED_10T 0x00007f00 +#define NFE_SEED_100TX 0x00002d00 +#define NFE_SEED_1000T 0x00007400 + +/* Rx/Tx descriptor */ +struct nfe_desc32 { + uint32_t physaddr; + uint16_t length; + uint16_t flags; +#define NFE_RX_FIXME_V1 0x6004 +#define NFE_RX_VALID_V1 (1 << 0) +#define NFE_TX_ERROR_V1 0x7808 +#define NFE_TX_LASTFRAG_V1 (1 << 0) +#define NFE_RX_ERROR1_V1 (1<<7) +#define NFE_RX_ERROR2_V1 (1<<8) +#define NFE_RX_ERROR3_V1 (1<<9) +#define NFE_RX_ERROR4_V1 (1<<10) +} __packed; + +#define NFE_V1_TXERR "\020" \ + "\14TXERROR\13UNDERFLOW\12LATECOLLISION\11LOSTCARRIER\10DEFERRED" \ + "\08FORCEDINT\03RETRY\00LASTPACKET" + +/* V2 Rx/Tx descriptor */ +struct nfe_desc64 { + uint32_t physaddr[2]; + uint32_t vtag; +#define NFE_RX_VTAG (1 << 16) +#define NFE_TX_VTAG (1 << 18) + uint16_t length; + uint16_t flags; +#define NFE_RX_FIXME_V2 0x4300 +#define NFE_RX_VALID_V2 (1 << 13) +#define NFE_TX_ERROR_V2 0x5c04 +#define NFE_TX_LASTFRAG_V2 (1 << 13) +#define NFE_RX_IP_CSUMOK_V2 0x1000 +#define NFE_RX_UDP_CSUMOK_V2 0x1400 +#define NFE_RX_TCP_CSUMOK_V2 0x1800 +#define NFE_RX_ERROR1_V2 (1<<2) +#define NFE_RX_ERROR2_V2 (1<<3) +#define NFE_RX_ERROR3_V2 (1<<4) +#define NFE_RX_ERROR4_V2 (1<<5) +} __packed; + +#define NFE_V2_TXERR "\020" \ + "\14FORCEDINT\13LASTPACKET\12UNDERFLOW\10LOSTCARRIER\09DEFERRED\02RETRY" + +/* flags common to V1/V2 descriptors */ +#define NFE_RX_CSUMOK 0x1c00 +#define NFE_RX_ERROR (1 << 14) +#define NFE_RX_READY (1 << 15) +#define NFE_TX_TCP_CSUM (1 << 10) +#define NFE_TX_IP_CSUM (1 << 11) +#define NFE_TX_VALID (1 << 15) + +#define NFE_READ(sc, reg) \ + bus_space_read_4((sc)->nfe_memt, (sc)->nfe_memh, (reg)) + +#define NFE_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->nfe_memt, (sc)->nfe_memh, (reg), (val)) + +#ifndef PCI_VENDOR_NVIDIA +#define PCI_VENDOR_NVIDIA 0x10DE +#endif + +#define PCI_PRODUCT_NVIDIA_NFORCE_LAN 0x01C3 +#define PCI_PRODUCT_NVIDIA_NFORCE2_LAN 0x0066 +#define PCI_PRODUCT_NVIDIA_NFORCE3_LAN1 0x00D6 +#define PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN1 0x0086 +#define PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN2 0x008C +#define PCI_PRODUCT_NVIDIA_NFORCE3_250_LAN 0x00E6 +#define PCI_PRODUCT_NVIDIA_NFORCE3_LAN4 0x00DF +#define PCI_PRODUCT_NVIDIA_NFORCE4_LAN1 0x0056 +#define PCI_PRODUCT_NVIDIA_NFORCE4_LAN2 0x0057 +#define PCI_PRODUCT_NVIDIA_MCP04_LAN1 0x0037 +#define PCI_PRODUCT_NVIDIA_MCP04_LAN2 0x0038 +#define PCI_PRODUCT_NVIDIA_NFORCE430_LAN1 0x0268 +#define PCI_PRODUCT_NVIDIA_NFORCE430_LAN2 0x0269 +#define PCI_PRODUCT_NVIDIA_MCP55_LAN1 0x0372 +#define PCI_PRODUCT_NVIDIA_MCP55_LAN2 0x0373 +#define PCI_PRODUCT_NVIDIA_MCP61_LAN1 0x03e5 +#define PCI_PRODUCT_NVIDIA_MCP61_LAN2 0x03e6 +#define PCI_PRODUCT_NVIDIA_MCP61_LAN3 0x03ee +#define PCI_PRODUCT_NVIDIA_MCP61_LAN4 0x03ef +#define PCI_PRODUCT_NVIDIA_MCP65_LAN1 0x0450 +#define PCI_PRODUCT_NVIDIA_MCP65_LAN2 0x0451 +#define PCI_PRODUCT_NVIDIA_MCP65_LAN3 0x0452 +#define PCI_PRODUCT_NVIDIA_MCP65_LAN4 0x0453 + +#define PCI_PRODUCT_NVIDIA_NFORCE3_LAN2 PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN1 +#define PCI_PRODUCT_NVIDIA_NFORCE3_LAN3 PCI_PRODUCT_NVIDIA_NFORCE2_400_LAN2 +#define PCI_PRODUCT_NVIDIA_NFORCE3_LAN5 PCI_PRODUCT_NVIDIA_NFORCE3_250_LAN +#define PCI_PRODUCT_NVIDIA_CK804_LAN1 PCI_PRODUCT_NVIDIA_NFORCE4_LAN1 +#define PCI_PRODUCT_NVIDIA_CK804_LAN2 PCI_PRODUCT_NVIDIA_NFORCE4_LAN2 +#define PCI_PRODUCT_NVIDIA_MCP51_LAN1 PCI_PRODUCT_NVIDIA_NFORCE430_LAN1 +#define PCI_PRODUCT_NVIDIA_MCP51_LAN2 PCI_PRODUCT_NVIDIA_NFORCE430_LAN2 + +#define NFE_DEBUG 0x0000 +#define NFE_DEBUG_INIT 0x0001 +#define NFE_DEBUG_RUNNING 0x0002 +#define NFE_DEBUG_DEINIT 0x0004 +#define NFE_DEBUG_IOCTL 0x0008 +#define NFE_DEBUG_INTERRUPT 0x0010 +#define NFE_DEBUG_API 0x0020 +#define NFE_DEBUG_LOCK 0x0040 +#define NFE_DEBUG_BROKEN 0x0080 +#define NFE_DEBUG_MII 0x0100 +#define NFE_DEBUG_ALL 0xFFFF --- sys/dev/nfe/if_nfevar.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/nfe/if_nfevar.h Tue Oct 17 11:41:28 2006 @@ -0,0 +1,127 @@ +/* $OpenBSD: if_nfevar.h,v 1.11 2006/02/19 13:57:02 damien Exp $ */ + +/*- + * Copyright (c) 2005 Jonathan Gray + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD: src/sys/dev/nfe/if_nfevar.h,v 1.3 2006/11/27 00:16:47 obrien Exp $ + */ + +#define NFE_IFQ_MAXLEN 64 + +struct nfe_tx_data { + bus_dmamap_t tx_data_map; + bus_dmamap_t active; + int nsegs; + struct mbuf *m; +}; + +struct nfe_tx_ring { + bus_dmamap_t tx_desc_map; + bus_dma_segment_t tx_desc_segs; + bus_addr_t physaddr; + struct nfe_desc32 *desc32; + struct nfe_desc64 *desc64; + struct nfe_tx_data data[NFE_TX_RING_COUNT]; + int queued; + int cur; + int next; + bus_addr_t tx_desc_addr; + bus_addr_t tx_data_addr; + bus_dma_tag_t tx_desc_tag; + bus_dma_tag_t tx_data_tag; +}; + +struct nfe_jbuf { + caddr_t buf; + bus_addr_t physaddr; + SLIST_ENTRY(nfe_jbuf) jnext; +}; + +struct nfe_rx_data { + bus_dmamap_t rx_data_map; + bus_dma_tag_t rx_data_tag; + bus_addr_t rx_data_addr; + bus_dma_segment_t rx_data_segs; + struct mbuf *m; +}; + +struct nfe_rx_ring { + bus_dmamap_t rx_desc_map; + bus_dma_segment_t rx_desc_segs; + bus_dma_tag_t rx_desc_tag; + bus_addr_t rx_desc_addr; +#ifndef JMBUF + bus_dmamap_t rx_jumbo_map; + bus_dma_segment_t rx_jumbo_segs; + bus_dma_tag_t rx_jumbo_tag; + bus_addr_t rx_jumbo_addr; + caddr_t jpool; + struct nfe_jbuf jbuf[NFE_JPOOL_COUNT]; + SLIST_HEAD(, nfe_jbuf) jfreelist; +#endif + bus_addr_t physaddr; + struct nfe_desc32 *desc32; + struct nfe_desc64 *desc64; + struct nfe_rx_data data[NFE_RX_RING_COUNT]; + int bufsz; + int cur; + int next; +}; + +struct nfe_softc { + struct ifnet *nfe_ifp; + device_t nfe_dev; + device_t nfe_miibus; + struct mtx nfe_mtx; + bus_space_handle_t nfe_memh; + bus_space_tag_t nfe_memt; + struct resource *nfe_res; + struct resource *nfe_irq; + void *nfe_intrhand; + struct mii_data nfe_mii; + u_int8_t nfe_unit; + struct callout nfe_stat_ch; + + struct arpcom nfe_arpcom; + bus_dma_tag_t nfe_parent_tag; + /* struct timeout nfe_tick_ch; */ + void *nfe_powerhook; + + int nfe_if_flags; + u_int nfe_flags; +#define NFE_JUMBO_SUP 0x01 +#define NFE_40BIT_ADDR 0x02 +#define NFE_HW_CSUM 0x04 +#define NFE_HW_VLAN 0x08 + u_int32_t rxtxctl; + u_int32_t nfe_mtu; + u_int8_t mii_phyaddr; + u_char eaddr[ETHER_ADDR_LEN]; + struct task nfe_txtask; + int nfe_link; + + struct nfe_tx_ring txq; + struct nfe_rx_ring rxq; + +#ifdef DEVICE_POLLING + int rxcycles; +#endif +}; + +struct nfe_type { + u_int16_t vid_id; + u_int16_t dev_id; + char *name; +}; --- sys/modules/Makefile.orig Tue Oct 17 11:46:24 2006 +++ sys/modules/Makefile Tue Oct 17 11:46:27 2006 @@ -175,6 +175,7 @@ ${_ncv} \ ${_ndis} \ netgraph \ + ${_nfe} \ nfsclient \ nfsserver \ nge \ @@ -424,6 +425,7 @@ _ixgb= ixgb _mly= mly _mxge= mxge +_nfe= nfe _nve= nve .if !defined(NO_CRYPT) || defined(ALL_MODULES) .if exists(${.CURDIR}/../crypto/via) @@ -483,6 +485,7 @@ _mly= mly _mxge= mxge _ndis= ndis +_nfe= nfe _nve= nve _ppc= ppc _rr232x= rr232x --- sys/modules/nfe/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/nfe/Makefile Tue Oct 17 11:42:32 2006 @@ -0,0 +1,8 @@ +# $FreeBSD: src/sys/modules/nfe/Makefile,v 1.1 2006/06/26 23:41:07 obrien Exp $ + +.PATH: ${.CURDIR}/../../dev/nfe + +KMOD= if_nfe +SRCS= if_nfe.c opt_bdg.h device_if.h bus_if.h pci_if.h miibus_if.h + +.include --- share/man/man4/Makefile.orig Sun Oct 15 21:14:47 2006 +++ share/man/man4/Makefile Tue Oct 17 11:59:44 2006 @@ -177,6 +177,7 @@ ncv.4 \ netgraph.4 \ netintro.4 \ + ${_nfe.4} \ ${_nfsmb.4} \ ng_async.4 \ ng_atm.4 \ @@ -303,6 +304,7 @@ snd_csa.4 \ snd_ds1.4 \ snd_emu10k1.4 \ + snd_emu10kx.4 \ snd_envy24.4 \ snd_envy24ht.4 \ snd_es137x.4 \ @@ -466,6 +468,7 @@ MLINKS+=my.4 if_my.4 MLINKS+=netintro.4 net.4 \ netintro.4 networking.4 +MLINKS+=${_nfe.4} ${_if_nfe.4} MLINKS+=nge.4 if_nge.4 MLINKS+=${_nve.4} ${_if_nve.4} MLINKS+=oldcard.4 card.4 @@ -491,6 +494,8 @@ MLINKS+=sl.4 if_sl.4 MLINKS+=smp.4 SMP.4 MLINKS+=sn.4 if_sn.4 +MLINKS+=snd_sbc.4 snd_sb16.4 \ + snd_sbc.4 snd_sb8.4 MLINKS+=${_spkr.4} ${_speaker.4} MLINKS+=splash.4 screensaver.4 MLINKS+=ste.4 if_ste.4 @@ -518,8 +523,10 @@ .if ${MACHINE_ARCH} == "amd64" || ${MACHINE_ARCH} == "i386" _amdsmb.4= amdsmb.4 +_if_nfe.4= if_nfe.4 _if_nve.4= if_nve.4 _ipmi.4= ipmi.4 +_nfe.4= nfe.4 _nfsmb.4= nfsmb.4 _nve.4= nve.4 _rr232x.4= rr232x.4 --- share/man/man4/nfe.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/nfe.4 Sat Sep 30 22:30:07 2006 @@ -0,0 +1,92 @@ +.\" $OpenBSD: nfe.4,v 1.7 2006/02/28 08:13:47 jsg Exp $ +.\" +.\" Copyright (c) 2006 Jonathan Gray +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" $FreeBSD: src/share/man/man4/nfe.4,v 1.3 2006/09/30 14:30:07 ru Exp $ +.\" +.Dd June 28, 2006 +.Dt NFE 4 +.Os +.Sh NAME +.Nm nfe +.Nd "NVIDIA nForce MCP Ethernet driver" +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device miibus" +.Cd "device nfe" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +if_nfe_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver supports PCI Ethernet adapters based on the NVIDIA +nForce Media and Communications Processors (MCP), such as +the nForce, nForce 2, nForce 3, CK804, MCP04, MCP51 and MCP55 +Ethernet controller chips. +.Pp +The +.Nm +driver supports the following media types: +.Pp +.Bl -tag -width ".Cm autoselect" -compact +.It Cm autoselect +Enable autoselection of the media type and options. +.It Cm 10baseT +Set 10Mbps operation. +.It Cm 100baseTX +Set 100Mbps (Fast Ethernet) operation. +.It Cm 1000baseT +Set 1000Mbps (Gigabit Ethernet) operation (recent models only). +.El +.Sh SEE ALSO +.Xr arp 4 , +.Xr intro 4 , +.Xr miibus 4 , +.Xr netintro 4 , +.Xr pci 4 , +.Xr ifconfig 8 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Ox 3.9 , +and then in +.Fx 6.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Jonathan Gray +.Aq jsg@openbsd.org +and +.An Damien Bergamini +.Aq damien@openbsd.org . +The +.Nm +driver was ported to +.Fx +by +.An Shigeaki Tagashira +.Aq shigeaki@se.hiroshima-u.ac.jp . --- sys/conf/files.orig Mon Oct 16 13:36:19 2006 +++ sys/conf/files Mon Oct 16 18:23:38 2006 @@ -60,6 +60,21 @@ compile-with "CC=${CC} AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/emu10k1-alsa.h emu10k1-alsa%diked.h" \ no-obj no-implicit-rule before-depend \ clean "emu10k1-alsa%diked.h" +emu10k1-alsa%diked.h optional snd_emu10kx pci \ + dependency "$S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/emu10k1-alsa.h" \ + compile-with "CC='${CC}' AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/emu10k1-alsa.h emu10k1-alsa%diked.h" \ + no-obj no-implicit-rule before-depend \ + clean "emu10k1-alsa%diked.h" +p16v-alsa%diked.h optional snd_emu10kx pci \ + dependency "$S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p16v-alsa.h" \ + compile-with "CC='${CC}' AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p16v-alsa.h p16v-alsa%diked.h" \ + no-obj no-implicit-rule before-depend \ + clean "p16v-alsa%diked.h" +p17v-alsa%diked.h optional snd_emu10kx pci \ + dependency "$S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p17v-alsa.h" \ + compile-with "CC='${CC}' AWK=${AWK} sh $S/tools/emu10k1-mkalsa.sh $S/gnu/dev/sound/pci/p17v-alsa.h p17v-alsa%diked.h" \ + no-obj no-implicit-rule before-depend \ + clean "p17v-alsa%diked.h" miidevs.h optional miibus \ dependency "$S/tools/miidevs2h.awk $S/dev/mii/miidevs" \ compile-with "${AWK} -f $S/tools/miidevs2h.awk $S/dev/mii/miidevs" \ @@ -869,8 +884,9 @@ dev/sn/if_sn_isa.c optional sn isa dev/sn/if_sn_pccard.c optional sn pccard dev/snp/snp.c optional snp +dev/sound/clone.c optional sound +dev/sound/unit.c optional sound dev/sound/isa/ad1816.c optional snd_ad1816 isa -dev/sound/isa/es1888.c optional snd_ess isa dev/sound/isa/ess.c optional snd_ess isa dev/sound/isa/gusc.c optional snd_gusc isa dev/sound/isa/mss.c optional snd_mss isa @@ -883,18 +899,33 @@ #dev/sound/pci/au88x0.c optional snd_au88x0 pci dev/sound/pci/cmi.c optional snd_cmi pci dev/sound/pci/cs4281.c optional snd_cs4281 pci -dev/sound/pci/csa.c optional snd_csa pci +dev/sound/pci/csa.c optional snd_csa pci \ + warning "kernel contains GPL contaminated csaimg.h header" dev/sound/pci/csapcm.c optional snd_csa pci dev/sound/pci/ds1.c optional snd_ds1 pci dev/sound/pci/emu10k1.c optional snd_emu10k1 pci \ dependency "emu10k1-alsa%diked.h" +dev/sound/pci/emu10kx.c optional snd_emu10kx pci \ + dependency "emu10k1-alsa%diked.h" \ + dependency "p16v-alsa%diked.h" \ + dependency "p17v-alsa%diked.h" \ + warning "kernel contains GPL contaminated emu10kx headers" +dev/sound/pci/emu10kx-pcm.c optional snd_emu10kx pci \ + dependency "emu10k1-alsa%diked.h" \ + dependency "p16v-alsa%diked.h" \ + dependency "p17v-alsa%diked.h" \ + warning "kernel contains GPL contaminated emu10kx headers" +dev/sound/pci/emu10kx-midi.c optional snd_emu10kx pci \ + dependency "emu10k1-alsa%diked.h" \ + warning "kernel contains GPL contaminated emu10kx headers" dev/sound/pci/envy24.c optional snd_envy24 pci dev/sound/pci/envy24ht.c optional snd_envy24ht pci dev/sound/pci/es137x.c optional snd_es137x pci dev/sound/pci/fm801.c optional snd_fm801 pci dev/sound/pci/ich.c optional snd_ich pci dev/sound/pci/maestro.c optional snd_maestro pci -dev/sound/pci/maestro3.c optional snd_maestro3 pci +dev/sound/pci/maestro3.c optional snd_maestro3 pci \ + warning "kernel contains GPL contaminated maestro3 headers" dev/sound/pci/neomagic.c optional snd_neomagic pci dev/sound/pci/solo.c optional snd_solo pci dev/sound/pci/spicds.c optional snd_spicds pci @@ -924,6 +955,12 @@ #dev/sound/usb/upcm.c optional snd_upcm usb dev/sound/usb/uaudio.c optional snd_uaudio usb dev/sound/usb/uaudio_pcm.c optional snd_uaudio usb +dev/sound/midi/midi.c optional sound +dev/sound/midi/mpu401.c optional sound +dev/sound/midi/mpu_if.m optional sound +dev/sound/midi/mpufoi_if.m optional sound +dev/sound/midi/sequencer.c optional sound +dev/sound/midi/synth_if.m optional sound dev/sr/if_sr.c optional sr dev/sr/if_sr_pci.c optional sr pci dev/stg/tmc18c30.c optional stg --- sys/conf/kmod.mk.orig Wed Sep 13 16:40:20 2006 +++ sys/conf/kmod.mk Sat Oct 14 22:01:21 2006 @@ -336,7 +336,8 @@ dev/pci/pcib_if.m dev/ppbus/ppbus_if.m dev/smbus/smbus_if.m \ dev/sound/pcm/ac97_if.m dev/sound/pcm/channel_if.m \ dev/sound/pcm/feeder_if.m dev/sound/pcm/mixer_if.m dev/uart/uart_if.m \ - dev/usb/usb_if.m isa/isa_if.m \ + dev/sound/midi/mpu_if.m dev/sound/midi/mpufoi_if.m \ + dev/sound/midi/synth_if.m dev/usb/usb_if.m isa/isa_if.m \ kern/bus_if.m kern/cpufreq_if.m kern/device_if.m \ libkern/iconv_converter_if.m opencrypto/crypto_if.m \ pc98/pc98/canbus_if.m pci/agp_if.m --- sys/conf/options.orig Tue Oct 17 01:17:54 2006 +++ sys/conf/options Tue Oct 17 01:19:23 2006 @@ -727,3 +727,6 @@ # XBOX options for FreeBSD/i386, but some files are MI XBOX opt_xbox.h + +# snd_emu10kx sound driver options +SND_EMU10KX_MULTICHANNEL opt_emu10kx.h --- sys/sys/soundcard.h.orig Tue Jul 10 04:51:37 2007 +++ sys/sys/soundcard.h Thu Jul 12 12:04:19 2007 @@ -3,7 +3,7 @@ */ /*- - * Copyright by Hannu Savolainen 1993 + * Copyright by Hannu Savolainen 1993 / 4Front Technologies 1993-2006 * Modified for the new FreeBSD sound driver by Luigi Rizzo, 1997 * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,13 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/sys/sys/soundcard.h,v 1.44.2.1 2005/12/30 19:55:52 netchild Exp $ + * $FreeBSD: src/sys/sys/soundcard.h,v 1.48 2006/11/26 11:55:48 netchild Exp $ + */ + +/* + * Unless coordinating changes with 4Front Technologies, do NOT make any + * modifications to ioctl commands, types, etc. that would break + * compatibility with the OSS API. */ #ifndef _SYS_SOUNDCARD_H_ @@ -719,8 +725,6 @@ int caps; }; -#define MIDI_CAP_MPU401 1 /* MPU-401 intelligent mode */ - struct midi_info { char name[30]; int device; /* 0-N. INITIALIZE BEFORE CALLING */ @@ -1436,5 +1440,439 @@ #define SOUND_PCM_GETOPTR SNDCTL_DSP_GETOPTR #define SOUND_PCM_MAPINBUF SNDCTL_DSP_MAPINBUF #define SOUND_PCM_MAPOUTBUF SNDCTL_DSP_MAPOUTBUF + +/***********************************************************************/ + +/** + * XXX OSSv4 defines -- some bits taken straight out of the new + * sys/soundcard.h bundled with recent OSS releases. + * + * NB: These macros and structures will be reorganized and inserted + * in appropriate places throughout this file once the code begins + * to take shape. + * + * @todo reorganize layout more like the 4Front version + * @todo ask about maintaining __SIOWR vs. _IOWR ioctl cmd defines + */ + +/** + * @note The @c OSSV4_EXPERIMENT macro is meant to wrap new development code + * in the sound system relevant to adopting 4Front's OSSv4 specification. + * Users should not enable this! Really! + */ +#if 0 +# define OSSV4_EXPERIMENT 1 +#else +# undef OSSV4_EXPERIMENT +#endif + +#ifdef SOUND_VERSION +# undef SOUND_VERSION +# define SOUND_VERSION 0x040000 +#endif /* !SOUND_VERSION */ + +#define OSS_LONGNAME_SIZE 64 +#define OSS_LABEL_SIZE 16 +#define OSS_DEVNODE_SIZE 32 +typedef char oss_longname_t[OSS_LONGNAME_SIZE]; +typedef char oss_label_t[OSS_LABEL_SIZE]; +typedef char oss_devnode_t[OSS_DEVNODE_SIZE]; + +typedef struct audio_errinfo +{ + int play_underruns; + int rec_overruns; + unsigned int play_ptradjust; + unsigned int rec_ptradjust; + int play_errorcount; + int rec_errorcount; + int play_lasterror; + int rec_lasterror; + long play_errorparm; + long rec_errorparm; + int filler[16]; +} audio_errinfo; + +#define SNDCTL_DSP_GETPLAYVOL _IOR ('P', 24, int) +#define SNDCTL_DSP_SETPLAYVOL _IOWR('P', 24, int) +#define SNDCTL_DSP_GETERROR _IOR ('P', 25, audio_errinfo) + + +/* + **************************************************************************** + * Sync groups for audio devices + */ +typedef struct oss_syncgroup +{ + int id; + int mode; + int filler[16]; +} oss_syncgroup; + +#define SNDCTL_DSP_SYNCGROUP _IOWR('P', 28, oss_syncgroup) +#define SNDCTL_DSP_SYNCSTART _IOW ('P', 29, int) + +/* + ************************************************************************** + * "cooked" mode enables software based conversions for sample rate, sample + * format (bits) and number of channels (mono/stereo). These conversions are + * required with some devices that support only one sample rate or just stereo + * to let the applications to use other formats. The cooked mode is enabled by + * default. However it's necessary to disable this mode when mmap() is used or + * when very deterministic timing is required. SNDCTL_DSP_COOKEDMODE is an + * optional call introduced in OSS 3.9.6f. It's _error return must be ignored_ + * since normally this call will return erno=EINVAL. + * + * SNDCTL_DSP_COOKEDMODE must be called immediately after open before doing + * anything else. Otherwise the call will not have any effect. + */ +#define SNDCTL_DSP_COOKEDMODE _IOW ('P', 30, int) + +/* + ************************************************************************** + * SNDCTL_DSP_SILENCE and SNDCTL_DSP_SKIP are new calls in OSS 3.99.0 + * that can be used to implement pause/continue during playback (no effect + * on recording). + */ +#define SNDCTL_DSP_SILENCE _IO ('P', 31) +#define SNDCTL_DSP_SKIP _IO ('P', 32) + +/* + **************************************************************************** + * Abort transfer (reset) functions for input and output + */ +#define SNDCTL_DSP_HALT_INPUT _IO ('P', 33) +#define SNDCTL_DSP_RESET_INPUT SNDCTL_DSP_HALT_INPUT /* Old name */ +#define SNDCTL_DSP_HALT_OUTPUT _IO ('P', 34) +#define SNDCTL_DSP_RESET_OUTPUT SNDCTL_DSP_HALT_OUTPUT /* Old name */ + +/* + **************************************************************************** + * Low water level control + */ +#define SNDCTL_DSP_LOW_WATER _IOW ('P', 34, int) + +/** @todo Get rid of OSS_NO_LONG_LONG references? */ + +/* + **************************************************************************** + * 64 bit pointer support. Only available in environments that support + * the 64 bit (long long) integer type. + */ +#ifndef OSS_NO_LONG_LONG +typedef struct +{ + long long samples; + int fifo_samples; + int filler[32]; /* For future use */ +} oss_count_t; + +#define SNDCTL_DSP_CURRENT_IPTR _IOR ('P', 35, oss_count_t) +#define SNDCTL_DSP_CURRENT_OPTR _IOR ('P', 36, oss_count_t) +#endif + +/* + **************************************************************************** + * Interface for selecting recording sources and playback output routings. + */ +#define SNDCTL_DSP_GET_RECSRC_NAMES _IOR ('P', 37, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_RECSRC _IOR ('P', 38, int) +#define SNDCTL_DSP_SET_RECSRC _IOWR('P', 38, int) + +#define SNDCTL_DSP_GET_PLAYTGT_NAMES _IOR ('P', 39, oss_mixer_enuminfo) +#define SNDCTL_DSP_GET_PLAYTGT _IOR ('P', 40, int) +#define SNDCTL_DSP_SET_PLAYTGT _IOWR('P', 40, int) +#define SNDCTL_DSP_GETRECVOL _IOR ('P', 41, int) +#define SNDCTL_DSP_SETRECVOL _IOWR('P', 41, int) + +/* + *************************************************************************** + * Some calls for setting the channel assignment with multi channel devices + * (see the manual for details). */ +#define SNDCTL_DSP_GET_CHNORDER _IOR ('P', 42, unsigned long long) +#define SNDCTL_DSP_SET_CHNORDER _IOWR('P', 42, unsigned long long) +# define CHID_UNDEF 0 +# define CHID_L 1 # define CHID_R 2 +# define CHID_C 3 +# define CHID_LFE 4 +# define CHID_LS 5 +# define CHID_RS 6 +# define CHID_LR 7 +# define CHID_RR 8 +#define CHNORDER_UNDEF 0x0000000000000000ULL +#define CHNORDER_NORMAL 0x0000000087654321ULL + +#define MAX_PEAK_CHANNELS 128 +typedef unsigned short oss_peaks_t[MAX_PEAK_CHANNELS]; +#define SNDCTL_DSP_GETIPEAKS _IOR('P', 43, oss_peaks_t) +#define SNDCTL_DSP_GETOPEAKS _IOR('P', 44, oss_peaks_t) +#define SNDCTL_DSP_POLICY _IOW('P', 45, int) /* See the manual */ + +/* + * OSS_SYSIFO is obsolete. Use SNDCTL_SYSINFO insteads. + */ +#define OSS_GETVERSION _IOR ('M', 118, int) + +/** + * @brief Argument for SNDCTL_SYSINFO ioctl. + * + * For use w/ the SNDCTL_SYSINFO ioctl available on audio (/dev/dsp*), + * mixer, and MIDI devices. + */ +typedef struct oss_sysinfo +{ + char product[32]; /* For example OSS/Free, OSS/Linux or + OSS/Solaris */ + char version[32]; /* For example 4.0a */ + int versionnum; /* See OSS_GETVERSION */ + char options[128]; /* Reserved */ + + int numaudios; /* # of audio/dsp devices */ + int openedaudio[8]; /* Bit mask telling which audio devices + are busy */ + + int numsynths; /* # of availavle synth devices */ + int nummidis; /* # of available MIDI ports */ + int numtimers; /* # of available timer devices */ + int nummixers; /* # of mixer devices */ + + int openedmidi[8]; /* Bit mask telling which midi devices + are busy */ + int numcards; /* Number of sound cards in the system */ + int filler[241]; /* For future expansion (set to -1) */ +} oss_sysinfo; + +typedef struct oss_mixext +{ + int dev; /* Mixer device number */ + int ctrl; /* Controller number */ + int type; /* Entry type */ +# define MIXT_DEVROOT 0 /* Device root entry */ +# define MIXT_GROUP 1 /* Controller group */ +# define MIXT_ONOFF 2 /* OFF (0) or ON (1) */ +# define MIXT_ENUM 3 /* Enumerated (0 to maxvalue) */ +# define MIXT_MONOSLIDER 4 /* Mono slider (0 to 100) */ +# define MIXT_STEREOSLIDER 5 /* Stereo slider (dual 0 to 100) */ +# define MIXT_MESSAGE 6 /* (Readable) textual message */ +# define MIXT_MONOVU 7 /* VU meter value (mono) */ +# define MIXT_STEREOVU 8 /* VU meter value (stereo) */ +# define MIXT_MONOPEAK 9 /* VU meter peak value (mono) */ +# define MIXT_STEREOPEAK 10 /* VU meter peak value (stereo) */ +# define MIXT_RADIOGROUP 11 /* Radio button group */ +# define MIXT_MARKER 12 /* Separator between normal and extension entries */ +# define MIXT_VALUE 13 /* Decimal value entry */ +# define MIXT_HEXVALUE 14 /* Hexadecimal value entry */ +# define MIXT_MONODB 15 /* Mono atten. slider (0 to -144) */ +# define MIXT_STEREODB 16 /* Stereo atten. slider (dual 0 to -144) */ +# define MIXT_SLIDER 17 /* Slider (mono) with full integer range */ +# define MIXT_3D 18 + + /* Possible value range (minvalue to maxvalue) */ + /* Note that maxvalue may also be smaller than minvalue */ + int maxvalue; + int minvalue; + + int flags; +# define MIXF_READABLE 0x00000001 /* Has readable value */ +# define MIXF_WRITEABLE 0x00000002 /* Has writeable value */ +# define MIXF_POLL 0x00000004 /* May change itself */ +# define MIXF_HZ 0x00000008 /* Herz scale */ +# define MIXF_STRING 0x00000010 /* Use dynamic extensions for value */ +# define MIXF_DYNAMIC 0x00000010 /* Supports dynamic extensions */ +# define MIXF_OKFAIL 0x00000020 /* Interpret value as 1=OK, 0=FAIL */ +# define MIXF_FLAT 0x00000040 /* Flat vertical space requirements */ +# define MIXF_LEGACY 0x00000080 /* Legacy mixer control group */ + char id[16]; /* Mnemonic ID (mainly for internal use) */ + int parent; /* Entry# of parent (group) node (-1 if root) */ + + int dummy; /* Internal use */ + + int timestamp; + + char data[64]; /* Misc data (entry type dependent) */ + unsigned char enum_present[32]; /* Mask of allowed enum values */ + int control_no; /* SOUND_MIXER_VOLUME..SOUND_MIXER_MIDI */ + /* (-1 means not indicated) */ + +/* + * The desc field is reserved for internal purposes of OSS. It should not be + * used by applications. + */ + unsigned int desc; +#define MIXEXT_SCOPE_MASK 0x0000003f +#define MIXEXT_SCOPE_OTHER 0x00000000 +#define MIXEXT_SCOPE_INPUT 0x00000001 +#define MIXEXT_SCOPE_OUTPUT 0x00000002 +#define MIXEXT_SCOPE_MONITOR 0x00000003 +#define MIXEXT_SCOPE_RECSWITCH 0x00000004 + + char extname[32]; + int update_counter; + int filler[7]; +} oss_mixext; + +typedef struct oss_mixext_root +{ + char id[16]; + char name[48]; +} oss_mixext_root; + +typedef struct oss_mixer_value +{ + int dev; + int ctrl; + int value; + int flags; /* Reserved for future use. Initialize to 0 */ + int timestamp; /* Must be set to oss_mixext.timestamp */ + int filler[8]; /* Reserved for future use. Initialize to 0 */ +} oss_mixer_value; + +#define OSS_ENUM_MAXVALUE 255 +typedef struct oss_mixer_enuminfo +{ + int dev; + int ctrl; + int nvalues; + int version; /* Read the manual */ + short strindex[OSS_ENUM_MAXVALUE]; + char strings[3000]; +} oss_mixer_enuminfo; + +#define OPEN_READ PCM_ENABLE_INPUT +#define OPEN_WRITE PCM_ENABLE_OUTPUT +#define OPEN_READWRITE (OPEN_READ|OPEN_WRITE) + +/** + * @brief Argument for SNDCTL_AUDIOINFO ioctl. + * + * For use w/ the SNDCTL_AUDIOINFO ioctl available on audio (/dev/dsp*) + * devices. + */ +typedef struct oss_audioinfo +{ + int dev; /* Audio device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + int caps; /* DSP_CAP_INPUT, DSP_CAP_OUTPUT */ + int iformats; + int oformats; + int magic; /* Reserved for internal use */ + char cmd[64]; /* Command using the device (if known) */ + int card_number; + int port_number; + int mixer_dev; + int real_device; /* Obsolete field. Replaced by devnode */ + int enabled; /* 1=enabled, 0=device not ready at this + moment */ + int flags; /* For internal use only - no practical + meaning */ + int min_rate; /* Sample rate limits */ + int max_rate; + int min_channels; /* Number of channels supported */ + int max_channels; + int binding; /* DSP_BIND_FRONT, etc. 0 means undefined */ + int rate_source; + char handle[32]; + #define OSS_MAX_SAMPLE_RATES 20 /* Cannot be changed */ + unsigned int nrates; + unsigned int rates[OSS_MAX_SAMPLE_RATES]; /* Please read the manual before using these */ + oss_longname_t song_name; /* Song name (if given) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + oss_devnode_t devnode; /* Device special file name (inside + /dev) */ + int filler[186]; +} oss_audioinfo; + +typedef struct oss_mixerinfo +{ + int dev; + char id[16]; + char name[32]; + int modify_counter; + int card_number; + int port_number; + char handle[32]; + int magic; /* Reserved */ + int enabled; /* Reserved */ + int caps; +#define MIXER_CAP_VIRTUAL 0x00000001 + int flags; /* Reserved */ + int nrext; + /* + * The priority field can be used to select the default (motherboard) + * mixer device. The mixer with the highest priority is the + * most preferred one. -2 or less means that this device cannot be used + * as the default mixer. + */ + int priority; + int filler[254]; /* Reserved */ +} oss_mixerinfo; + +typedef struct oss_midi_info +{ + int dev; /* Midi device number */ + char name[64]; + int busy; /* 0, OPEN_READ, OPEN_WRITE or OPEN_READWRITE */ + int pid; + char cmd[64]; /* Command using the device (if known) */ + int caps; +#define MIDI_CAP_MPU401 0x00000001 /**** OBSOLETE ****/ +#define MIDI_CAP_INPUT 0x00000002 +#define MIDI_CAP_OUTPUT 0x00000004 +#define MIDI_CAP_INOUT (MIDI_CAP_INPUT|MIDI_CAP_OUTPUT) +#define MIDI_CAP_VIRTUAL 0x00000008 /* Pseudo device */ +#define MIDI_CAP_MTCINPUT 0x00000010 /* Supports SNDCTL_MIDI_MTCINPUT */ +#define MIDI_CAP_CLIENT 0x00000020 /* Virtual client side device */ +#define MIDI_CAP_SERVER 0x00000040 /* Virtual server side device */ +#define MIDI_CAP_INTERNAL 0x00000080 /* Internal (synth) device */ +#define MIDI_CAP_EXTERNAL 0x00000100 /* external (MIDI port) device */ +#define MIDI_CAP_PTOP 0x00000200 /* Point to point link to one device */ +#define MIDI_CAP_MTC 0x00000400 /* MTC/SMPTE (control) device */ + int magic; /* Reserved for internal use */ + int card_number; + int port_number; + int enabled; /* 1=enabled, 0=device not ready at this moment */ + int flags; /* For internal use only - no practical meaning */ + char handle[32]; + oss_longname_t song_name; /* Song name (if known) */ + oss_label_t label; /* Device label (if given) */ + int latency; /* In usecs, -1=unknown */ + int filler[244]; +} oss_midi_info; + +typedef struct oss_card_info +{ + int card; + char shortname[16]; + char longname[128]; + int flags; + int filler[256]; +} oss_card_info; + +#define SNDCTL_SYSINFO _IOR ('X', 1, oss_sysinfo) +#define OSS_SYSINFO SNDCTL_SYSINFO /* Old name */ + +#define SNDCTL_MIX_NRMIX _IOR ('X', 2, int) +#define SNDCTL_MIX_NREXT _IOWR('X', 3, int) +#define SNDCTL_MIX_EXTINFO _IOWR('X', 4, oss_mixext) +#define SNDCTL_MIX_READ _IOWR('X', 5, oss_mixer_value) +#define SNDCTL_MIX_WRITE _IOWR('X', 6, oss_mixer_value) + +#define SNDCTL_AUDIOINFO _IOWR('X', 7, oss_audioinfo) +#define SNDCTL_MIX_ENUMINFO _IOWR('X', 8, oss_mixer_enuminfo) +#define SNDCTL_MIDIINFO _IOWR('X', 9, oss_midi_info) +#define SNDCTL_MIXERINFO _IOWR('X',10, oss_mixerinfo) +#define SNDCTL_CARDINFO _IOWR('X',11, oss_card_info) + +/* + * Few more "globally" available ioctl calls. + */ +#define SNDCTL_SETSONG _IOW ('Y', 2, oss_longname_t) +#define SNDCTL_GETSONG _IOR ('Y', 2, oss_longname_t) +#define SNDCTL_SETNAME _IOW ('Y', 3, oss_longname_t) +#define SNDCTL_SETLABEL _IOW ('Y', 4, oss_label_t) +#define SNDCTL_GETLABEL _IOR ('Y', 4, oss_label_t) #endif /* !_SYS_SOUNDCARD_H_ */ --- sys/dev/sound/clone.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/clone.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,795 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/clone.c,v 1.4 2007/06/14 11:10:21 ariff Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#if defined(SND_DIAGNOSTIC) || defined(SND_DEBUG) +#include +#endif + +#include + +/* + * So here we go again, another clonedevs manager. Unlike default clonedevs, + * this clone manager is designed to withstand various abusive behavior + * (such as 'while : ; do ls /dev/whatever ; done', etc.), reusable object + * after reaching certain expiration threshold, aggressive garbage collector, + * transparent device allocator and concurrency handling across multiple + * thread/proc. Due to limited information given by dev_clone EVENTHANDLER, + * we don't have much clues whether the caller wants a real open() or simply + * making fun of us with things like stat(), mtime() etc. Assuming that: + * 1) Time window between dev_clone EH <-> real open() should be small + * enough and 2) mtime()/stat() etc. always looks like a half way / stalled + * operation, we can decide whether a new cdev must be created, old + * (expired) cdev can be reused or an existing cdev can be shared. + * + * Most of the operations and logics are generic enough and can be applied + * on other places (such as if_tap, snp, etc). Perhaps this can be + * rearranged to complement clone_*(). However, due to this still being + * specific to the sound driver (and as a proof of concept on how it can be + * done), si_drv2 is used to keep the pointer of the clone list entry to + * avoid expensive lookup. + */ + +/* clone entry */ +struct snd_clone_entry { + TAILQ_ENTRY(snd_clone_entry) link; + struct snd_clone *parent; + struct cdev *devt; + struct timespec tsp; + uint32_t flags; + pid_t pid; + int unit; +}; + +/* clone manager */ +struct snd_clone { + TAILQ_HEAD(link_head, snd_clone_entry) head; + struct timespec tsp; + int refcount; + int size; + int typemask; + int maxunit; + int deadline; + uint32_t flags; +}; + +#ifdef SND_DIAGNOSTIC +#define SND_CLONE_ASSERT(x, y) do { \ + if (!(x)) \ + panic y; \ +} while(0) +#else +#define SND_CLONE_ASSERT(x...) KASSERT(x) +#endif + +/* + * Shamelessly ripped off from vfs_subr.c + * We need at least 1/HZ precision as default timestamping. + */ +enum { SND_TSP_SEC, SND_TSP_HZ, SND_TSP_USEC, SND_TSP_NSEC }; + +static int snd_timestamp_precision = SND_TSP_HZ; +TUNABLE_INT("hw.snd.timestamp_precision", &snd_timestamp_precision); + +void +snd_timestamp(struct timespec *tsp) +{ + struct timeval tv; + + switch (snd_timestamp_precision) { + case SND_TSP_SEC: + tsp->tv_sec = time_second; + tsp->tv_nsec = 0; + break; + case SND_TSP_HZ: + getnanouptime(tsp); + break; + case SND_TSP_USEC: + microuptime(&tv); + TIMEVAL_TO_TIMESPEC(&tv, tsp); + break; + case SND_TSP_NSEC: + nanouptime(tsp); + break; + default: + snd_timestamp_precision = SND_TSP_HZ; + getnanouptime(tsp); + break; + } +} + +#if defined(SND_DIAGNOSTIC) || defined(SND_DEBUG) +static int +sysctl_hw_snd_timestamp_precision(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = snd_timestamp_precision; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err == 0 && req->newptr != NULL) { + switch (val) { + case SND_TSP_SEC: + case SND_TSP_HZ: + case SND_TSP_USEC: + case SND_TSP_NSEC: + snd_timestamp_precision = val; + break; + default: + break; + } + } + + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, timestamp_precision, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_timestamp_precision, "I", + "timestamp precision (0=s 1=hz 2=us 3=ns)"); +#endif + +/* + * snd_clone_create() : Return opaque allocated clone manager. + */ +struct snd_clone * +snd_clone_create(int typemask, int maxunit, int deadline, uint32_t flags) +{ + struct snd_clone *c; + + SND_CLONE_ASSERT(!(typemask & ~SND_CLONE_MAXUNIT), + ("invalid typemask: 0x%08x", typemask)); + SND_CLONE_ASSERT(maxunit == -1 || + !(maxunit & ~(~typemask & SND_CLONE_MAXUNIT)), + ("maxunit overflow: typemask=0x%08x maxunit=%d", + typemask, maxunit)); + SND_CLONE_ASSERT(!(flags & ~SND_CLONE_MASK), + ("invalid clone flags=0x%08x", flags)); + + c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); + c->refcount = 0; + c->size = 0; + c->typemask = typemask; + c->maxunit = (maxunit == -1) ? (~typemask & SND_CLONE_MAXUNIT) : + maxunit; + c->deadline = deadline; + c->flags = flags; + snd_timestamp(&c->tsp); + TAILQ_INIT(&c->head); + + return (c); +} + +int +snd_clone_busy(struct snd_clone *c) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (c->size == 0) + return (0); + + TAILQ_FOREACH(ce, &c->head, link) { + if ((ce->flags & SND_CLONE_BUSY) || + (ce->devt != NULL && ce->devt->si_threadcount != 0)) + return (EBUSY); + } + + return (0); +} + +/* + * snd_clone_enable()/disable() : Suspend/resume clone allocation through + * snd_clone_alloc(). Everything else will not be affected by this. + */ +int +snd_clone_enable(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (c->flags & SND_CLONE_ENABLE) + return (EINVAL); + + c->flags |= SND_CLONE_ENABLE; + + return (0); +} + +int +snd_clone_disable(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (!(c->flags & SND_CLONE_ENABLE)) + return (EINVAL); + + c->flags &= ~SND_CLONE_ENABLE; + + return (0); +} + +/* + * Getters / Setters. Not worth explaining :) + */ +int +snd_clone_getsize(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->size); +} + +int +snd_clone_getmaxunit(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->maxunit); +} + +int +snd_clone_setmaxunit(struct snd_clone *c, int maxunit) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(maxunit == -1 || + !(maxunit & ~(~c->typemask & SND_CLONE_MAXUNIT)), + ("maxunit overflow: typemask=0x%08x maxunit=%d", + c->typemask, maxunit)); + + c->maxunit = (maxunit == -1) ? (~c->typemask & SND_CLONE_MAXUNIT) : + maxunit; + + return (c->maxunit); +} + +int +snd_clone_getdeadline(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->deadline); +} + +int +snd_clone_setdeadline(struct snd_clone *c, int deadline) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + c->deadline = deadline; + + return (c->deadline); +} + +int +snd_clone_gettime(struct snd_clone *c, struct timespec *tsp) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(tsp != NULL, ("NULL timespec")); + + *tsp = c->tsp; + + return (0); +} + +uint32_t +snd_clone_getflags(struct snd_clone *c) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + return (c->flags); +} + +uint32_t +snd_clone_setflags(struct snd_clone *c, uint32_t flags) +{ + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(!(flags & ~SND_CLONE_MASK), + ("invalid clone flags=0x%08x", flags)); + + c->flags = flags; + + return (c->flags); +} + +int +snd_clone_getdevtime(struct cdev *dev, struct timespec *tsp) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + SND_CLONE_ASSERT(tsp != NULL, ("NULL timespec")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + *tsp = ce->tsp; + + return (0); +} + +uint32_t +snd_clone_getdevflags(struct cdev *dev) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0xffffffff); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + return (ce->flags); +} + +uint32_t +snd_clone_setdevflags(struct cdev *dev, uint32_t flags) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + SND_CLONE_ASSERT(!(flags & ~SND_CLONE_DEVMASK), + ("invalid clone dev flags=0x%08x", flags)); + + ce = dev->si_drv2; + if (ce == NULL) + return (0xffffffff); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + ce->flags = flags; + + return (ce->flags); +} + +/* Elapsed time conversion to ms */ +#define SND_CLONE_ELAPSED(x, y) \ + ((((x)->tv_sec - (y)->tv_sec) * 1000) + \ + (((y)->tv_nsec > (x)->tv_nsec) ? \ + (((1000000000L + (x)->tv_nsec - \ + (y)->tv_nsec) / 1000000) - 1000) : \ + (((x)->tv_nsec - (y)->tv_nsec) / 1000000))) + +#define SND_CLONE_EXPIRED(x, y, z) \ + ((x)->deadline < 1 || \ + ((y)->tv_sec - (z)->tv_sec) > ((x)->deadline / 1000) || \ + SND_CLONE_ELAPSED(y, z) > (x)->deadline) + +/* + * snd_clone_gc() : Garbage collector for stalled, expired objects. Refer to + * clone.h for explanations on GC settings. + */ +int +snd_clone_gc(struct snd_clone *c) +{ + struct snd_clone_entry *ce, *tce; + struct timespec now; + int pruned; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + if (!(c->flags & SND_CLONE_GC_ENABLE) || c->size == 0) + return (0); + + snd_timestamp(&now); + + /* + * Bail out if the last clone handler was invoked below the deadline + * threshold. + */ + if ((c->flags & SND_CLONE_GC_EXPIRED) && + !SND_CLONE_EXPIRED(c, &now, &c->tsp)) + return (0); + + pruned = 0; + + /* + * Visit each object in reverse order. If the object is still being + * referenced by a valid open(), skip it. Look for expired objects + * and either revoke its clone invocation status or mercilessly + * throw it away. + */ + TAILQ_FOREACH_REVERSE_SAFE(ce, &c->head, link_head, link, tce) { + if (!(ce->flags & SND_CLONE_BUSY) && + (!(ce->flags & SND_CLONE_INVOKE) || + SND_CLONE_EXPIRED(c, &now, &ce->tsp))) { + if ((c->flags & SND_CLONE_GC_REVOKE) || + ce->devt->si_threadcount != 0) { + ce->flags &= ~SND_CLONE_INVOKE; + ce->pid = -1; + } else { + TAILQ_REMOVE(&c->head, ce, link); + destroy_dev(ce->devt); + free(ce, M_DEVBUF); + c->size--; + } + pruned++; + } + } + + /* return total pruned objects */ + return (pruned); +} + +void +snd_clone_destroy(struct snd_clone *c) +{ + struct snd_clone_entry *ce, *tmp; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + + ce = TAILQ_FIRST(&c->head); + while (ce != NULL) { + tmp = TAILQ_NEXT(ce, link); + if (ce->devt != NULL) + destroy_dev(ce->devt); + free(ce, M_DEVBUF); + ce = tmp; + } + + free(c, M_DEVBUF); +} + +/* + * snd_clone_acquire() : The vital part of concurrency management. Must be + * called somewhere at the beginning of open() handler. ENODEV is not really + * fatal since it just tell the caller that this is not cloned stuff. + * EBUSY is *real*, don't forget that! + */ +int +snd_clone_acquire(struct cdev *dev) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + ce->flags &= ~SND_CLONE_INVOKE; + + if (ce->flags & SND_CLONE_BUSY) + return (EBUSY); + + ce->flags |= SND_CLONE_BUSY; + + return (0); +} + +/* + * snd_clone_release() : Release busy status. Must be called somewhere at + * the end of close() handler, or somewhere after fail open(). + */ +int +snd_clone_release(struct cdev *dev) +{ + struct snd_clone_entry *ce; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (ENODEV); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + ce->flags &= ~SND_CLONE_INVOKE; + + if (!(ce->flags & SND_CLONE_BUSY)) + return (EBADF); + + ce->flags &= ~SND_CLONE_BUSY; + ce->pid = -1; + + return (0); +} + +/* + * snd_clone_ref/unref() : Garbage collector reference counter. To make + * garbage collector run automatically, the sequence must be something like + * this (both in open() and close() handlers): + * + * open() - 1) snd_clone_acquire() + * 2) .... check check ... if failed, snd_clone_release() + * 3) Success. Call snd_clone_ref() + * + * close() - 1) .... check check check .... + * 2) Success. snd_clone_release() + * 3) snd_clone_unref() . Garbage collector will run at this point + * if this is the last referenced object. + */ +int +snd_clone_ref(struct cdev *dev) +{ + struct snd_clone_entry *ce; + struct snd_clone *c; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0); + + c = ce->parent; + SND_CLONE_ASSERT(c != NULL, ("NULL parent")); + SND_CLONE_ASSERT(c->refcount >= 0, ("refcount < 0")); + + return (++c->refcount); +} + +int +snd_clone_unref(struct cdev *dev) +{ + struct snd_clone_entry *ce; + struct snd_clone *c; + + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + + ce = dev->si_drv2; + if (ce == NULL) + return (0); + + c = ce->parent; + SND_CLONE_ASSERT(c != NULL, ("NULL parent")); + SND_CLONE_ASSERT(c->refcount > 0, ("refcount <= 0")); + + c->refcount--; + + /* + * Run automatic garbage collector, if needed. + */ + if ((c->flags & SND_CLONE_GC_UNREF) && + (!(c->flags & SND_CLONE_GC_LASTREF) || + (c->refcount == 0 && (c->flags & SND_CLONE_GC_LASTREF)))) + (void)snd_clone_gc(c); + + return (c->refcount); +} + +void +snd_clone_register(struct snd_clone_entry *ce, struct cdev *dev) +{ + SND_CLONE_ASSERT(ce != NULL, ("NULL snd_clone_entry")); + SND_CLONE_ASSERT(dev != NULL, ("NULL dev")); + SND_CLONE_ASSERT(dev->si_drv2 == NULL, ("dev->si_drv2 not NULL")); + SND_CLONE_ASSERT((ce->flags & SND_CLONE_ALLOC) == SND_CLONE_ALLOC, + ("invalid clone alloc flags=0x%08x", ce->flags)); + SND_CLONE_ASSERT(ce->devt == NULL, ("ce->devt not NULL")); + SND_CLONE_ASSERT(ce->unit == dev2unit(dev), + ("invalid unit ce->unit=0x%08x dev2unit=0x%08x", + ce->unit, dev2unit(dev))); + + SND_CLONE_ASSERT(ce->parent != NULL, ("NULL parent")); + + dev->si_drv2 = ce; + ce->devt = dev; + ce->flags &= ~SND_CLONE_ALLOC; + ce->flags |= SND_CLONE_INVOKE; +} + +struct snd_clone_entry * +snd_clone_alloc(struct snd_clone *c, struct cdev **dev, int *unit, int tmask) +{ + struct snd_clone_entry *ce, *after, *bce, *cce, *nce, *tce; + struct timespec now; + int cunit, allocunit; + pid_t curpid; + + SND_CLONE_ASSERT(c != NULL, ("NULL snd_clone")); + SND_CLONE_ASSERT(dev != NULL, ("NULL dev pointer")); + SND_CLONE_ASSERT((c->typemask & tmask) == tmask, + ("invalid tmask: typemask=0x%08x tmask=0x%08x", + c->typemask, tmask)); + SND_CLONE_ASSERT(unit != NULL, ("NULL unit pointer")); + SND_CLONE_ASSERT(*unit == -1 || !(*unit & (c->typemask | tmask)), + ("typemask collision: typemask=0x%08x tmask=0x%08x *unit=%d", + c->typemask, tmask, *unit)); + + if (!(c->flags & SND_CLONE_ENABLE) || + (*unit != -1 && *unit > c->maxunit)) + return (NULL); + + ce = NULL; + after = NULL; + bce = NULL; /* "b"usy candidate */ + cce = NULL; /* "c"urthread/proc candidate */ + nce = NULL; /* "n"ull, totally unbusy candidate */ + tce = NULL; /* Last "t"ry candidate */ + cunit = 0; + allocunit = (*unit == -1) ? 0 : *unit; + curpid = curthread->td_proc->p_pid; + + snd_timestamp(&now); + + TAILQ_FOREACH(ce, &c->head, link) { + /* + * Sort incrementally according to device type. + */ + if (tmask > (ce->unit & c->typemask)) { + if (cunit == 0) + after = ce; + continue; + } else if (tmask < (ce->unit & c->typemask)) + break; + + /* + * Shoot.. this is where the grumpiness begin. Just + * return immediately. + */ + if (*unit != -1 && *unit == (ce->unit & ~tmask)) + goto snd_clone_alloc_out; + + cunit++; + /* + * Simmilar device type. Sort incrementally according + * to allocation unit. While here, look for free slot + * and possible collision for new / future allocation. + */ + if (*unit == -1 && (ce->unit & ~tmask) == allocunit) + allocunit++; + if ((ce->unit & ~tmask) < allocunit) + after = ce; + /* + * Clone logic: + * 1. Look for non busy, but keep track of the best + * possible busy cdev. + * 2. Look for the best (oldest referenced) entry that is + * in a same process / thread. + * 3. Look for the best (oldest referenced), absolute free + * entry. + * 4. Lastly, look for the best (oldest referenced) + * any entries that doesn't fit with anything above. + */ + if (ce->flags & SND_CLONE_BUSY) { + if (ce->devt != NULL && (bce == NULL || + timespeccmp(&ce->tsp, &bce->tsp, <))) + bce = ce; + continue; + } + if (ce->pid == curpid && + (cce == NULL || timespeccmp(&ce->tsp, &cce->tsp, <))) + cce = ce; + else if (!(ce->flags & SND_CLONE_INVOKE) && + (nce == NULL || timespeccmp(&ce->tsp, &nce->tsp, <))) + nce = ce; + else if (tce == NULL || timespeccmp(&ce->tsp, &tce->tsp, <)) + tce = ce; + } + if (*unit != -1) + goto snd_clone_alloc_new; + else if (cce != NULL) { + /* Same proc entry found, go for it */ + ce = cce; + goto snd_clone_alloc_out; + } else if (nce != NULL) { + /* + * Next, try absolute free entry. If the calculated + * allocunit is smaller, create new entry instead. + */ + if (allocunit < (nce->unit & ~tmask)) + goto snd_clone_alloc_new; + ce = nce; + goto snd_clone_alloc_out; + } else if (allocunit > c->maxunit) { + /* + * Maximum allowable unit reached. Try returning any + * available cdev and hope for the best. If the lookup is + * done for things like stat(), mtime() etc. , things should + * be ok. Otherwise, open() handler should do further checks + * and decide whether to return correct error code or not. + */ + if (tce != NULL) { + ce = tce; + goto snd_clone_alloc_out; + } else if (bce != NULL) { + ce = bce; + goto snd_clone_alloc_out; + } + return (NULL); + } + +snd_clone_alloc_new: + /* + * No free entries found, and we still haven't reached maximum + * allowable units. Allocate, setup a minimal unique entry with busy + * status so nobody will monkey on this new entry. Unit magic is set + * right here to avoid collision with other contesting handler. + * The caller must be carefull here to maintain its own + * synchronization, as long as it will not conflict with malloc(9) + * operations. + * + * That said, go figure. + */ + ce = malloc(sizeof(*ce), M_DEVBUF, + ((c->flags & SND_CLONE_WAITOK) ? M_WAITOK : M_NOWAIT) | M_ZERO); + if (ce == NULL) { + if (*unit != -1) + return (NULL); + /* + * We're being dense, ignorance is bliss, + * Super Regulatory Measure (TM).. TRY AGAIN! + */ + if (nce != NULL) { + ce = nce; + goto snd_clone_alloc_out; + } else if (tce != NULL) { + ce = tce; + goto snd_clone_alloc_out; + } else if (bce != NULL) { + ce = bce; + goto snd_clone_alloc_out; + } + return (NULL); + } + /* Setup new entry */ + ce->parent = c; + ce->unit = tmask | allocunit; + ce->pid = curpid; + ce->tsp = now; + ce->flags |= SND_CLONE_ALLOC; + if (after != NULL) { + TAILQ_INSERT_AFTER(&c->head, after, ce, link); + } else { + TAILQ_INSERT_HEAD(&c->head, ce, link); + } + c->size++; + c->tsp = now; + /* + * Save new allocation unit for caller which will be used + * by make_dev(). + */ + *unit = allocunit; + + return (ce); + +snd_clone_alloc_out: + /* + * Set, mark, timestamp the entry if this is a truly free entry. + * Leave busy entry alone. + */ + if (!(ce->flags & SND_CLONE_BUSY)) { + ce->pid = curpid; + ce->tsp = now; + ce->flags |= SND_CLONE_INVOKE; + } + c->tsp = now; + *dev = ce->devt; + + return (NULL); +} --- sys/dev/sound/clone.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/clone.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,132 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/clone.h,v 1.2 2007/06/14 11:10:21 ariff Exp $ + */ + +#ifndef _SND_CLONE_H_ +#define _SND_CLONE_H_ + +struct snd_clone_entry; +struct snd_clone; + +/* + * 750 milisecond default deadline. Short enough to not cause excessive + * garbage collection, long enough to indicate stalled VFS. + */ +#define SND_CLONE_DEADLINE_DEFAULT 750 + +/* + * Fit within 24bit MAXMINOR. + */ +#define SND_CLONE_MAXUNIT 0xffffff + +/* + * Creation flags, mostly related to the behaviour of garbage collector. + * + * SND_CLONE_ENABLE - Enable clone allocation. + * SND_CLONE_GC_ENABLE - Enable garbage collector operation, automatically + * or if explicitly called upon. + * SND_CLONE_GC_UNREF - Garbage collect during unref operation. + * SND_CLONE_GC_LASTREF - Garbage collect during last reference + * (refcount = 0) + * SND_CLONE_GC_EXPIRED - Don't garbage collect unless the global clone + * handler has been expired. + * SND_CLONE_GC_REVOKE - Revoke clone invocation status which has been + * expired instead of removing and freeing it. + * SND_CLONE_WAITOK - malloc() is allowed to sleep while allocating + * clone entry. + */ +#define SND_CLONE_ENABLE 0x00000001 +#define SND_CLONE_GC_ENABLE 0x00000002 +#define SND_CLONE_GC_UNREF 0x00000004 +#define SND_CLONE_GC_LASTREF 0x00000008 +#define SND_CLONE_GC_EXPIRED 0x00000010 +#define SND_CLONE_GC_REVOKE 0x00000020 +#define SND_CLONE_WAITOK 0x80000000 + +#define SND_CLONE_GC_MASK (SND_CLONE_GC_ENABLE | \ + SND_CLONE_GC_UNREF | \ + SND_CLONE_GC_LASTREF | \ + SND_CLONE_GC_EXPIRED | \ + SND_CLONE_GC_REVOKE) + +#define SND_CLONE_MASK (SND_CLONE_ENABLE | SND_CLONE_GC_MASK | \ + SND_CLONE_WAITOK) + +/* + * Runtime clone device flags + * + * These are mostly private to the clone manager operation: + * + * SND_CLONE_NEW - New clone allocation in progress. + * SND_CLONE_INVOKE - Cloning being invoked, waiting for next VFS operation. + * SND_CLONE_BUSY - In progress, being referenced by living thread/proc. + */ +#define SND_CLONE_NEW 0x00000001 +#define SND_CLONE_INVOKE 0x00000002 +#define SND_CLONE_BUSY 0x00000004 + +/* + * Nothing important, just for convenience. + */ +#define SND_CLONE_ALLOC (SND_CLONE_NEW | SND_CLONE_INVOKE | \ + SND_CLONE_BUSY) + +#define SND_CLONE_DEVMASK SND_CLONE_ALLOC + + +void snd_timestamp(struct timespec *); + +struct snd_clone *snd_clone_create(int, int, int, uint32_t); +int snd_clone_busy(struct snd_clone *); +int snd_clone_enable(struct snd_clone *); +int snd_clone_disable(struct snd_clone *); +int snd_clone_getsize(struct snd_clone *); +int snd_clone_getmaxunit(struct snd_clone *); +int snd_clone_setmaxunit(struct snd_clone *, int); +int snd_clone_getdeadline(struct snd_clone *); +int snd_clone_setdeadline(struct snd_clone *, int); +int snd_clone_gettime(struct snd_clone *, struct timespec *); +uint32_t snd_clone_getflags(struct snd_clone *); +uint32_t snd_clone_setflags(struct snd_clone *, uint32_t); +int snd_clone_getdevtime(struct cdev *, struct timespec *); +uint32_t snd_clone_getdevflags(struct cdev *); +uint32_t snd_clone_setdevflags(struct cdev *, uint32_t); +int snd_clone_gc(struct snd_clone *); +void snd_clone_destroy(struct snd_clone *); +int snd_clone_acquire(struct cdev *); +int snd_clone_release(struct cdev *); +int snd_clone_ref(struct cdev *); +int snd_clone_unref(struct cdev *); +void snd_clone_register(struct snd_clone_entry *, struct cdev *); +struct snd_clone_entry *snd_clone_alloc(struct snd_clone *, struct cdev **, + int *, int); + +#define snd_clone_enabled(x) ((x) != NULL && \ + (snd_clone_getflags(x) & SND_CLONE_ENABLE)) +#define snd_clone_disabled(x) (!snd_clone_enabled(x)) + +#endif /* !_SND_CLONE_H */ --- sys/dev/sound/driver.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/driver.c Thu Jul 12 12:04:19 2007 @@ -18,12 +18,12 @@ * 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, WHETHERIN CONTRACT, STRICT + * 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 THEPOSSIBILITY OF + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/driver.c,v 1.13.2.3 2007/05/13 21:11:40 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/driver.c,v 1.21 2007/01/31 08:53:45 joel Exp $ */ #include @@ -61,11 +61,10 @@ MODULE_DEPEND(snd_driver, snd_csa, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_csapcm, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_ds1, 1, 1, 1); -MODULE_DEPEND(snd_driver, snd_emu10k1, 1, 1, 1); +MODULE_DEPEND(snd_driver, snd_emu10kx, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_envy24, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_envy24ht, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_es137x, 1, 1, 1); -MODULE_DEPEND(snd_driver, snd_es1888, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_ess, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_fm801, 1, 1, 1); MODULE_DEPEND(snd_driver, snd_gusc, 1, 1, 1); --- sys/dev/sound/isa/ad1816.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/ad1816.c Thu Jul 12 12:04:19 2007 @@ -1,7 +1,7 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright Luigi Rizzo, 1997,1998 - * Copyright by Hannu Savolainen 1994, 1995 + * Copyright (c) 1997,1998 Luigi Rizzo + * Copyright (c) 1994,1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ad1816.c,v 1.37.2.2 2006/04/04 17:23:24 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ad1816.c,v 1.45 2007/06/17 06:10:40 ariff Exp $"); struct ad1816_info; @@ -318,7 +318,7 @@ ch->parent = ad1816; ch->channel = c; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, ad1816->parent_dmat, ad1816->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, ad1816->parent_dmat, 0, ad1816->bufsize) != 0) return NULL; return ch; } @@ -411,7 +411,7 @@ struct ad1816_info *ad1816 = ch->parent; int wr, reg; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); @@ -598,10 +598,9 @@ struct ad1816_info *ad1816; char status[SND_STATUSLEN], status2[SND_STATUSLEN]; - ad1816 = (struct ad1816_info *)malloc(sizeof *ad1816, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!ad1816) return ENXIO; - - ad1816->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + ad1816 = malloc(sizeof(*ad1816), M_DEVBUF, M_WAITOK | M_ZERO); + ad1816->lock = snd_mtxcreate(device_get_nameunit(dev), + "snd_ad1816 softc"); ad1816->io_rid = 2; ad1816->irq_rid = 0; ad1816->drq1_rid = 0; @@ -613,7 +612,8 @@ if (mixer_init(dev, &ad1816mixer_class, ad1816)) goto no; snd_setup_intr(dev, ad1816->irq, 0, ad1816_intr, ad1816, &ad1816->ih); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/isa/ad1816.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/ad1816.h Thu Jul 12 12:04:19 2007 @@ -1,10 +1,34 @@ /*- - * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * Copyright (c) 1997 Luigi Rizzo + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/isa/ad1816.h,v 1.4 2007/02/02 13:44:09 joel Exp $ + */ + +/* * This file contains information and macro definitions for * the ad1816 chip - * - * $FreeBSD: src/sys/dev/sound/isa/ad1816.h,v 1.2 2005/01/06 01:43:17 imp Exp $ */ /* AD1816 register macros */ --- sys/dev/sound/isa/es1888.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/es1888.c Thu Jan 1 07:30:00 1970 @@ -1,177 +0,0 @@ -/*- - * Copyright (c) 1999 Doug Rabson - * 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 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 AUTHOR 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 -#include - -#include - -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/es1888.c,v 1.15 2005/01/06 01:43:17 imp Exp $"); - -#ifdef __alpha__ -static int -es1888_dspready(u_int32_t port) -{ - return ((inb(port + SBDSP_STATUS) & 0x80) == 0); -} - -static int -es1888_dspwr(u_int32_t port, u_char val) -{ - int i; - - for (i = 0; i < 1000; i++) { - if (es1888_dspready(port)) { - outb(port + SBDSP_CMD, val); - return 0; - } - if (i > 10) DELAY((i > 100)? 1000 : 10); - } - return ENXIO; -} - -static u_int -es1888_get_byte(u_int32_t port) -{ - int i; - - for (i = 1000; i > 0; i--) { - if (inb(port + DSP_DATA_AVAIL) & 0x80) - return inb(port + DSP_READ); - else - DELAY(20); - } - return 0xffff; -} - -static int -es1888_reset(u_int32_t port) -{ - outb(port + SBDSP_RST, 3); - DELAY(100); - outb(port + SBDSP_RST, 0); - if (es1888_get_byte(port) != 0xAA) { - return ENXIO; /* Sorry */ - } - return 0; -} - -static void -es1888_configuration_mode(void) -{ - /* - * Emit the Read-Sequence-Key to enter configuration - * mode. Note this only works after a reset (or after bit 2 of - * mixer register 0x40 is set). - * - * 3 reads from 0x229 in a row guarantees reset of key - * sequence to beginning. - */ - inb(0x229); - inb(0x229); - inb(0x229); - - inb(0x22b); /* state 1 */ - inb(0x229); /* state 2 */ - inb(0x22b); /* state 3 */ - inb(0x229); /* state 4 */ - inb(0x229); /* state 5 */ - inb(0x22b); /* state 6 */ - inb(0x229); /* state 7 */ -} - -static void -es1888_set_port(u_int32_t port) -{ - es1888_configuration_mode(); - inb(port); -} -#endif - -static void -es1888_identify(driver_t *driver, device_t parent) -{ -/* - * Only use this on alpha since PNPBIOS is a better solution on x86. - */ -#ifdef __alpha__ - u_int32_t lo, hi; - device_t dev; - - es1888_set_port(0x220); - if (es1888_reset(0x220)) - return; - - /* - * Check identification bytes for es1888. - */ - if (es1888_dspwr(0x220, 0xe7)) - return; - hi = es1888_get_byte(0x220); - lo = es1888_get_byte(0x220); - if (hi != 0x68 || (lo & 0xf0) != 0x80) - return; - - /* - * Program irq and drq. - */ - if (es1888_dspwr(0x220, 0xc6) /* enter extended mode */ - || es1888_dspwr(0x220, 0xb1) /* write register b1 */ - || es1888_dspwr(0x220, 0x14) /* enable irq 5 */ - || es1888_dspwr(0x220, 0xb2) /* write register b1 */ - || es1888_dspwr(0x220, 0x18)) /* enable drq 1 */ - return; - - /* - * Create the device and program its resources. - */ - dev = BUS_ADD_CHILD(parent, ISA_ORDER_PNPBIOS, NULL, -1); - bus_set_resource(dev, SYS_RES_IOPORT, 0, 0x220, 0x10); - bus_set_resource(dev, SYS_RES_IRQ, 0, 5, 1); - bus_set_resource(dev, SYS_RES_DRQ, 0, 1, 1); - isa_set_vendorid(dev, PNP_EISAID("ESS1888")); - isa_set_logicalid(dev, PNP_EISAID("ESS1888")); -#endif -} - -static device_method_t es1888_methods[] = { - /* Device interface */ - DEVMETHOD(device_identify, es1888_identify), - - { 0, 0 } -}; - -static driver_t es1888_driver = { - "pcm", - es1888_methods, - 1, /* no softc */ -}; - -DRIVER_MODULE(snd_es1888, isa, es1888_driver, pcm_devclass, 0, 0); -MODULE_DEPEND(snd_es1888, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); -MODULE_VERSION(snd_es1888, 1); - - --- sys/dev/sound/isa/ess.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/ess.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright 1997,1998 Luigi Rizzo. + * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright @@ -38,7 +38,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ess.c,v 1.34.2.2 2006/01/19 01:17:00 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/ess.c,v 1.42 2007/06/17 06:10:40 ariff Exp $"); #define ESS_BUFFSIZE (4096) #define ABS(x) (((x) < 0)? -(x) : (x)) @@ -97,7 +97,8 @@ bus_dma_tag_t parent_dmat; unsigned int bufsize; - int type, duplex:1, newspeed:1; + int type; + unsigned int duplex:1, newspeed:1; u_long bd_flags; /* board-specific flags */ struct ess_chinfo pch, rch; }; @@ -563,7 +564,7 @@ ch->parent = sc; ch->channel = c; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsize) != 0) return NULL; ch->dir = dir; ch->hwch = 1; @@ -610,7 +611,7 @@ { struct ess_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; switch (go) { @@ -808,10 +809,7 @@ char status[SND_STATUSLEN], buf[64]; int ver; - sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!sc) - return ENXIO; - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->parent_dev = device_get_parent(dev); sc->bufsize = pcm_getbuffersize(dev, 4096, ESS_BUFFSIZE, 65536); if (ess_alloc_resources(sc, dev)) @@ -851,7 +849,8 @@ if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/isa/gusc.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/gusc.c Thu Jul 12 12:04:19 2007 @@ -41,11 +41,8 @@ #include #include -#ifdef __alpha__ /* XXX workaround a stupid warning */ -#include -#endif -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/gusc.c,v 1.16 2005/01/06 01:43:17 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/gusc.c,v 1.19 2007/02/23 19:40:13 ariff Exp $"); #define LOGICALID_NOPNP 0 #define LOGICALID_PCM 0x0000561e @@ -319,7 +316,7 @@ } if (scp->irq != NULL) - bus_setup_intr(dev, scp->irq, INTR_TYPE_AV, gusc_intr, scp, &ih); + snd_setup_intr(dev, scp->irq, 0, gusc_intr, scp, &ih); bus_generic_attach(dev); return (0); @@ -421,12 +418,21 @@ } static int -gusc_setup_intr(device_t dev, device_t child, struct resource *irq, - int flags, driver_intr_t *intr, void *arg, void **cookiep) +gusc_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, void *arg, void **cookiep) { sc_p scp = (sc_p)device_get_softc(dev); devclass_t devclass; +#if __FreeBSD_version >= 700031 + if (filter != NULL) { + printf("gusc.c: we cannot use a filter here\n"); + return (EINVAL); + } +#endif devclass = device_get_devclass(child); if (strcmp(devclass_get_name(devclass), "midi") == 0) { scp->midi_intr.intr = intr; @@ -437,8 +443,11 @@ scp->pcm_intr.arg = arg; return 0; } - return bus_generic_setup_intr(dev, child, irq, flags, intr, - arg, cookiep); + return bus_generic_setup_intr(dev, child, irq, flags, +#if __FreeBSD_version >= 700031 + filter, +#endif + intr, arg, cookiep); } static device_t --- sys/dev/sound/isa/mss.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/mss.c Thu Jul 12 12:04:19 2007 @@ -1,8 +1,8 @@ /*- * Copyright (c) 2001 George Reid * Copyright (c) 1999 Cameron Grant - * Copyright Luigi Rizzo, 1997,1998 - * Copyright by Hannu Savolainen 1994, 1995 + * Copyright (c) 1997,1998 Luigi Rizzo + * Copyright (c) 1994,1995 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/mss.c,v 1.95.2.3 2006/04/04 17:30:59 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/mss.c,v 1.112 2007/06/17 06:10:40 ariff Exp $"); /* board-specific include files */ #include @@ -1152,7 +1152,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, mss->parent_dmat, mss->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, mss->parent_dmat, 0, mss->bufsize) != 0) return NULL; sndbuf_dmasetup(ch->buffer, (dir == PCMDIR_PLAY)? mss->drq1 : mss->drq2); return ch; @@ -1201,7 +1201,7 @@ struct mss_chinfo *ch = data; struct mss_info *mss = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); @@ -1320,7 +1320,7 @@ goto mss_probe_end; } tmp &= 0x3f; - if (!(tmp == 0x04 || tmp == 0x0f || tmp == 0x00)) { + if (!(tmp == 0x04 || tmp == 0x0f || tmp == 0x00 || tmp == 0x05)) { BVDDB(printf("No MSS signature detected on port 0x%lx (0x%x)\n", rman_get_start(mss->io_base), tmpx)); goto no; @@ -1696,7 +1696,7 @@ int pdma, rdma, flags = device_get_flags(dev); char status[SND_STATUSLEN], status2[SND_STATUSLEN]; - mss->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + mss->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_mss softc"); mss->bufsize = pcm_getbuffersize(dev, 4096, MSS_DEFAULT_BUFSZ, 65536); if (!mss_alloc_resources(mss, dev)) goto no; mss_init(mss, dev); @@ -1744,7 +1744,8 @@ } if (pdma == rdma) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -1978,10 +1979,7 @@ { struct mss_info *mss; - mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!mss) - return ENXIO; - + mss = malloc(sizeof(*mss), M_DEVBUF, M_WAITOK | M_ZERO); mss->io_rid = 0; mss->conf_rid = -1; mss->irq_rid = 0; --- sys/dev/sound/isa/mss.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/mss.h Thu Jul 12 12:04:19 2007 @@ -1,15 +1,6 @@ /*- - * file: mss.h - * - * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) - * - * This file contains information and macro definitions for - * AD1848-compatible devices, used in the MSS/WSS compatible boards. - * - */ - -/*- * Copyright (c) 1999 Doug Rabson + * Copyright (c) 1997 Luigi Rizzo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +24,12 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/isa/mss.h,v 1.12 2005/01/06 01:43:17 imp Exp $ + * $FreeBSD: src/sys/dev/sound/isa/mss.h,v 1.14 2007/02/02 13:44:09 joel Exp $ + */ + +/* + * This file contains information and macro definitions for + * AD1848-compatible devices, used in the MSS/WSS compatible boards. */ /* --- sys/dev/sound/isa/sb.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/sb.h Thu Jul 12 12:04:19 2007 @@ -1,6 +1,29 @@ -/* - * file: sbcard.h - * $FreeBSD: src/sys/dev/sound/isa/sb.h,v 1.15 2004/05/13 11:32:54 truckman Exp $ +/*- + * Copyright (c) 1997,1998 Luigi Rizzo + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/isa/sb.h,v 1.16 2007/02/02 13:33:35 joel Exp $ */ #ifndef SB_H --- sys/dev/sound/isa/sb16.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/sb16.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright 1997,1998 Luigi Rizzo. + * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright @@ -38,7 +38,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb16.c,v 1.90.2.1 2005/12/30 19:55:53 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb16.c,v 1.97 2007/06/17 06:10:40 ariff Exp $"); #define SB16_BUFFSIZE 4096 #define PLAIN_SB16(x) ((((x)->bd_flags) & (BD_F_SB16|BD_F_SB16X)) == BD_F_SB16) @@ -681,7 +681,7 @@ ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, sb->parent_dmat, sb->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, sb->parent_dmat, 0, sb->bufsize) != 0) return NULL; return ch; @@ -724,7 +724,7 @@ struct sb_chinfo *ch = data; struct sb_info *sb = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) @@ -813,10 +813,7 @@ uintptr_t ver; char status[SND_STATUSLEN], status2[SND_STATUSLEN]; - sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!sb) - return ENXIO; - + sb = malloc(sizeof(*sb), M_DEVBUF, M_WAITOK | M_ZERO); sb->parent_dev = device_get_parent(dev); BUS_READ_IVAR(sb->parent_dev, dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; @@ -841,7 +838,8 @@ sb->prio = 0; - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/isa/sb8.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/sb8.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * Copyright 1997,1998 Luigi Rizzo. + * Copyright (c) 1997,1998 Luigi Rizzo * * Derived from files in the Voxware 3.5 distribution, * Copyright by Hannu Savolainen 1994, under the same copyright @@ -38,7 +38,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb8.c,v 1.79.2.1 2005/12/30 19:55:53 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sb8.c,v 1.86 2007/06/17 06:10:41 ariff Exp $"); #define SB_DEFAULT_BUFSZ 4096 @@ -599,7 +599,7 @@ ch->channel = c; ch->dir = dir; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sb->parent_dmat, sb->bufsize) != 0) + if (sndbuf_alloc(ch->buffer, sb->parent_dmat, 0, sb->bufsize) != 0) return NULL; sndbuf_dmasetup(ch->buffer, sb->drq); return ch; @@ -637,7 +637,7 @@ { struct sb_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; sndbuf_dma(ch->buffer, go); @@ -714,10 +714,7 @@ char status[SND_STATUSLEN]; uintptr_t ver; - sb = (struct sb_info *)malloc(sizeof *sb, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!sb) - return ENXIO; - + sb = malloc(sizeof(*sb), M_DEVBUF, M_WAITOK | M_ZERO); sb->parent_dev = device_get_parent(dev); BUS_READ_IVAR(device_get_parent(dev), dev, 1, &ver); sb->bd_id = ver & 0x0000ffff; @@ -735,7 +732,8 @@ pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/isa/sbc.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/isa/sbc.c Thu Jul 12 12:04:19 2007 @@ -30,7 +30,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sbc.c,v 1.44.2.1 2005/12/30 19:55:53 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sbc.c,v 1.48 2007/03/15 16:41:25 ariff Exp $"); #define IO_MAX 3 #define IRQ_MAX 1 @@ -80,8 +80,12 @@ static int sbc_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); static int sbc_setup_intr(device_t dev, device_t child, struct resource *irq, - int flags, driver_intr_t *intr, void *arg, - void **cookiep); + int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, + void *arg, void **cookiep); static int sbc_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie); @@ -116,7 +120,8 @@ static void sbc_lockinit(struct sbc_softc *scp) { - scp->lock = snd_mtxcreate(device_get_nameunit(scp->dev), "sound softc"); + scp->lock = snd_mtxcreate(device_get_nameunit(scp->dev), + "snd_sbc softc"); } static void @@ -502,14 +507,23 @@ } static int -sbc_setup_intr(device_t dev, device_t child, struct resource *irq, - int flags, driver_intr_t *intr, void *arg, - void **cookiep) +sbc_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, + void *arg, void **cookiep) { struct sbc_softc *scp = device_get_softc(dev); struct sbc_ihl *ihl = NULL; int i, ret; +#if __FreeBSD_version >= 700031 + if (filter != NULL) { + printf("sbc.c: we cannot use a filter here\n"); + return (EINVAL); + } +#endif sbc_lock(scp); i = 0; while (i < IRQ_MAX) { --- sys/dev/sound/lpmap.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/lpmap.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,307 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD$ + * + * "Little-pmap": Lost Technology for RELENG_5/RELENG_6. + * + */ + +#ifndef _LPMAP_C_ +#define _LPMAP_C_ + +#if !(defined(__i386__) || defined(__amd64__)) +#error "Not applicable for non-i386/non-amd64" +#endif + +/* For cpu_feature, VM_MAXUSER_ADDRESS */ +#include +#include +#include + +#ifndef PG_PTE_PAT +#define PG_PTE_PAT 0x0080 +#endif + +#ifndef PG_PDE_PAT +#define PG_PDE_PAT 0x1000 +#endif + +#ifndef PAT_UNCACHEABLE +#define PAT_UNCACHEABLE 0x00 +#endif + +#ifndef PAT_WRITE_COMBINING +#define PAT_WRITE_COMBINING 0x01 +#endif + +#ifndef PAT_WRITE_THROUGH +#define PAT_WRITE_THROUGH 0x04 +#endif + +#ifndef PAT_WRITE_PROTECTED +#define PAT_WRITE_PROTECTED 0x05 +#endif + +#ifndef PAT_WRITE_BACK +#define PAT_WRITE_BACK 0x06 +#endif + +#ifndef PAT_UNCACHED +#define PAT_UNCACHED 0x07 +#endif + +#define lpmap_p(x, y...) /*printf("%s(): "x, __func__, y)*/ + +#undef pmap_change_attr +#define pmap_change_attr lpmap_change_attr + +#if defined(__i386__) +#define lpmap_pde(m, v) (&((m)->pm_pdir[(vm_offset_t)(v) >> PDRSHIFT])) +#elif defined(__amd64__) +static __inline vm_pindex_t +lpmap_pde_index(vm_offset_t va) +{ + return ((va >> PDRSHIFT) & ((1UL << NPDEPGSHIFT) - 1)); +} + +static __inline vm_pindex_t +lpmap_pdpe_index(vm_offset_t va) +{ + return ((va >> PDPSHIFT) & ((1UL << NPDPEPGSHIFT) - 1)); +} + +static __inline vm_pindex_t +lpmap_pml4e_index(vm_offset_t va) +{ + return ((va >> PML4SHIFT) & ((1UL << NPML4EPGSHIFT) - 1)); +} + +static __inline pd_entry_t * +lpmap_pdpe_to_pde(pdp_entry_t *pdpe, vm_offset_t va) +{ + pd_entry_t *pde; + + pde = (pd_entry_t *)PHYS_TO_DMAP(*pdpe & PG_FRAME); + return (&pde[lpmap_pde_index(va)]); +} + +static __inline pml4_entry_t * +lpmap_pml4e(pmap_t pmap, vm_offset_t va) +{ + if (pmap == NULL) + return (NULL); + return (&pmap->pm_pml4[lpmap_pml4e_index(va)]); +} + +static __inline pdp_entry_t * +lpmap_pml4e_to_pdpe(pml4_entry_t *pml4e, vm_offset_t va) +{ + pdp_entry_t *pdpe; + + pdpe = (pdp_entry_t *)PHYS_TO_DMAP(*pml4e & PG_FRAME); + return (&pdpe[lpmap_pdpe_index(va)]); +} + +static __inline pdp_entry_t * +lpmap_pdpe(pmap_t pmap, vm_offset_t va) +{ + pml4_entry_t *pml4e; + + pml4e = lpmap_pml4e(pmap, va); + if (pml4e == NULL || (*pml4e & PG_V) == 0) + return (NULL); + return (lpmap_pml4e_to_pdpe(pml4e, va)); +} + +static __inline pd_entry_t * +lpmap_pde(pmap_t pmap, vm_offset_t va) +{ + pdp_entry_t *pdpe; + + pdpe = lpmap_pdpe(pmap, va); + if (pdpe == NULL || (*pdpe & PG_V) == 0) + return (NULL); + return (lpmap_pdpe_to_pde(pdpe, va)); +} +#endif + +static __inline int +lpmap_validate_range(vm_offset_t base, vm_size_t size) +{ + vm_offset_t va; + pt_entry_t *pte; + pd_entry_t *pde; + + if (base <= VM_MAXUSER_ADDRESS) { + lpmap_p("base <= VM_MAXUSER_ADDRESS : base=0x%jx\n", + (uintmax_t)base); + return (EINVAL); + } + + va = base; + while (va < (base + size)) { + pde = lpmap_pde(kernel_pmap, va); + if (pde == NULL || *pde == 0) { + lpmap_p("Failed: %s : va=0x%jx\n", + (pde == NULL) ? "pde == NULL" : "*pde == 0", + (uintmax_t)va); + return (EINVAL); + } + if (*pde & PG_PS) { +#if defined(__amd64__) + if (size < NBPDR) { + lpmap_p("Failed: size < NBPDR : va=0x%jx\n", + (uintmax_t)va); + return (EINVAL); + } + va += NBPDR; +#else + lpmap_p("Failed: (*pde & PG_PS) != 0 : va=0x%jx\n", + (uintmax_t)va); + return (EINVAL); +#endif + } else { + pte = vtopte(va); + if (pte == NULL || *pte == 0) { + lpmap_p("Failed: %s : va=0x%jx\n", + (pte == NULL) ? "pte == NULL" : "*pte == 0", + (uintmax_t)va); + return (EINVAL); + } + va += PAGE_SIZE; + } + } + + return (0); +} + +extern int osreldate; + +static int +lpmap_change_attr(vm_offset_t va, vm_size_t size, int mode) +{ + vm_offset_t base, offset; + pt_entry_t *pte; +#if defined(__amd64__) + pd_entry_t *pde; +#endif + u_int opxe, npxe, ptefl, pdefl, attr; + int err, flushtlb; + + switch (mode) { + case PAT_UNCACHED: + case PAT_WRITE_COMBINING: + case PAT_WRITE_PROTECTED: + if (!(cpu_feature & CPUID_PAT)) + mode = PAT_UNCACHEABLE; + break; + default: + break; + } + + attr = 0; + + switch (mode) { + case PAT_UNCACHED: + case PAT_UNCACHEABLE: + case PAT_WRITE_PROTECTED: + attr |= PG_NC_PCD; + case PAT_WRITE_THROUGH: + attr |= PG_NC_PWT; + break; + case PAT_WRITE_COMBINING: + attr |= PG_NC_PCD; + break; + case PAT_WRITE_BACK: + break; + default: + lpmap_p("Unsupported mode=0x%08x\n", mode); + return (EINVAL); + break; + } + + ptefl = PG_NC_PCD | PG_NC_PWT; + pdefl = PG_NC_PCD | PG_NC_PWT; + if (osreldate >= 602110) { + ptefl |= PG_PTE_PAT; + pdefl |= PG_PDE_PAT; + } + + base = va & PG_FRAME; + offset = va & PAGE_MASK; + size = roundup(offset + size, PAGE_SIZE); + + err = lpmap_validate_range(base, size); + if (err != 0) { + lpmap_p("Validation failed! " + "vm_offset_t=0x%jx vm_size_t=%ju attr=0x%04x\n", + (uintmax_t)va, (uintmax_t)size, attr); + return (err); + } + + lpmap_p("vm_offset_t=0x%jx vm_size_t=%ju attr=0x%04x\n", + (uintmax_t)va, (uintmax_t)size, attr); + + flushtlb = 0; + + while (size > 0) { +#if defined(__amd64__) + pde = lpmap_pde(kernel_pmap, base); + if (*pde & PG_PS) { + do { + opxe = *(u_int *)pde; + npxe = opxe & ~pdefl; + npxe |= attr; + } while (npxe != opxe && ++flushtlb && + !atomic_cmpset_int((u_int *)pde, opxe, npxe)); + size -= NBPDR; + base += NBPDR; + } else { +#endif + pte = vtopte(base); + do { + opxe = *(u_int *)pte; + npxe = opxe & ~ptefl; + npxe |= attr; + } while (npxe != opxe && ++flushtlb && + !atomic_cmpset_int((u_int *)pte, opxe, npxe)); + size -= PAGE_SIZE; + base += PAGE_SIZE; +#if defined(__amd64__) + } +#endif + } + + /* XXX Gross!! */ + if (flushtlb != 0) { + lpmap_p("flushtlb=%d\n", flushtlb); + pmap_invalidate_all(kernel_pmap); + } + + return (0); +} + +#endif /* !_LPMAP_C_ */ --- sys/dev/sound/midi/midi.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/midi.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,1528 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@netbsd.org). + * + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + */ + + /* + * Parts of this file started out as NetBSD: midi.c 1.31 + * They are mostly gone. Still the most obvious will be the state + * machine midi_in + */ + +#include +__FBSDID("$FreeBSD: src/sys/dev/sound/midi/midi.c,v 1.24 2007/04/02 06:03:47 ariff Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "mpu_if.h" + +#include +#include "synth_if.h" +MALLOC_DEFINE(M_MIDI, "midi buffers", "Midi data allocation area"); + + +#define PCMMKMINOR(u, d, c) ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) +#define MIDIMKMINOR(u, d, c) PCMMKMINOR(u, d, c) + +#define MIDI_DEV_RAW 2 +#define MIDI_DEV_MIDICTL 12 + +enum midi_states { + MIDI_IN_START, MIDI_IN_SYSEX, MIDI_IN_DATA +}; + +/* + * The MPU interface current has init() uninit() inqsize(( outqsize() + * callback() : fiddle with the tx|rx status. + */ + +#include "mpu_if.h" + +/* + * /dev/rmidi Structure definitions + */ + +#define MIDI_NAMELEN 16 +struct snd_midi { + KOBJ_FIELDS; + struct mtx lock; /* Protects all but queues */ + void *cookie; + + int unit; /* Should only be used in midistat */ + int channel; /* Should only be used in midistat */ + + int busy; + int flags; /* File flags */ + char name[MIDI_NAMELEN]; + struct mtx qlock; /* Protects inq, outq and flags */ + MIDIQ_HEAD(, char) inq, outq; + int rchan, wchan; + struct selinfo rsel, wsel; + int hiwat; /* QLEN(outq)>High-water -> disable + * writes from userland */ + enum midi_states inq_state; + int inq_status, inq_left; /* Variables for the state machine in + * Midi_in, this is to provide that + * signals only get issued only + * complete command packets. */ + struct proc *async; + struct cdev *dev; + struct synth_midi *synth; + int synth_flags; + TAILQ_ENTRY(snd_midi) link; +}; + +struct synth_midi { + KOBJ_FIELDS; + struct snd_midi *m; +}; + +static synth_open_t midisynth_open; +static synth_close_t midisynth_close; +static synth_writeraw_t midisynth_writeraw; +static synth_killnote_t midisynth_killnote; +static synth_startnote_t midisynth_startnote; +static synth_setinstr_t midisynth_setinstr; +static synth_alloc_t midisynth_alloc; +static synth_controller_t midisynth_controller; +static synth_bender_t midisynth_bender; + + +static kobj_method_t midisynth_methods[] = { + KOBJMETHOD(synth_open, midisynth_open), + KOBJMETHOD(synth_close, midisynth_close), + KOBJMETHOD(synth_writeraw, midisynth_writeraw), + KOBJMETHOD(synth_setinstr, midisynth_setinstr), + KOBJMETHOD(synth_startnote, midisynth_startnote), + KOBJMETHOD(synth_killnote, midisynth_killnote), + KOBJMETHOD(synth_alloc, midisynth_alloc), + KOBJMETHOD(synth_controller, midisynth_controller), + KOBJMETHOD(synth_bender, midisynth_bender), + {0, 0} +}; + +DEFINE_CLASS(midisynth, midisynth_methods, 0); + +/* + * Module Exports & Interface + * + * struct midi_chan *midi_init(MPU_CLASS cls, int unit, int chan) int + * midi_uninit(struct snd_midi *) 0 == no error EBUSY or other error int + * Midi_in(struct midi_chan *, char *buf, int count) int Midi_out(struct + * midi_chan *, char *buf, int count) + * + * midi_{in,out} return actual size transfered + * + */ + + +/* + * midi_devs tailq, holder of all rmidi instances protected by midistat_lock + */ + +TAILQ_HEAD(, snd_midi) midi_devs; + +/* + * /dev/midistat variables and declarations, protected by midistat_lock + */ + +static struct mtx midistat_lock; +static int midistat_isopen = 0; +static struct sbuf midistat_sbuf; +static int midistat_bufptr; +static struct cdev *midistat_dev; + +/* + * /dev/midistat dev_t declarations + */ + +static d_open_t midistat_open; +static d_close_t midistat_close; +static d_read_t midistat_read; + +static struct cdevsw midistat_cdevsw = { + .d_version = D_VERSION, + .d_open = midistat_open, + .d_close = midistat_close, + .d_read = midistat_read, + .d_name = "midistat", +}; + + +/* + * /dev/rmidi dev_t declarations, struct variable access is protected by + * locks contained within the structure. + */ + +static d_open_t midi_open; +static d_close_t midi_close; +static d_ioctl_t midi_ioctl; +static d_read_t midi_read; +static d_write_t midi_write; +static d_poll_t midi_poll; + +static struct cdevsw midi_cdevsw = { + .d_version = D_VERSION, + .d_open = midi_open, + .d_close = midi_close, + .d_read = midi_read, + .d_write = midi_write, + .d_ioctl = midi_ioctl, + .d_poll = midi_poll, + .d_name = "rmidi", +}; + +/* + * Prototypes of library functions + */ + +static int midi_destroy(struct snd_midi *, int); +static int midistat_prepare(struct sbuf * s); +static int midi_load(void); +static int midi_unload(void); + +/* + * Misc declr. + */ +SYSCTL_NODE(_hw, OID_AUTO, midi, CTLFLAG_RD, 0, "Midi driver"); +SYSCTL_NODE(_hw_midi, OID_AUTO, stat, CTLFLAG_RD, 0, "Status device"); + +int midi_debug; +/* XXX: should this be moved into debug.midi? */ +SYSCTL_INT(_hw_midi, OID_AUTO, debug, CTLFLAG_RW, &midi_debug, 0, ""); + +int midi_dumpraw; +SYSCTL_INT(_hw_midi, OID_AUTO, dumpraw, CTLFLAG_RW, &midi_dumpraw, 0, ""); + +int midi_instroff; +SYSCTL_INT(_hw_midi, OID_AUTO, instroff, CTLFLAG_RW, &midi_instroff, 0, ""); + +int midistat_verbose; +SYSCTL_INT(_hw_midi_stat, OID_AUTO, verbose, CTLFLAG_RW, + &midistat_verbose, 0, ""); + +#define MIDI_DEBUG(l,a) if(midi_debug>=l) a +/* + * CODE START + */ + +/* + * Register a new rmidi device. cls midi_if interface unit == 0 means + * auto-assign new unit number unit != 0 already assigned a unit number, eg. + * not the first channel provided by this device. channel, sub-unit + * cookie is passed back on MPU calls Typical device drivers will call with + * unit=0, channel=1..(number of channels) and cookie=soft_c and won't care + * what unit number is used. + * + * It is an error to call midi_init with an already used unit/channel combo. + * + * Returns NULL on error + * + */ +struct snd_midi * +midi_init(kobj_class_t cls, int unit, int channel, void *cookie) +{ + struct snd_midi *m; + int i; + int inqsize, outqsize; + MIDI_TYPE *buf; + + MIDI_DEBUG(1, printf("midiinit: unit %d/%d.\n", unit, channel)); + mtx_lock(&midistat_lock); + /* + * Protect against call with existing unit/channel or auto-allocate a + * new unit number. + */ + i = -1; + TAILQ_FOREACH(m, &midi_devs, link) { + mtx_lock(&m->lock); + if (unit != 0) { + if (m->unit == unit && m->channel == channel) { + mtx_unlock(&m->lock); + goto err0; + } + } else { + /* + * Find a better unit number + */ + if (m->unit > i) + i = m->unit; + } + mtx_unlock(&m->lock); + } + + if (unit == 0) + unit = i + 1; + + MIDI_DEBUG(1, printf("midiinit #2: unit %d/%d.\n", unit, channel)); + m = malloc(sizeof(*m), M_MIDI, M_NOWAIT | M_ZERO); + if (m == NULL) + goto err0; + + m->synth = malloc(sizeof(*m->synth), M_MIDI, M_NOWAIT | M_ZERO); + kobj_init((kobj_t)m->synth, &midisynth_class); + m->synth->m = m; + kobj_init((kobj_t)m, cls); + inqsize = MPU_INQSIZE(m, cookie); + outqsize = MPU_OUTQSIZE(m, cookie); + + MIDI_DEBUG(1, printf("midiinit queues %d/%d.\n", inqsize, outqsize)); + if (!inqsize && !outqsize) + goto err1; + + mtx_init(&m->lock, "raw midi", NULL, 0); + mtx_init(&m->qlock, "q raw midi", NULL, 0); + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (inqsize) + buf = malloc(sizeof(MIDI_TYPE) * inqsize, M_MIDI, M_NOWAIT); + else + buf = NULL; + + MIDIQ_INIT(m->inq, buf, inqsize); + + if (outqsize) + buf = malloc(sizeof(MIDI_TYPE) * outqsize, M_MIDI, M_NOWAIT); + else + buf = NULL; + m->hiwat = outqsize / 2; + + MIDIQ_INIT(m->outq, buf, outqsize); + + if ((inqsize && !MIDIQ_BUF(m->inq)) || + (outqsize && !MIDIQ_BUF(m->outq))) + goto err2; + + + m->busy = 0; + m->flags = 0; + m->unit = unit; + m->channel = channel; + m->cookie = cookie; + + if (MPU_INIT(m, cookie)) + goto err2; + + mtx_unlock(&m->lock); + mtx_unlock(&m->qlock); + + TAILQ_INSERT_TAIL(&midi_devs, m, link); + + mtx_unlock(&midistat_lock); + + m->dev = make_dev(&midi_cdevsw, + MIDIMKMINOR(unit, MIDI_DEV_RAW, channel), + UID_ROOT, GID_WHEEL, 0666, "midi%d.%d", unit, channel); + m->dev->si_drv1 = m; + + return m; + +err2: mtx_destroy(&m->qlock); + mtx_destroy(&m->lock); + + if (MIDIQ_BUF(m->inq)) + free(MIDIQ_BUF(m->inq), M_MIDI); + if (MIDIQ_BUF(m->outq)) + free(MIDIQ_BUF(m->outq), M_MIDI); +err1: free(m, M_MIDI); +err0: mtx_unlock(&midistat_lock); + MIDI_DEBUG(1, printf("midi_init ended in error\n")); + return NULL; +} + +/* + * midi_uninit does not call MIDI_UNINIT, as since this is the implementors + * entry point. midi_unint if fact, does not send any methods. A call to + * midi_uninit is a defacto promise that you won't manipulate ch anymore + * + */ + +int +midi_uninit(struct snd_midi *m) +{ + int err; + + err = ENXIO; + mtx_lock(&midistat_lock); + mtx_lock(&m->lock); + if (m->busy) { + if (!(m->rchan || m->wchan)) + goto err; + + if (m->rchan) { + wakeup(&m->rchan); + m->rchan = 0; + } + if (m->wchan) { + wakeup(&m->wchan); + m->wchan = 0; + } + } + err = midi_destroy(m, 0); + if (!err) + goto exit; + +err: mtx_unlock(&m->lock); +exit: mtx_unlock(&midistat_lock); + return err; +} + +/* + * midi_in: process all data until the queue is full, then discards the rest. + * Since midi_in is a state machine, data discards can cause it to get out of + * whack. Process as much as possible. It calls, wakeup, selnotify and + * psignal at most once. + */ + +#ifdef notdef +static int midi_lengths[] = {2, 2, 2, 2, 1, 1, 2, 0}; + +#endif /* notdef */ +/* Number of bytes in a MIDI command */ +#define MIDI_LENGTH(d) (midi_lengths[((d) >> 4) & 7]) +#define MIDI_ACK 0xfe +#define MIDI_IS_STATUS(d) ((d) >= 0x80) +#define MIDI_IS_COMMON(d) ((d) >= 0xf0) + +#define MIDI_SYSEX_START 0xF0 +#define MIDI_SYSEX_END 0xF7 + + +int +midi_in(struct snd_midi *m, MIDI_TYPE *buf, int size) +{ + /* int i, sig, enq; */ + int used; + + /* MIDI_TYPE data; */ + MIDI_DEBUG(5, printf("midi_in: m=%p size=%d\n", m, size)); + +/* + * XXX: locking flub + */ + if (!(m->flags & M_RX)) + return size; + + used = 0; + + mtx_lock(&m->qlock); +#if 0 + /* + * Don't bother queuing if not in read mode. Discard everything and + * return size so the caller doesn't freak out. + */ + + if (!(m->flags & M_RX)) + return size; + + for (i = sig = 0; i < size; i++) { + + data = buf[i]; + enq = 0; + if (data == MIDI_ACK) + continue; + + switch (m->inq_state) { + case MIDI_IN_START: + if (MIDI_IS_STATUS(data)) { + switch (data) { + case 0xf0: /* Sysex */ + m->inq_state = MIDI_IN_SYSEX; + break; + case 0xf1: /* MTC quarter frame */ + case 0xf3: /* Song select */ + m->inq_state = MIDI_IN_DATA; + enq = 1; + m->inq_left = 1; + break; + case 0xf2: /* Song position pointer */ + m->inq_state = MIDI_IN_DATA; + enq = 1; + m->inq_left = 2; + break; + default: + if (MIDI_IS_COMMON(data)) { + enq = 1; + sig = 1; + } else { + m->inq_state = MIDI_IN_DATA; + enq = 1; + m->inq_status = data; + m->inq_left = MIDI_LENGTH(data); + } + break; + } + } else if (MIDI_IS_STATUS(m->inq_status)) { + m->inq_state = MIDI_IN_DATA; + if (!MIDIQ_FULL(m->inq)) { + used++; + MIDIQ_ENQ(m->inq, &m->inq_status, 1); + } + enq = 1; + m->inq_left = MIDI_LENGTH(m->inq_status) - 1; + } + break; + /* + * End of case MIDI_IN_START: + */ + + case MIDI_IN_DATA: + enq = 1; + if (--m->inq_left <= 0) + sig = 1;/* deliver data */ + break; + case MIDI_IN_SYSEX: + if (data == MIDI_SYSEX_END) + m->inq_state = MIDI_IN_START; + break; + } + + if (enq) + if (!MIDIQ_FULL(m->inq)) { + MIDIQ_ENQ(m->inq, &data, 1); + used++; + } + /* + * End of the state machines main "for loop" + */ + } + if (sig) { +#endif + MIDI_DEBUG(6, printf("midi_in: len %jd avail %jd\n", + (intmax_t)MIDIQ_LEN(m->inq), + (intmax_t)MIDIQ_AVAIL(m->inq))); + if (MIDIQ_AVAIL(m->inq) > size) { + used = size; + MIDIQ_ENQ(m->inq, buf, size); + } else { + MIDI_DEBUG(4, printf("midi_in: Discarding data qu\n")); + mtx_unlock(&m->qlock); + return 0; + } + if (m->rchan) { + wakeup(&m->rchan); + m->rchan = 0; + } + selwakeup(&m->rsel); + if (m->async) { + PROC_LOCK(m->async); + psignal(m->async, SIGIO); + PROC_UNLOCK(m->async); + } +#if 0 + } +#endif + mtx_unlock(&m->qlock); + return used; +} + +/* + * midi_out: The only clearer of the M_TXEN flag. + */ +int +midi_out(struct snd_midi *m, MIDI_TYPE *buf, int size) +{ + int used; + +/* + * XXX: locking flub + */ + if (!(m->flags & M_TXEN)) + return 0; + + MIDI_DEBUG(2, printf("midi_out: %p\n", m)); + mtx_lock(&m->qlock); + used = MIN(size, MIDIQ_LEN(m->outq)); + MIDI_DEBUG(3, printf("midi_out: used %d\n", used)); + if (used) + MIDIQ_DEQ(m->outq, buf, used); + if (MIDIQ_EMPTY(m->outq)) { + m->flags &= ~M_TXEN; + MPU_CALLBACKP(m, m->cookie, m->flags); + } + if (used && MIDIQ_AVAIL(m->outq) > m->hiwat) { + if (m->wchan) { + wakeup(&m->wchan); + m->wchan = 0; + } + selwakeup(&m->wsel); + if (m->async) { + PROC_LOCK(m->async); + psignal(m->async, SIGIO); + PROC_UNLOCK(m->async); + } + } + mtx_unlock(&m->qlock); + return used; +} + + +/* + * /dev/rmidi#.# device access functions + */ +int +midi_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct snd_midi *m = i_dev->si_drv1; + int retval; + + MIDI_DEBUG(1, printf("midiopen %p %s %s\n", td, + flags & FREAD ? "M_RX" : "", flags & FWRITE ? "M_TX" : "")); + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + retval = 0; + + if (flags & FREAD) { + if (MIDIQ_SIZE(m->inq) == 0) + retval = ENXIO; + else if (m->flags & M_RX) + retval = EBUSY; + if (retval) + goto err; + } + if (flags & FWRITE) { + if (MIDIQ_SIZE(m->outq) == 0) + retval = ENXIO; + else if (m->flags & M_TX) + retval = EBUSY; + if (retval) + goto err; + } + m->busy++; + + m->rchan = 0; + m->wchan = 0; + m->async = 0; + + if (flags & FREAD) { + m->flags |= M_RX | M_RXEN; + /* + * Only clear the inq, the outq might still have data to drain + * from a previous session + */ + MIDIQ_CLEAR(m->inq); + }; + + if (flags & FWRITE) + m->flags |= M_TX; + + MPU_CALLBACK(m, m->cookie, m->flags); + + MIDI_DEBUG(2, printf("midi_open: opened.\n")); + +err: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + return retval; +} + +int +midi_close(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct snd_midi *m = i_dev->si_drv1; + int retval; + int oldflags; + + MIDI_DEBUG(1, printf("midi_close %p %s %s\n", td, + flags & FREAD ? "M_RX" : "", flags & FWRITE ? "M_TX" : "")); + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if ((flags & FREAD && !(m->flags & M_RX)) || + (flags & FWRITE && !(m->flags & M_TX))) { + retval = ENXIO; + goto err; + } + m->busy--; + + oldflags = m->flags; + + if (flags & FREAD) + m->flags &= ~(M_RX | M_RXEN); + if (flags & FWRITE) + m->flags &= ~M_TX; + + if ((m->flags & (M_TXEN | M_RXEN)) != (oldflags & (M_RXEN | M_TXEN))) + MPU_CALLBACK(m, m->cookie, m->flags); + + MIDI_DEBUG(1, printf("midi_close: closed, busy = %d.\n", m->busy)); + + mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + retval = 0; +err: return retval; +} + +/* + * TODO: midi_read, per oss programmer's guide pg. 42 should return as soon + * as data is available. + */ +int +midi_read(struct cdev *i_dev, struct uio *uio, int ioflag) +{ +#define MIDI_RSIZE 32 + struct snd_midi *m = i_dev->si_drv1; + int retval; + int used; + char buf[MIDI_RSIZE]; + + MIDI_DEBUG(5, printf("midiread: count=%lu\n", + (unsigned long)uio->uio_resid)); + + retval = EIO; + + if (m == NULL) + goto err0; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (!(m->flags & M_RX)) + goto err1; + + while (uio->uio_resid > 0) { + while (MIDIQ_EMPTY(m->inq)) { + retval = EWOULDBLOCK; + if (ioflag & O_NONBLOCK) + goto err1; + mtx_unlock(&m->lock); + m->rchan = 1; + retval = msleep(&m->rchan, &m->qlock, + PCATCH | PDROP, "midi RX", 0); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; + if (m != i_dev->si_drv1) + retval = ENXIO; + /* if (retval && retval != ERESTART) */ + if (retval) + goto err0; + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + m->rchan = 0; + if (!m->busy) + goto err1; + } + MIDI_DEBUG(6, printf("midi_read start\n")); + /* + * At this point, it is certain that m->inq has data + */ + + used = MIN(MIDIQ_LEN(m->inq), uio->uio_resid); + used = MIN(used, MIDI_RSIZE); + + MIDI_DEBUG(6, printf("midiread: uiomove cc=%d\n", used)); + MIDIQ_DEQ(m->inq, buf, used); + retval = uiomove(buf, used, uio); + if (retval) + goto err1; + } + + /* + * If we Made it here then transfer is good + */ + retval = 0; +err1: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); +err0: MIDI_DEBUG(4, printf("midi_read: ret %d\n", retval)); + return retval; +} + +/* + * midi_write: The only setter of M_TXEN + */ + +int +midi_write(struct cdev *i_dev, struct uio *uio, int ioflag) +{ +#define MIDI_WSIZE 32 + struct snd_midi *m = i_dev->si_drv1; + int retval; + int used; + char buf[MIDI_WSIZE]; + + + MIDI_DEBUG(4, printf("midi_write\n")); + retval = 0; + if (m == NULL) + goto err0; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (!(m->flags & M_TX)) + goto err1; + + while (uio->uio_resid > 0) { + while (MIDIQ_AVAIL(m->outq) == 0) { + retval = EWOULDBLOCK; + if (ioflag & O_NONBLOCK) + goto err1; + mtx_unlock(&m->lock); + m->wchan = 1; + MIDI_DEBUG(3, printf("midi_write msleep\n")); + retval = msleep(&m->wchan, &m->qlock, + PCATCH | PDROP, "midi TX", 0); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; + if (m != i_dev->si_drv1) + retval = ENXIO; + if (retval) + goto err0; + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + m->wchan = 0; + if (!m->busy) + goto err1; + } + + /* + * We are certain than data can be placed on the queue + */ + + used = MIN(MIDIQ_AVAIL(m->outq), uio->uio_resid); + used = MIN(used, MIDI_WSIZE); + MIDI_DEBUG(5, printf("midiout: resid %d len %jd avail %jd\n", + uio->uio_resid, (intmax_t)MIDIQ_LEN(m->outq), + (intmax_t)MIDIQ_AVAIL(m->outq))); + + + MIDI_DEBUG(5, printf("midi_write: uiomove cc=%d\n", used)); + retval = uiomove(buf, used, uio); + if (retval) + goto err1; + MIDIQ_ENQ(m->outq, buf, used); + /* + * Inform the bottom half that data can be written + */ + if (!(m->flags & M_TXEN)) { + m->flags |= M_TXEN; + MPU_CALLBACK(m, m->cookie, m->flags); + } + } + /* + * If we Made it here then transfer is good + */ + retval = 0; +err1: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); +err0: return retval; +} + +int +midi_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + return ENXIO; +} + +int +midi_poll(struct cdev *i_dev, int events, struct thread *td) +{ + struct snd_midi *m = i_dev->si_drv1; + int revents; + + if (m == NULL) + return 0; + + revents = 0; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (events & (POLLIN | POLLRDNORM)) + if (!MIDIQ_EMPTY(m->inq)) + events |= events & (POLLIN | POLLRDNORM); + + if (events & (POLLOUT | POLLWRNORM)) + if (MIDIQ_AVAIL(m->outq) < m->hiwat) + events |= events & (POLLOUT | POLLWRNORM); + + if (revents == 0) { + if (events & (POLLIN | POLLRDNORM)) + selrecord(td, &m->rsel); + + if (events & (POLLOUT | POLLWRNORM)) + selrecord(td, &m->wsel); + } + mtx_unlock(&m->lock); + mtx_unlock(&m->qlock); + + return (revents); +} + +/* + * /dev/midistat device functions + * + */ +static int +midistat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + int error; + + MIDI_DEBUG(1, printf("midistat_open\n")); + mtx_lock(&midistat_lock); + + if (midistat_isopen) { + mtx_unlock(&midistat_lock); + return EBUSY; + } + midistat_isopen = 1; + mtx_unlock(&midistat_lock); + + if (sbuf_new(&midistat_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { + error = ENXIO; + mtx_lock(&midistat_lock); + goto out; + } + mtx_lock(&midistat_lock); + midistat_bufptr = 0; + error = (midistat_prepare(&midistat_sbuf) > 0) ? 0 : ENOMEM; + +out: if (error) + midistat_isopen = 0; + mtx_unlock(&midistat_lock); + return error; +} + +static int +midistat_close(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + MIDI_DEBUG(1, printf("midistat_close\n")); + mtx_lock(&midistat_lock); + if (!midistat_isopen) { + mtx_unlock(&midistat_lock); + return EBADF; + } + sbuf_delete(&midistat_sbuf); + midistat_isopen = 0; + + mtx_unlock(&midistat_lock); + return 0; +} + +static int +midistat_read(struct cdev *i_dev, struct uio *buf, int flag) +{ + int l, err; + + MIDI_DEBUG(4, printf("midistat_read\n")); + mtx_lock(&midistat_lock); + if (!midistat_isopen) { + mtx_unlock(&midistat_lock); + return EBADF; + } + l = min(buf->uio_resid, sbuf_len(&midistat_sbuf) - midistat_bufptr); + err = 0; + if (l > 0) { + mtx_unlock(&midistat_lock); + err = uiomove(sbuf_data(&midistat_sbuf) + midistat_bufptr, l, + buf); + mtx_lock(&midistat_lock); + } else + l = 0; + midistat_bufptr += l; + mtx_unlock(&midistat_lock); + return err; +} + +/* + * Module library functions + */ + +static int +midistat_prepare(struct sbuf *s) +{ + struct snd_midi *m; + + mtx_assert(&midistat_lock, MA_OWNED); + + sbuf_printf(s, "FreeBSD Midi Driver (midi2)\n"); + if (TAILQ_EMPTY(&midi_devs)) { + sbuf_printf(s, "No devices installed.\n"); + sbuf_finish(s); + return sbuf_len(s); + } + sbuf_printf(s, "Installed devices:\n"); + + TAILQ_FOREACH(m, &midi_devs, link) { + mtx_lock(&m->lock); + sbuf_printf(s, "%s [%d/%d:%s]", m->name, m->unit, m->channel, + MPU_PROVIDER(m, m->cookie)); + sbuf_printf(s, "%s", MPU_DESCR(m, m->cookie, midistat_verbose)); + sbuf_printf(s, "\n"); + mtx_unlock(&m->lock); + } + + sbuf_finish(s); + return sbuf_len(s); +} + +#ifdef notdef +/* + * Convert IOCTL command to string for debugging + */ + +static char * +midi_cmdname(int cmd) +{ + static struct { + int cmd; + char *name; + } *tab, cmdtab_midiioctl[] = { +#define A(x) {x, ## x} + /* + * Once we have some real IOCTLs define, the following will + * be relavant. + * + * A(SNDCTL_MIDI_PRETIME), A(SNDCTL_MIDI_MPUMODE), + * A(SNDCTL_MIDI_MPUCMD), A(SNDCTL_SYNTH_INFO), + * A(SNDCTL_MIDI_INFO), A(SNDCTL_SYNTH_MEMAVL), + * A(SNDCTL_FM_LOAD_INSTR), A(SNDCTL_FM_4OP_ENABLE), + * A(MIOSPASSTHRU), A(MIOGPASSTHRU), A(AIONWRITE), + * A(AIOGSIZE), A(AIOSSIZE), A(AIOGFMT), A(AIOSFMT), + * A(AIOGMIX), A(AIOSMIX), A(AIOSTOP), A(AIOSYNC), + * A(AIOGCAP), + */ +#undef A + { + -1, "unknown" + }, + }; + + for (tab = cmdtab_midiioctl; tab->cmd != cmd && tab->cmd != -1; tab++); + return tab->name; +} + +#endif /* notdef */ + +/* + * midisynth + */ + + +int +midisynth_open(void *n, void *arg, int flags) +{ + struct snd_midi *m = ((struct synth_midi *)n)->m; + int retval; + + MIDI_DEBUG(1, printf("midisynth_open %s %s\n", + flags & FREAD ? "M_RX" : "", flags & FWRITE ? "M_TX" : "")); + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + retval = 0; + + if (flags & FREAD) { + if (MIDIQ_SIZE(m->inq) == 0) + retval = ENXIO; + else if (m->flags & M_RX) + retval = EBUSY; + if (retval) + goto err; + } + if (flags & FWRITE) { + if (MIDIQ_SIZE(m->outq) == 0) + retval = ENXIO; + else if (m->flags & M_TX) + retval = EBUSY; + if (retval) + goto err; + } + m->busy++; + + /* + * TODO: Consider m->async = 0; + */ + + if (flags & FREAD) { + m->flags |= M_RX | M_RXEN; + /* + * Only clear the inq, the outq might still have data to drain + * from a previous session + */ + MIDIQ_CLEAR(m->inq); + m->rchan = 0; + }; + + if (flags & FWRITE) { + m->flags |= M_TX; + m->wchan = 0; + } + m->synth_flags = flags & (FREAD | FWRITE); + + MPU_CALLBACK(m, m->cookie, m->flags); + + +err: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + MIDI_DEBUG(2, printf("midisynth_open: return %d.\n", retval)); + return retval; +} + +int +midisynth_close(void *n) +{ + struct snd_midi *m = ((struct synth_midi *)n)->m; + int retval; + int oldflags; + + MIDI_DEBUG(1, printf("midisynth_close %s %s\n", + m->synth_flags & FREAD ? "M_RX" : "", + m->synth_flags & FWRITE ? "M_TX" : "")); + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if ((m->synth_flags & FREAD && !(m->flags & M_RX)) || + (m->synth_flags & FWRITE && !(m->flags & M_TX))) { + retval = ENXIO; + goto err; + } + m->busy--; + + oldflags = m->flags; + + if (m->synth_flags & FREAD) + m->flags &= ~(M_RX | M_RXEN); + if (m->synth_flags & FWRITE) + m->flags &= ~M_TX; + + if ((m->flags & (M_TXEN | M_RXEN)) != (oldflags & (M_RXEN | M_TXEN))) + MPU_CALLBACK(m, m->cookie, m->flags); + + MIDI_DEBUG(1, printf("midi_close: closed, busy = %d.\n", m->busy)); + + mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); + retval = 0; +err: return retval; +} + +/* + * Always blocking. + */ + +int +midisynth_writeraw(void *n, uint8_t *buf, size_t len) +{ + struct snd_midi *m = ((struct synth_midi *)n)->m; + int retval; + int used; + int i; + + MIDI_DEBUG(4, printf("midisynth_writeraw\n")); + + retval = 0; + + if (m == NULL) + return ENXIO; + + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + + if (!(m->flags & M_TX)) + goto err1; + + if (midi_dumpraw) + printf("midi dump: "); + + while (len > 0) { + while (MIDIQ_AVAIL(m->outq) == 0) { + if (!(m->flags & M_TXEN)) { + m->flags |= M_TXEN; + MPU_CALLBACK(m, m->cookie, m->flags); + } + mtx_unlock(&m->lock); + m->wchan = 1; + MIDI_DEBUG(3, printf("midisynth_writeraw msleep\n")); + retval = msleep(&m->wchan, &m->qlock, + PCATCH | PDROP, "midi TX", 0); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; + + if (retval) + goto err0; + mtx_lock(&m->lock); + mtx_lock(&m->qlock); + m->wchan = 0; + if (!m->busy) + goto err1; + } + + /* + * We are certain than data can be placed on the queue + */ + + used = MIN(MIDIQ_AVAIL(m->outq), len); + used = MIN(used, MIDI_WSIZE); + MIDI_DEBUG(5, + printf("midi_synth: resid %zu len %jd avail %jd\n", + len, (intmax_t)MIDIQ_LEN(m->outq), + (intmax_t)MIDIQ_AVAIL(m->outq))); + + if (midi_dumpraw) + for (i = 0; i < used; i++) + printf("%x ", buf[i]); + + MIDIQ_ENQ(m->outq, buf, used); + len -= used; + + /* + * Inform the bottom half that data can be written + */ + if (!(m->flags & M_TXEN)) { + m->flags |= M_TXEN; + MPU_CALLBACK(m, m->cookie, m->flags); + } + } + /* + * If we Made it here then transfer is good + */ + if (midi_dumpraw) + printf("\n"); + + retval = 0; +err1: mtx_unlock(&m->qlock); + mtx_unlock(&m->lock); +err0: return retval; +} + +static int +midisynth_killnote(void *n, uint8_t chn, uint8_t note, uint8_t vel) +{ + u_char c[3]; + + + if (note > 127 || chn > 15) + return (EINVAL); + + if (vel > 127) + vel = 127; + + if (vel == 64) { + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = 0; + } else { + c[0] = 0x80 | (chn & 0x0f); /* Note off. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + } + + return midisynth_writeraw(n, c, 3); +} + +static int +midisynth_setinstr(void *n, uint8_t chn, uint16_t instr) +{ + u_char c[2]; + + if (instr > 127 || chn > 15) + return EINVAL; + + c[0] = 0xc0 | (chn & 0x0f); /* Progamme change. */ + c[1] = instr + midi_instroff; + + return midisynth_writeraw(n, c, 2); +} + +static int +midisynth_startnote(void *n, uint8_t chn, uint8_t note, uint8_t vel) +{ + u_char c[3]; + + if (note > 127 || chn > 15) + return EINVAL; + + if (vel > 127) + vel = 127; + + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + + return midisynth_writeraw(n, c, 3); +} +static int +midisynth_alloc(void *n, uint8_t chan, uint8_t note) +{ + return chan; +} + +static int +midisynth_controller(void *n, uint8_t chn, uint8_t ctrlnum, uint16_t val) +{ + u_char c[3]; + + if (ctrlnum > 127 || chn > 15) + return EINVAL; + + c[0] = 0xb0 | (chn & 0x0f); /* Control Message. */ + c[1] = ctrlnum; + c[2] = val; + return midisynth_writeraw(n, c, 3); +} + +static int +midisynth_bender(void *n, uint8_t chn, uint16_t val) +{ + u_char c[3]; + + + if (val > 16383 || chn > 15) + return EINVAL; + + c[0] = 0xe0 | (chn & 0x0f); /* Pitch bend. */ + c[1] = (u_char)val & 0x7f; + c[2] = (u_char)(val >> 7) & 0x7f; + + return midisynth_writeraw(n, c, 3); +} + +/* + * Single point of midi destructions. + */ +static int +midi_destroy(struct snd_midi *m, int midiuninit) +{ + + mtx_assert(&midistat_lock, MA_OWNED); + mtx_assert(&m->lock, MA_OWNED); + + MIDI_DEBUG(3, printf("midi_destroy\n")); + m->dev->si_drv1 = NULL; + destroy_dev(m->dev); + TAILQ_REMOVE(&midi_devs, m, link); + if (midiuninit) + MPU_UNINIT(m, m->cookie); + free(MIDIQ_BUF(m->inq), M_MIDI); + free(MIDIQ_BUF(m->outq), M_MIDI); + mtx_destroy(&m->qlock); + mtx_destroy(&m->lock); + free(m, M_MIDI); + return 0; +} + +/* + * Load and unload functions, creates the /dev/midistat device + */ + +static int +midi_load() +{ + mtx_init(&midistat_lock, "midistat lock", NULL, 0); + TAILQ_INIT(&midi_devs); /* Initialize the queue. */ + + midistat_dev = make_dev(&midistat_cdevsw, + MIDIMKMINOR(0, MIDI_DEV_MIDICTL, 0), + UID_ROOT, GID_WHEEL, 0666, "midistat"); + + return 0; +} + +static int +midi_unload() +{ + struct snd_midi *m; + int retval; + + MIDI_DEBUG(1, printf("midi_unload()\n")); + retval = EBUSY; + mtx_lock(&midistat_lock); + if (midistat_isopen) + goto exit0; + + TAILQ_FOREACH(m, &midi_devs, link) { + mtx_lock(&m->lock); + if (m->busy) + retval = EBUSY; + else + retval = midi_destroy(m, 1); + if (retval) + goto exit1; + } + + destroy_dev(midistat_dev); + /* + * Made it here then unload is complete + */ + mtx_destroy(&midistat_lock); + return 0; + +exit1: + mtx_unlock(&m->lock); +exit0: + mtx_unlock(&midistat_lock); + if (retval) + MIDI_DEBUG(2, printf("midi_unload: failed\n")); + return retval; +} + +extern int seq_modevent(module_t mod, int type, void *data); + +static int +midi_modevent(module_t mod, int type, void *data) +{ + int retval; + + retval = 0; + + switch (type) { + case MOD_LOAD: + retval = midi_load(); +#if 0 + if (retval == 0) + retval = seq_modevent(mod, type, data); +#endif + break; + + case MOD_UNLOAD: + retval = midi_unload(); +#if 0 + if (retval == 0) + retval = seq_modevent(mod, type, data); +#endif + break; + + default: + break; + } + + return retval; +} + +kobj_t +midimapper_addseq(void *arg1, int *unit, void **cookie) +{ + unit = 0; + + return (kobj_t)arg1; +} + +int +midimapper_open(void *arg1, void **cookie) +{ + int retval = 0; + struct snd_midi *m; + + mtx_lock(&midistat_lock); + + TAILQ_FOREACH(m, &midi_devs, link) { + retval++; + } + + mtx_unlock(&midistat_lock); + return retval; +} + +int +midimapper_close(void *arg1, void *cookie) +{ + return 0; +} + +kobj_t +midimapper_fetch_synth(void *arg, void *cookie, int unit) +{ + struct snd_midi *m; + int retval = 0; + + mtx_lock(&midistat_lock); + + TAILQ_FOREACH(m, &midi_devs, link) { + if (unit == retval) { + mtx_unlock(&midistat_lock); + return (kobj_t)m->synth; + } + retval++; + } + + mtx_unlock(&midistat_lock); + return NULL; +} + +DEV_MODULE(midi, midi_modevent, NULL); +MODULE_VERSION(midi, 1); --- sys/dev/sound/midi/midi.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/midi.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/midi/midi.h,v 1.16 2007/02/25 13:51:51 netchild Exp $ + */ + +#ifndef MIDI_H +#define MIDI_H + +#include +#include + +MALLOC_DECLARE(M_MIDI); + +#define M_RX 0x01 +#define M_TX 0x02 +#define M_RXEN 0x04 +#define M_TXEN 0x08 + +#define MIDI_TYPE unsigned char + +struct snd_midi; + +struct snd_midi * +midi_init(kobj_class_t _mpu_cls, int _unit, int _channel, void *cookie); +int midi_uninit(struct snd_midi *_m); +int midi_out(struct snd_midi *_m, MIDI_TYPE *_buf, int _size); +int midi_in(struct snd_midi *_m, MIDI_TYPE *_buf, int _size); + +kobj_t midimapper_addseq(void *arg1, int *unit, void **cookie); +int midimapper_open(void *arg1, void **cookie); +int midimapper_close(void *arg1, void *cookie); +kobj_t midimapper_fetch_synth(void *arg, void *cookie, int unit); + +#endif --- sys/dev/sound/midi/midiq.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/midiq.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,106 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/midi/midiq.h,v 1.3 2007/02/25 13:51:51 netchild Exp $ + */ + +#ifndef MIDIQ_H +#define MIDIQ_H + +#define MIDIQ_MOVE(a,b,c) bcopy(b,a,c) + +#define MIDIQ_HEAD(name, type) \ +struct name { \ + int h, t, s; \ + type * b; \ +} + +#define MIDIQ_INIT(head, buf, size) do { \ + (head).h=(head).t=0; \ + (head).s=size; \ + (head).b=buf; \ +} while (0) + +#define MIDIQ_EMPTY(head) ((head).h == (head).t ) + +#define MIDIQ_LENBASE(head) ((head).h - (head).t < 0 ? \ + (head).h - (head).t + (head).s : \ + (head).h - (head).t) + +#define MIDIQ_FULL(head) ((head).h == -1) +#define MIDIQ_AVAIL(head) (MIDIQ_FULL(head) ? 0 : (head).s - MIDIQ_LENBASE(head)) +#define MIDIQ_LEN(head) ((head).s - MIDIQ_AVAIL(head)) +#define MIDIQ_DEBUG 0 +/* + * No protection against overflow, underflow + */ +#define MIDIQ_ENQ(head, buf, size) do { \ + if(MIDIQ_DEBUG)\ + printf("#1 %p %p bytes copied %jd tran req s %d h %d t %d\n", \ + &(head).b[(head).h], (buf), \ + (intmax_t)(sizeof(*(head).b) * \ + MIN( (size), (head).s - (head).h) ), \ + (size), (head).h, (head).t); \ + MIDIQ_MOVE(&(head).b[(head).h], (buf), sizeof(*(head).b) * MIN((size), (head).s - (head).h)); \ + if( (head).s - (head).h < (size) ) { \ + if(MIDIQ_DEBUG) \ + printf("#2 %p %p bytes copied %jd\n", (head).b, (buf) + (head).s - (head).h, (intmax_t)sizeof(*(head).b) * ((size) - (head).s + (head).h) ); \ + MIDIQ_MOVE((head).b, (buf) + (head).s - (head).h, sizeof(*(head).b) * ((size) - (head).s + (head).h) ); \ + } \ + (head).h+=(size); \ + (head).h%=(head).s; \ + if(MIDIQ_EMPTY(head)) (head).h=-1; \ + if(MIDIQ_DEBUG)\ + printf("#E h %d t %d\n", (head).h, (head).t); \ +} while (0) + +#define MIDIQ_DEQ_I(head, buf, size, move, update) do { \ + if(MIDIQ_FULL(head)) (head).h=(head).t; \ + if(MIDIQ_DEBUG)\ + printf("#1 %p %p bytes copied %jd tran req s %d h %d t %d\n", &(head).b[(head).t], (buf), (intmax_t)sizeof(*(head).b) * MIN((size), (head).s - (head).t), (size), (head).h, (head).t); \ + if (move) MIDIQ_MOVE((buf), &(head).b[(head).t], sizeof(*(head).b) * MIN((size), (head).s - (head).t)); \ + if( (head).s - (head).t < (size) ) { \ + if(MIDIQ_DEBUG) \ + printf("#2 %p %p bytes copied %jd\n", (head).b, (buf) + (head).s - (head).t, (intmax_t)sizeof(*(head).b) * ((size) - (head).s + (head).t) ); \ + if (move) MIDIQ_MOVE((buf) + (head).s - (head).t, (head).b, sizeof(*(head).b) * ((size) - (head).s + (head).t) ); \ + } \ + if (update) { \ + (head).t+=(size); \ + (head).t%=(head).s; \ + } else { \ + if (MIDIQ_EMPTY(head)) (head).h=-1; \ + } \ + if(MIDIQ_DEBUG)\ + printf("#E h %d t %d\n", (head).h, (head).t); \ +} while (0) + +#define MIDIQ_SIZE(head) ((head).s) +#define MIDIQ_CLEAR(head) ((head).h = (head).t = 0) +#define MIDIQ_BUF(head) ((head).b) +#define MIDIQ_DEQ(head, buf, size) MIDIQ_DEQ_I(head, buf, size, 1, 1) +#define MIDIQ_PEEK(head, buf, size) MIDIQ_DEQ_I(head, buf, size, 1, 0) +#define MIDIQ_POP(head, size) MIDIQ_DEQ_I(head, &head, size, 0, 1) + +#endif --- sys/dev/sound/midi/mpu401.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpu401.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,287 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * 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 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 AUTHOR 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 +__FBSDID("$FreeBSD: src/sys/dev/sound/midi/mpu401.c,v 1.3 2007/02/25 13:51:51 netchild Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* to get driver_intr_t */ + +#include +#include + +#include "mpu_if.h" +#include "mpufoi_if.h" + +#define MPU_DATAPORT 0 +#define MPU_CMDPORT 1 +#define MPU_STATPORT 1 +#define MPU_RESET 0xff +#define MPU_UART 0x3f +#define MPU_ACK 0xfe +#define MPU_STATMASK 0xc0 +#define MPU_OUTPUTBUSY 0x40 +#define MPU_INPUTBUSY 0x80 +#define MPU_TRYDATA 50 +#define MPU_DELAY 2500 + +#define CMD(m,d) MPUFOI_WRITE(m, m->cookie, MPU_CMDPORT,d) +#define STATUS(m) MPUFOI_READ(m, m->cookie, MPU_STATPORT) +#define READ(m) MPUFOI_READ(m, m->cookie, MPU_DATAPORT) +#define WRITE(m,d) MPUFOI_WRITE(m, m->cookie, MPU_DATAPORT,d) + +struct mpu401 { + KOBJ_FIELDS; + struct snd_midi *mid; + int flags; + driver_intr_t *si; + void *cookie; + struct callout timer; +}; + +static void mpu401_timeout(void *m); +static mpu401_intr_t mpu401_intr; + +static int mpu401_minit(kobj_t obj, struct mpu401 *m); +static int mpu401_muninit(kobj_t obj, struct mpu401 *m); +static int mpu401_minqsize(kobj_t obj, struct mpu401 *m); +static int mpu401_moutqsize(kobj_t obj, struct mpu401 *m); +static void mpu401_mcallback(kobj_t obj, struct mpu401 *m, int flags); +static void mpu401_mcallbackp(kobj_t obj, struct mpu401 *m, int flags); +static const char *mpu401_mdescr(kobj_t obj, struct mpu401 *m, int verbosity); +static const char *mpu401_mprovider(kobj_t obj, struct mpu401 *m); + +static kobj_method_t mpu401_methods[] = { + KOBJMETHOD(mpu_init, mpu401_minit), + KOBJMETHOD(mpu_uninit, mpu401_muninit), + KOBJMETHOD(mpu_inqsize, mpu401_minqsize), + KOBJMETHOD(mpu_outqsize, mpu401_moutqsize), + KOBJMETHOD(mpu_callback, mpu401_mcallback), + KOBJMETHOD(mpu_callbackp, mpu401_mcallbackp), + KOBJMETHOD(mpu_descr, mpu401_mdescr), + KOBJMETHOD(mpu_provider, mpu401_mprovider), + {0, 0} +}; + +DEFINE_CLASS(mpu401, mpu401_methods, 0); + +void +mpu401_timeout(void *a) +{ + struct mpu401 *m = (struct mpu401 *)a; + + if (m->si) + (m->si)(m->cookie); + +} +static int +mpu401_intr(struct mpu401 *m) +{ +#define MPU_INTR_BUF 16 + MIDI_TYPE b[MPU_INTR_BUF]; + int i; + int s; + +/* + printf("mpu401_intr\n"); +*/ +#define RXRDY(m) ( (STATUS(m) & MPU_INPUTBUSY) == 0) +#define TXRDY(m) ( (STATUS(m) & MPU_OUTPUTBUSY) == 0) +#if 0 +#define D(x,l) printf("mpu401_intr %d %x %s %s\n",l, x, x&MPU_INPUTBUSY?"RX":"", x&MPU_OUTPUTBUSY?"TX":"") +#else +#define D(x,l) +#endif + i = 0; + s = STATUS(m); + D(s, 1); + while ((s & MPU_INPUTBUSY) == 0 && i < MPU_INTR_BUF) { + b[i] = READ(m); +/* + printf("mpu401_intr in i %d d %d\n", i, b[i]); +*/ + i++; + s = STATUS(m); + } + if (i) + midi_in(m->mid, b, i); + i = 0; + while (!(s & MPU_OUTPUTBUSY) && i < MPU_INTR_BUF) { + if (midi_out(m->mid, b, 1)) { +/* + printf("mpu401_intr out i %d d %d\n", i, b[0]); +*/ + + WRITE(m, *b); + } else { +/* + printf("mpu401_intr write: no output\n"); +*/ + return 0; + } + i++; + /* DELAY(100); */ + s = STATUS(m); + } + + if ((m->flags & M_TXEN) && (m->si)) { + callout_reset(&m->timer, 1, mpu401_timeout, m); + } + return (m->flags & M_TXEN) == M_TXEN; +} + +struct mpu401 * +mpu401_init(kobj_class_t cls, void *cookie, driver_intr_t softintr, + mpu401_intr_t ** cb) +{ + struct mpu401 *m; + + *cb = NULL; + m = malloc(sizeof(*m), M_MIDI, M_NOWAIT | M_ZERO); + + if (!m) + return NULL; + + kobj_init((kobj_t)m, cls); + + callout_init(&m->timer, 1); + + m->si = softintr; + m->cookie = cookie; + m->flags = 0; + + m->mid = midi_init(&mpu401_class, 0, 0, m); + if (!m->mid) + goto err; + *cb = mpu401_intr; + return m; +err: + printf("mpu401_init error\n"); + free(m, M_MIDI); + return NULL; +} + +int +mpu401_uninit(struct mpu401 *m) +{ + int retval; + + CMD(m, MPU_RESET); + retval = midi_uninit(m->mid); + if (retval) + return retval; + free(m, M_MIDI); + return 0; +} + +static int +mpu401_minit(kobj_t obj, struct mpu401 *m) +{ + int i; + + CMD(m, MPU_RESET); + CMD(m, MPU_UART); + return 0; + i = 0; + while (++i < 2000) { + if (RXRDY(m)) + if (READ(m) == MPU_ACK) + break; + } + + if (i < 2000) { + CMD(m, MPU_UART); + return 0; + } + printf("mpu401_minit failed active sensing\n"); + return 1; +} + + +int +mpu401_muninit(kobj_t obj, struct mpu401 *m) +{ + + return MPUFOI_UNINIT(m, m->cookie); +} + +int +mpu401_minqsize(kobj_t obj, struct mpu401 *m) +{ + return 128; +} + +int +mpu401_moutqsize(kobj_t obj, struct mpu401 *m) +{ + return 128; +} + +static void +mpu401_mcallback(kobj_t obj, struct mpu401 *m, int flags) +{ +#if 0 + printf("mpu401_callback %s %s %s %s\n", + flags & M_RX ? "M_RX" : "", + flags & M_TX ? "M_TX" : "", + flags & M_RXEN ? "M_RXEN" : "", + flags & M_TXEN ? "M_TXEN" : ""); +#endif + if (flags & M_TXEN && m->si) { + callout_reset(&m->timer, 1, mpu401_timeout, m); + } + m->flags = flags; +} + +static void +mpu401_mcallbackp(kobj_t obj, struct mpu401 *m, int flags) +{ +/* printf("mpu401_callbackp\n"); */ + mpu401_mcallback(obj, m, flags); +} + +static const char * +mpu401_mdescr(kobj_t obj, struct mpu401 *m, int verbosity) +{ + + return "descr mpu401"; +} + +static const char * +mpu401_mprovider(kobj_t obj, struct mpu401 *m) +{ + return "provider mpu401"; +} --- sys/dev/sound/midi/mpu401.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpu401.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/midi/mpu401.h,v 1.3 2007/02/25 13:51:51 netchild Exp $ + */ + +#ifndef MPU401_H +#define MPU401_H + +struct mpu401; + +typedef int mpu401_intr_t(struct mpu401 *_obj); + +extern struct mpu401 * +mpu401_init(kobj_class_t _cls, void *cookie, driver_intr_t *_softintr, + mpu401_intr_t ** _cb); +extern int mpu401_uninit(struct mpu401 *_obj); + +#endif --- sys/dev/sound/midi/mpu_if.m.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpu_if.m Thu Jul 12 12:04:19 2007 @@ -0,0 +1,74 @@ +#- +# Copyright (c) 2003 Mathew Kanner +# 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 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 AUTHOR 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. +# +# $FreeBSD: src/sys/dev/sound/midi/mpu_if.m,v 1.3 2007/02/25 13:51:51 netchild Exp $ +# + +#include + +INTERFACE mpu; + +METHOD int inqsize { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD int outqsize { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD int init { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD void callbackp { + struct snd_midi *_kobj; + void *_cookie; + int _flags; +}; + +METHOD void callback { + struct snd_midi *_kobj; + void *_cookie; + int _flags; +}; + +METHOD const char * provider { + struct snd_midi *_kobj; + void *_cookie; +}; + +METHOD const char * descr { + struct snd_midi *_kobj; + void *_cookie; + int _verbosity; +}; + +METHOD int uninit { + struct snd_midi *_kobj; + void *_cookie; +}; --- sys/dev/sound/midi/mpufoi_if.m.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/mpufoi_if.m Thu Jul 12 12:04:19 2007 @@ -0,0 +1,50 @@ +#- +# Copyright (c) 2003 Mathew Kanner +# 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 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 AUTHOR 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. +# +# $FreeBSD: src/sys/dev/sound/midi/mpufoi_if.m,v 1.3 2007/02/25 13:51:52 netchild Exp $ +# + +#include +#include + +INTERFACE mpufoi; + +METHOD unsigned char read { + struct mpu401 *_kobj; + void *_cookie; + int _reg; +}; + +METHOD void write { + struct mpu401 *_kobj; + void *_cookie; + int _reg; + unsigned char _d; +}; + +METHOD int uninit { + struct mpu401 *_kobj; + void *_cookie; +}; --- sys/dev/sound/midi/sequencer.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/sequencer.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,2088 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * Copyright (c) 1993 Hannu Savolainen + * 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 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 AUTHOR 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. + */ + +/* + * The sequencer personality manager. + */ + +#include +__FBSDID("$FreeBSD: src/sys/dev/sound/midi/sequencer.c,v 1.26 2007/03/15 14:57:54 ariff Exp $"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include /* for DATA_SET */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for DELAY */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include "synth_if.h" + +#include + +#define TMR_TIMERBASE 13 + +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + * synthesizer and MIDI output) */ +#define SND_DEV_MUSIC 8 /* /dev/music, level 2 interface */ + +/* Length of a sequencer event. */ +#define EV_SZ 8 +#define IEV_SZ 8 + +/* Lookup modes */ +#define LOOKUP_EXIST (0) +#define LOOKUP_OPEN (1) +#define LOOKUP_CLOSE (2) + +#define PCMMKMINOR(u, d, c) \ + ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) +#define MIDIMKMINOR(u, d, c) PCMMKMINOR(u, d, c) +#define MIDIUNIT(y) ((minor(y) >> 4) & 0x0f) +#define MIDIDEV(y) (minor(y) & 0x0f) + +/* These are the entries to the sequencer driver. */ +static d_open_t seq_open; +static d_close_t seq_close; +static d_ioctl_t seq_ioctl; +static d_read_t seq_read; +static d_write_t seq_write; +static d_poll_t seq_poll; + +static struct cdevsw seq_cdevsw = { + .d_version = D_VERSION, + .d_open = seq_open, + .d_close = seq_close, + .d_read = seq_read, + .d_write = seq_write, + .d_ioctl = seq_ioctl, + .d_poll = seq_poll, + .d_name = "sequencer", +}; + +struct seq_softc { + KOBJ_FIELDS; + + struct mtx seq_lock, q_lock; + struct cv empty_cv, reset_cv, in_cv, out_cv, state_cv, th_cv; + + MIDIQ_HEAD(, u_char) in_q, out_q; + + u_long flags; + /* Flags (protected by flag_mtx of mididev_info) */ + int fflags; /* Access mode */ + int music; + + int out_water; /* Sequence output threshould */ + snd_sync_parm sync_parm; /* AIOSYNC parameter set */ + struct thread *sync_thread; /* AIOSYNCing thread */ + struct selinfo in_sel, out_sel; + int midi_number; + struct cdev *seqdev, *musicdev; + int unit; + int maxunits; + kobj_t *midis; + int *midi_flags; + kobj_t mapper; + void *mapper_cookie; + struct timeval timerstop, timersub; + int timerbase, tempo; + int timerrun; + int done; + int playing; + int recording; + int busy; + int pre_event_timeout; + int waiting; +}; + +/* + * Module specific stuff, including how many sequecers + * we currently own. + */ + +SYSCTL_NODE(_hw_midi, OID_AUTO, seq, CTLFLAG_RD, 0, "Midi sequencer"); + +int seq_debug; +/* XXX: should this be moved into debug.midi? */ +SYSCTL_INT(_hw_midi_seq, OID_AUTO, debug, CTLFLAG_RW, &seq_debug, 0, ""); + +midi_cmdtab cmdtab_seqevent[] = { + {SEQ_NOTEOFF, "SEQ_NOTEOFF"}, + {SEQ_NOTEON, "SEQ_NOTEON"}, + {SEQ_WAIT, "SEQ_WAIT"}, + {SEQ_PGMCHANGE, "SEQ_PGMCHANGE"}, + {SEQ_SYNCTIMER, "SEQ_SYNCTIMER"}, + {SEQ_MIDIPUTC, "SEQ_MIDIPUTC"}, + {SEQ_DRUMON, "SEQ_DRUMON"}, + {SEQ_DRUMOFF, "SEQ_DRUMOFF"}, + {SEQ_ECHO, "SEQ_ECHO"}, + {SEQ_AFTERTOUCH, "SEQ_AFTERTOUCH"}, + {SEQ_CONTROLLER, "SEQ_CONTROLLER"}, + {SEQ_BALANCE, "SEQ_BALANCE"}, + {SEQ_VOLMODE, "SEQ_VOLMODE"}, + {SEQ_FULLSIZE, "SEQ_FULLSIZE"}, + {SEQ_PRIVATE, "SEQ_PRIVATE"}, + {SEQ_EXTENDED, "SEQ_EXTENDED"}, + {EV_SEQ_LOCAL, "EV_SEQ_LOCAL"}, + {EV_TIMING, "EV_TIMING"}, + {EV_CHN_COMMON, "EV_CHN_COMMON"}, + {EV_CHN_VOICE, "EV_CHN_VOICE"}, + {EV_SYSEX, "EV_SYSEX"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_seqioctl[] = { + {SNDCTL_SEQ_RESET, "SNDCTL_SEQ_RESET"}, + {SNDCTL_SEQ_SYNC, "SNDCTL_SEQ_SYNC"}, + {SNDCTL_SYNTH_INFO, "SNDCTL_SYNTH_INFO"}, + {SNDCTL_SEQ_CTRLRATE, "SNDCTL_SEQ_CTRLRATE"}, + {SNDCTL_SEQ_GETOUTCOUNT, "SNDCTL_SEQ_GETOUTCOUNT"}, + {SNDCTL_SEQ_GETINCOUNT, "SNDCTL_SEQ_GETINCOUNT"}, + {SNDCTL_SEQ_PERCMODE, "SNDCTL_SEQ_PERCMODE"}, + {SNDCTL_FM_LOAD_INSTR, "SNDCTL_FM_LOAD_INSTR"}, + {SNDCTL_SEQ_TESTMIDI, "SNDCTL_SEQ_TESTMIDI"}, + {SNDCTL_SEQ_RESETSAMPLES, "SNDCTL_SEQ_RESETSAMPLES"}, + {SNDCTL_SEQ_NRSYNTHS, "SNDCTL_SEQ_NRSYNTHS"}, + {SNDCTL_SEQ_NRMIDIS, "SNDCTL_SEQ_NRMIDIS"}, + {SNDCTL_SEQ_GETTIME, "SNDCTL_SEQ_GETTIME"}, + {SNDCTL_MIDI_INFO, "SNDCTL_MIDI_INFO"}, + {SNDCTL_SEQ_THRESHOLD, "SNDCTL_SEQ_THRESHOLD"}, + {SNDCTL_SYNTH_MEMAVL, "SNDCTL_SYNTH_MEMAVL"}, + {SNDCTL_FM_4OP_ENABLE, "SNDCTL_FM_4OP_ENABLE"}, + {SNDCTL_PMGR_ACCESS, "SNDCTL_PMGR_ACCESS"}, + {SNDCTL_SEQ_PANIC, "SNDCTL_SEQ_PANIC"}, + {SNDCTL_SEQ_OUTOFBAND, "SNDCTL_SEQ_OUTOFBAND"}, + {SNDCTL_TMR_TIMEBASE, "SNDCTL_TMR_TIMEBASE"}, + {SNDCTL_TMR_START, "SNDCTL_TMR_START"}, + {SNDCTL_TMR_STOP, "SNDCTL_TMR_STOP"}, + {SNDCTL_TMR_CONTINUE, "SNDCTL_TMR_CONTINUE"}, + {SNDCTL_TMR_TEMPO, "SNDCTL_TMR_TEMPO"}, + {SNDCTL_TMR_SOURCE, "SNDCTL_TMR_SOURCE"}, + {SNDCTL_TMR_METRONOME, "SNDCTL_TMR_METRONOME"}, + {SNDCTL_TMR_SELECT, "SNDCTL_TMR_SELECT"}, + {SNDCTL_MIDI_PRETIME, "SNDCTL_MIDI_PRETIME"}, + {AIONWRITE, "AIONWRITE"}, + {AIOGSIZE, "AIOGSIZE"}, + {AIOSSIZE, "AIOSSIZE"}, + {AIOGFMT, "AIOGFMT"}, + {AIOSFMT, "AIOSFMT"}, + {AIOGMIX, "AIOGMIX"}, + {AIOSMIX, "AIOSMIX"}, + {AIOSTOP, "AIOSTOP"}, + {AIOSYNC, "AIOSYNC"}, + {AIOGCAP, "AIOGCAP"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_timer[] = { + {TMR_WAIT_REL, "TMR_WAIT_REL"}, + {TMR_WAIT_ABS, "TMR_WAIT_ABS"}, + {TMR_STOP, "TMR_STOP"}, + {TMR_START, "TMR_START"}, + {TMR_CONTINUE, "TMR_CONTINUE"}, + {TMR_TEMPO, "TMR_TEMPO"}, + {TMR_ECHO, "TMR_ECHO"}, + {TMR_CLOCK, "TMR_CLOCK"}, + {TMR_SPP, "TMR_SPP"}, + {TMR_TIMESIG, "TMR_TIMESIG"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_seqcv[] = { + {MIDI_NOTEOFF, "MIDI_NOTEOFF"}, + {MIDI_NOTEON, "MIDI_NOTEON"}, + {MIDI_KEY_PRESSURE, "MIDI_KEY_PRESSURE"}, + {-1, NULL}, +}; + +midi_cmdtab cmdtab_seqccmn[] = { + {MIDI_CTL_CHANGE, "MIDI_CTL_CHANGE"}, + {MIDI_PGM_CHANGE, "MIDI_PGM_CHANGE"}, + {MIDI_CHN_PRESSURE, "MIDI_CHN_PRESSURE"}, + {MIDI_PITCH_BEND, "MIDI_PITCH_BEND"}, + {MIDI_SYSTEM_PREFIX, "MIDI_SYSTEM_PREFIX"}, + {-1, NULL}, +}; + +/* + * static const char *mpu401_mprovider(kobj_t obj, struct mpu401 *m); + */ + +static kobj_method_t seq_methods[] = { + /* KOBJMETHOD(mpu_provider,mpu401_mprovider), */ + {0, 0} +}; + +DEFINE_CLASS(sequencer, seq_methods, 0); + +/* The followings are the local function. */ +static int seq_convertold(u_char *event, u_char *out); + +/* + * static void seq_midiinput(struct seq_softc * scp, void *md); + */ +static void seq_reset(struct seq_softc *scp); +static int seq_sync(struct seq_softc *scp); + +static int seq_processevent(struct seq_softc *scp, u_char *event); + +static int seq_timing(struct seq_softc *scp, u_char *event); +static int seq_local(struct seq_softc *scp, u_char *event); + +static int seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event); +static int seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event); +static int seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event); + +static int seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md); +void seq_copytoinput(struct seq_softc *scp, u_char *event, int len); +int seq_modevent(module_t mod, int type, void *data); +struct seq_softc *seqs[10]; +static struct mtx seqinfo_mtx; +static u_long nseq = 0; + +static void timer_start(struct seq_softc *t); +static void timer_stop(struct seq_softc *t); +static void timer_setvals(struct seq_softc *t, int tempo, int timerbase); +static void timer_wait(struct seq_softc *t, int ticks, int wait_abs); +static int timer_now(struct seq_softc *t); + + +static void +timer_start(struct seq_softc *t) +{ + t->timerrun = 1; + getmicrotime(&t->timersub); +} + +static void +timer_continue(struct seq_softc *t) +{ + struct timeval now; + + if (t->timerrun == 1) + return; + t->timerrun = 1; + getmicrotime(&now); + timevalsub(&now, &t->timerstop); + timevaladd(&t->timersub, &now); +} + +static void +timer_stop(struct seq_softc *t) +{ + t->timerrun = 0; + getmicrotime(&t->timerstop); +} + +static void +timer_setvals(struct seq_softc *t, int tempo, int timerbase) +{ + t->tempo = tempo; + t->timerbase = timerbase; +} + +static void +timer_wait(struct seq_softc *t, int ticks, int wait_abs) +{ + struct timeval now, when; + int ret; + unsigned long long i; + + while (t->timerrun == 0) { + SEQ_DEBUG(2, printf("Timer wait when timer isn't running\n")); + /* + * The old sequencer used timeouts that only increased + * the timer when the timer was running. + * Hence the sequencer would stick (?) if the + * timer was disabled. + */ + cv_wait(&t->reset_cv, &t->seq_lock); + if (t->playing == 0) + return; + } + + i = ticks * 60ull * 1000000ull / (t->tempo * t->timerbase); + + when.tv_sec = i / 1000000; + when.tv_usec = i % 1000000; + +#if 0 + printf("timer_wait tempo %d timerbase %d ticks %d abs %d u_sec %llu\n", + t->tempo, t->timerbase, ticks, wait_abs, i); +#endif + + if (wait_abs != 0) { + getmicrotime(&now); + timevalsub(&now, &t->timersub); + timevalsub(&when, &now); + } + if (when.tv_sec < 0 || when.tv_usec < 0) { + SEQ_DEBUG(3, + printf("seq_timer error negative time %lds.%06lds\n", + (long)when.tv_sec, (long)when.tv_usec)); + return; + } + i = when.tv_sec * 1000000ull; + i += when.tv_usec; + i *= hz; + i /= 1000000ull; +#if 0 + printf("seq_timer usec %llu ticks %llu\n", + when.tv_sec * 1000000ull + when.tv_usec, i); +#endif + t->waiting = 1; + ret = cv_timedwait(&t->reset_cv, &t->seq_lock, i + 1); + t->waiting = 0; + + if (ret != EWOULDBLOCK) + SEQ_DEBUG(3, printf("seq_timer didn't timeout\n")); + +} + +static int +timer_now(struct seq_softc *t) +{ + struct timeval now; + unsigned long long i; + int ret; + + if (t->timerrun == 0) + now = t->timerstop; + else + getmicrotime(&now); + + timevalsub(&now, &t->timersub); + + i = now.tv_sec * 1000000ull; + i += now.tv_usec; + i *= t->timerbase; +/* i /= t->tempo; */ + i /= 1000000ull; + + ret = i; + /* + * printf("timer_now: %llu %d\n", i, ret); + */ + + return ret; +} + +static void +seq_eventthread(void *arg) +{ + struct seq_softc *scp = arg; + char event[EV_SZ]; + + mtx_lock(&scp->seq_lock); + SEQ_DEBUG(2, printf("seq_eventthread started\n")); + while (scp->done == 0) { +restart: + while (scp->playing == 0) { + cv_wait(&scp->state_cv, &scp->seq_lock); + if (scp->done) + goto done; + } + + while (MIDIQ_EMPTY(scp->out_q)) { + cv_broadcast(&scp->empty_cv); + cv_wait(&scp->out_cv, &scp->seq_lock); + if (scp->playing == 0) + goto restart; + if (scp->done) + goto done; + } + + MIDIQ_DEQ(scp->out_q, event, EV_SZ); + + if (MIDIQ_AVAIL(scp->out_q) < scp->out_water) { + cv_broadcast(&scp->out_cv); + selwakeup(&scp->out_sel); + } + seq_processevent(scp, event); + } + +done: + cv_broadcast(&scp->th_cv); + mtx_unlock(&scp->seq_lock); + mtx_lock(&Giant); + SEQ_DEBUG(2, printf("seq_eventthread finished\n")); + kthread_exit(0); +} + +/* + * seq_processevent: This maybe called by the event thread or the IOCTL + * handler for queued and out of band events respectively. + */ +static int +seq_processevent(struct seq_softc *scp, u_char *event) +{ + int ret; + kobj_t m; + + ret = 0; + + if (event[0] == EV_SEQ_LOCAL) + ret = seq_local(scp, event); + else if (event[0] == EV_TIMING) + ret = seq_timing(scp, event); + else if (event[0] != EV_CHN_VOICE && + event[0] != EV_CHN_COMMON && + event[0] != EV_SYSEX && + event[0] != SEQ_MIDIPUTC) { + ret = 1; + SEQ_DEBUG(2, printf("seq_processevent not known %d\n", + event[0])); + } else if (seq_fetch_mid(scp, event[1], &m) != 0) { + ret = 1; + SEQ_DEBUG(2, printf("seq_processevent midi unit not found %d\n", + event[1])); + } else + switch (event[0]) { + case EV_CHN_VOICE: + ret = seq_chnvoice(scp, m, event); + break; + case EV_CHN_COMMON: + ret = seq_chncommon(scp, m, event); + break; + case EV_SYSEX: + ret = seq_sysex(scp, m, event); + break; + case SEQ_MIDIPUTC: + mtx_unlock(&scp->seq_lock); + ret = SYNTH_WRITERAW(m, &event[2], 1); + mtx_lock(&scp->seq_lock); + break; + } + return ret; +} + +static int +seq_addunit(void) +{ + struct seq_softc *scp; + int ret; + u_char *buf; + + /* Allocate the softc. */ + ret = ENOMEM; + scp = malloc(sizeof(*scp), M_DEVBUF, M_NOWAIT | M_ZERO); + if (scp == NULL) { + SEQ_DEBUG(1, printf("seq_addunit: softc allocation failed.\n")); + goto err; + } + kobj_init((kobj_t)scp, &sequencer_class); + + buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); + if (buf == NULL) + goto err; + MIDIQ_INIT(scp->in_q, buf, EV_SZ * 1024); + buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); + if (buf == NULL) + goto err; + MIDIQ_INIT(scp->out_q, buf, EV_SZ * 1024); + ret = EINVAL; + + scp->midis = malloc(sizeof(kobj_t) * 32, M_TEMP, M_NOWAIT | M_ZERO); + scp->midi_flags = malloc(sizeof(*scp->midi_flags) * 32, M_TEMP, + M_NOWAIT | M_ZERO); + + if (scp->midis == NULL || scp->midi_flags == NULL) + goto err; + + scp->flags = 0; + + mtx_init(&scp->seq_lock, "seqflq", NULL, 0); + cv_init(&scp->state_cv, "seqstate"); + cv_init(&scp->empty_cv, "seqempty"); + cv_init(&scp->reset_cv, "seqtimer"); + cv_init(&scp->out_cv, "seqqout"); + cv_init(&scp->in_cv, "seqqin"); + cv_init(&scp->th_cv, "seqstart"); + + /* + * Init the damn timer + */ + + scp->mapper = midimapper_addseq(scp, &scp->unit, &scp->mapper_cookie); + if (scp->mapper == NULL) + goto err; + + scp->seqdev = make_dev(&seq_cdevsw, + MIDIMKMINOR(scp->unit, SND_DEV_SEQ, 0), UID_ROOT, + GID_WHEEL, 0666, "sequencer%d", scp->unit); + + scp->musicdev = make_dev(&seq_cdevsw, + MIDIMKMINOR(scp->unit, SND_DEV_MUSIC, 0), UID_ROOT, + GID_WHEEL, 0666, "music%d", scp->unit); + + if (scp->seqdev == NULL || scp->musicdev == NULL) + goto err; + /* + * TODO: Add to list of sequencers this module provides + */ + + ret = kthread_create(seq_eventthread, scp, NULL, RFHIGHPID, 0, + "sequencer %02d", scp->unit); + + if (ret) + goto err; + + scp->seqdev->si_drv1 = scp->musicdev->si_drv1 = scp; + + SEQ_DEBUG(2, printf("sequencer %d created scp %p\n", scp->unit, scp)); + + ret = 0; + + mtx_lock(&seqinfo_mtx); + seqs[nseq++] = scp; + mtx_unlock(&seqinfo_mtx); + + goto ok; + +err: + if (scp != NULL) { + if (scp->seqdev != NULL) + destroy_dev(scp->seqdev); + if (scp->musicdev != NULL) + destroy_dev(scp->musicdev); + /* + * TODO: Destroy mutex and cv + */ + if (scp->midis != NULL) + free(scp->midis, M_TEMP); + if (scp->midi_flags != NULL) + free(scp->midi_flags, M_TEMP); + if (scp->out_q.b) + free(scp->out_q.b, M_TEMP); + if (scp->in_q.b) + free(scp->in_q.b, M_TEMP); + free(scp, M_DEVBUF); + } +ok: + return ret; +} + +static int +seq_delunit(int unit) +{ + struct seq_softc *scp = seqs[unit]; + int i; + + //SEQ_DEBUG(4, printf("seq_delunit: %d\n", unit)); + SEQ_DEBUG(1, printf("seq_delunit: 1 \n")); + mtx_lock(&scp->seq_lock); + + scp->playing = 0; + scp->done = 1; + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->reset_cv); + SEQ_DEBUG(1, printf("seq_delunit: 2 \n")); + cv_wait(&scp->th_cv, &scp->seq_lock); + SEQ_DEBUG(1, printf("seq_delunit: 3.0 \n")); + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(1, printf("seq_delunit: 3.1 \n")); + + cv_destroy(&scp->state_cv); + SEQ_DEBUG(1, printf("seq_delunit: 4 \n")); + cv_destroy(&scp->empty_cv); + SEQ_DEBUG(1, printf("seq_delunit: 5 \n")); + cv_destroy(&scp->reset_cv); + SEQ_DEBUG(1, printf("seq_delunit: 6 \n")); + cv_destroy(&scp->out_cv); + SEQ_DEBUG(1, printf("seq_delunit: 7 \n")); + cv_destroy(&scp->in_cv); + SEQ_DEBUG(1, printf("seq_delunit: 8 \n")); + cv_destroy(&scp->th_cv); + + SEQ_DEBUG(1, printf("seq_delunit: 10 \n")); + if (scp->seqdev) + destroy_dev(scp->seqdev); + SEQ_DEBUG(1, printf("seq_delunit: 11 \n")); + if (scp->musicdev) + destroy_dev(scp->musicdev); + SEQ_DEBUG(1, printf("seq_delunit: 12 \n")); + scp->seqdev = scp->musicdev = NULL; + if (scp->midis != NULL) + free(scp->midis, M_TEMP); + SEQ_DEBUG(1, printf("seq_delunit: 13 \n")); + if (scp->midi_flags != NULL) + free(scp->midi_flags, M_TEMP); + SEQ_DEBUG(1, printf("seq_delunit: 14 \n")); + free(scp->out_q.b, M_TEMP); + SEQ_DEBUG(1, printf("seq_delunit: 15 \n")); + free(scp->in_q.b, M_TEMP); + + SEQ_DEBUG(1, printf("seq_delunit: 16 \n")); + + mtx_destroy(&scp->seq_lock); + SEQ_DEBUG(1, printf("seq_delunit: 17 \n")); + free(scp, M_DEVBUF); + + mtx_lock(&seqinfo_mtx); + for (i = unit; i < (nseq - 1); i++) + seqs[i] = seqs[i + 1]; + nseq--; + mtx_unlock(&seqinfo_mtx); + + return 0; +} + +int +seq_modevent(module_t mod, int type, void *data) +{ + int retval, r; + + retval = 0; + + switch (type) { + case MOD_LOAD: + mtx_init(&seqinfo_mtx, "seqmod", NULL, 0); + retval = seq_addunit(); + break; + + case MOD_UNLOAD: + while (nseq) { + r = seq_delunit(nseq - 1); + if (r) { + retval = r; + break; + } + } + if (nseq == 0) { + retval = 0; + mtx_destroy(&seqinfo_mtx); + } + break; + + default: + break; + } + + return retval; +} + +static int +seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md) +{ + + if (unit > scp->midi_number || unit < 0) + return EINVAL; + + *md = scp->midis[unit]; + + return 0; +} + +int +seq_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct seq_softc *scp = i_dev->si_drv1; + int i; + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(3, printf("seq_open: scp %p unit %d, flags 0x%x.\n", + scp, scp->unit, flags)); + + /* + * Mark this device busy. + */ + + mtx_lock(&scp->seq_lock); + if (scp->busy) { + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(2, printf("seq_open: unit %d is busy.\n", scp->unit)); + return EBUSY; + } + scp->fflags = flags; + /* + if ((scp->fflags & O_NONBLOCK) != 0) + scp->flags |= SEQ_F_NBIO; + */ + scp->music = MIDIDEV(i_dev) == SND_DEV_MUSIC; + + /* + * Enumerate the available midi devices + */ + scp->midi_number = 0; + scp->maxunits = midimapper_open(scp->mapper, &scp->mapper_cookie); + + if (scp->maxunits == 0) + SEQ_DEBUG(2, printf("seq_open: no midi devices\n")); + + for (i = 0; i < scp->maxunits; i++) { + scp->midis[scp->midi_number] = + midimapper_fetch_synth(scp->mapper, scp->mapper_cookie, i); + if (scp->midis[scp->midi_number]) { + if (SYNTH_OPEN(scp->midis[scp->midi_number], scp, + scp->fflags) != 0) + scp->midis[scp->midi_number] = NULL; + else { + scp->midi_flags[scp->midi_number] = + SYNTH_QUERY(scp->midis[scp->midi_number]); + scp->midi_number++; + } + } + } + + timer_setvals(scp, 60, 100); + + timer_start(scp); + timer_stop(scp); + /* + * actually, if we're in rdonly mode, we should start the timer + */ + /* + * TODO: Handle recording now + */ + + scp->out_water = MIDIQ_SIZE(scp->out_q) / 2; + + scp->busy = 1; + mtx_unlock(&scp->seq_lock); + + SEQ_DEBUG(2, printf("seq_open: opened, mode %s.\n", + scp->music ? "music" : "sequencer")); + SEQ_DEBUG(2, + printf("Sequencer %d %p opened maxunits %d midi_number %d:\n", + scp->unit, scp, scp->maxunits, scp->midi_number)); + for (i = 0; i < scp->midi_number; i++) + SEQ_DEBUG(3, printf(" midi %d %p\n", i, scp->midis[i])); + + return 0; +} + +/* + * seq_close + */ +int +seq_close(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + int i; + struct seq_softc *scp = i_dev->si_drv1; + int ret; + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(2, printf("seq_close: unit %d.\n", scp->unit)); + + mtx_lock(&scp->seq_lock); + + ret = ENXIO; + if (scp->busy == 0) + goto err; + + seq_reset(scp); + seq_sync(scp); + + for (i = 0; i < scp->midi_number; i++) + if (scp->midis[i]) + SYNTH_CLOSE(scp->midis[i]); + + midimapper_close(scp->mapper, scp->mapper_cookie); + + timer_stop(scp); + + scp->busy = 0; + ret = 0; + +err: + SEQ_DEBUG(3, printf("seq_close: closed ret = %d.\n", ret)); + mtx_unlock(&scp->seq_lock); + return ret; +} + +int +seq_read(struct cdev *i_dev, struct uio *uio, int ioflag) +{ + int retval, used; + struct seq_softc *scp = i_dev->si_drv1; + +#define SEQ_RSIZE 32 + u_char buf[SEQ_RSIZE]; + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(7, printf("seq_read: unit %d, resid %d.\n", + scp->unit, uio->uio_resid)); + + mtx_lock(&scp->seq_lock); + if ((scp->fflags & FREAD) == 0) { + SEQ_DEBUG(2, printf("seq_read: unit %d is not for reading.\n", + scp->unit)); + retval = EIO; + goto err1; + } + /* + * Begin recording. + */ + /* + * if ((scp->flags & SEQ_F_READING) == 0) + */ + /* + * TODO, start recording if not alread + */ + + /* + * I think the semantics are to return as soon + * as possible. + * Second thought, it doens't seem like midimoutain + * expects that at all. + * TODO: Look up in some sort of spec + */ + + while (uio->uio_resid > 0) { + while (MIDIQ_EMPTY(scp->in_q)) { + retval = EWOULDBLOCK; + /* + * I wish I knew which one to care about + */ + + if (scp->fflags & O_NONBLOCK) + goto err1; + if (ioflag & O_NONBLOCK) + goto err1; + + retval = cv_wait_sig(&scp->in_cv, &scp->seq_lock); + if (retval == EINTR) + goto err1; + } + + used = MIN(MIDIQ_LEN(scp->in_q), uio->uio_resid); + used = MIN(used, SEQ_RSIZE); + + SEQ_DEBUG(8, printf("midiread: uiomove cc=%d\n", used)); + MIDIQ_DEQ(scp->in_q, buf, used); + retval = uiomove(buf, used, uio); + if (retval) + goto err1; + } + + retval = 0; +err1: + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(6, printf("seq_read: ret %d, resid %d.\n", + retval, uio->uio_resid)); + + return retval; +} + +int +seq_write(struct cdev *i_dev, struct uio *uio, int ioflag) +{ + u_char event[EV_SZ], newevent[EV_SZ], ev_code; + struct seq_softc *scp = i_dev->si_drv1; + int retval; + int used; + + SEQ_DEBUG(7, printf("seq_write: unit %d, resid %d.\n", + scp->unit, uio->uio_resid)); + + if (scp == NULL) + return ENXIO; + + mtx_lock(&scp->seq_lock); + + if ((scp->fflags & FWRITE) == 0) { + SEQ_DEBUG(2, printf("seq_write: unit %d is not for writing.\n", + scp->unit)); + retval = EIO; + goto err0; + } + while (uio->uio_resid > 0) { + while (MIDIQ_AVAIL(scp->out_q) == 0) { + retval = EWOULDBLOCK; + if (scp->fflags & O_NONBLOCK) + goto err0; + if (ioflag & O_NONBLOCK) + goto err0; + SEQ_DEBUG(8, printf("seq_write cvwait\n")); + + scp->playing = 1; + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->state_cv); + + retval = cv_wait_sig(&scp->out_cv, &scp->seq_lock); + /* + * We slept, maybe things have changed since last + * dying check + */ + if (retval == EINTR) + goto err0; +#if 0 + /* + * Useless test + */ + if (scp != i_dev->si_drv1) + retval = ENXIO; +#endif + } + + used = MIN(uio->uio_resid, 4); + + SEQ_DEBUG(8, printf("seqout: resid %d len %jd avail %jd\n", + uio->uio_resid, (intmax_t)MIDIQ_LEN(scp->out_q), + (intmax_t)MIDIQ_AVAIL(scp->out_q))); + + if (used != 4) { + retval = ENXIO; + goto err0; + } + retval = uiomove(event, used, uio); + if (retval) + goto err0; + + ev_code = event[0]; + SEQ_DEBUG(8, printf("seq_write: unit %d, event %s.\n", + scp->unit, midi_cmdname(ev_code, cmdtab_seqevent))); + + /* Have a look at the event code. */ + if (ev_code == SEQ_FULLSIZE) { + + /* + * TODO: restore code for SEQ_FULLSIZE + */ +#if 0 + /* + * A long event, these are the patches/samples for a + * synthesizer. + */ + midiunit = *(u_short *)&event[2]; + mtx_lock(&sd->seq_lock); + ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); + mtx_unlock(&sd->seq_lock); + if (ret != 0) + return (ret); + + SEQ_DEBUG(printf("seq_write: loading a patch to the unit %d.\n", midiunit)); + + ret = md->synth.loadpatch(md, *(short *)&event[0], buf, + p + 4, count, 0); + return (ret); +#else + /* + * For now, just flush the darn buffer + */ + SEQ_DEBUG(2, + printf("seq_write: SEQ_FULLSIZE flusing buffer.\n")); + while (uio->uio_resid > 0) { + retval = uiomove(event, EV_SZ, uio); + if (retval) + goto err0; + + } + retval = 0; + goto err0; +#endif + } + retval = EINVAL; + if (ev_code >= 128) { + + /* + * Some sort of an extended event. The size is eight + * bytes. scoop extra info. + */ + if (scp->music && ev_code == SEQ_EXTENDED) { + SEQ_DEBUG(2, printf("seq_write: invalid level two event %x.\n", ev_code)); + goto err0; + } + if (uiomove((caddr_t)&event[4], 4, uio)) { + SEQ_DEBUG(2, + printf("seq_write: user memory mangled?\n")); + goto err0; + } + } else { + /* + * Size four event. + */ + if (scp->music) { + SEQ_DEBUG(2, printf("seq_write: four byte event in music mode.\n")); + goto err0; + } + } + if (ev_code == SEQ_MIDIPUTC) { + /* + * TODO: event[2] is unit number to receive char. + * Range check it. + */ + } + if (scp->music) { +#ifdef not_ever_ever + if (event[0] == EV_TIMING && + (event[1] == TMR_START || event[1] == TMR_STOP)) { + /* + * For now, try to make midimoutain work by + * forcing these events to be processed + * immediatly. + */ + seq_processevent(scp, event); + } else + MIDIQ_ENQ(scp->out_q, event, EV_SZ); +#else + MIDIQ_ENQ(scp->out_q, event, EV_SZ); +#endif + } else { + if (seq_convertold(event, newevent) > 0) + MIDIQ_ENQ(scp->out_q, newevent, EV_SZ); +#if 0 + else + goto err0; +#endif + } + + } + + scp->playing = 1; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + + retval = 0; + +err0: + SEQ_DEBUG(6, + printf("seq_write done: leftover buffer length %d retval %d\n", + uio->uio_resid, retval)); + mtx_unlock(&scp->seq_lock); + return retval; +} + +int +seq_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + int midiunit, ret, tmp; + struct seq_softc *scp = i_dev->si_drv1; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + u_char event[EV_SZ]; + u_char newevent[EV_SZ]; + + kobj_t md; + + /* + * struct snd_size *sndsize; + */ + + if (scp == NULL) + return ENXIO; + + SEQ_DEBUG(6, printf("seq_ioctl: unit %d, cmd %s.\n", + scp->unit, midi_cmdname(cmd, cmdtab_seqioctl))); + + ret = 0; + + switch (cmd) { + case SNDCTL_SEQ_GETTIME: + /* + * ioctl needed by libtse + */ + mtx_lock(&scp->seq_lock); + *(int *)arg = timer_now(scp); + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(6, printf("seq_ioctl: gettime %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_TMR_METRONOME: + /* fallthrough */ + case SNDCTL_TMR_SOURCE: + /* + * Not implemented + */ + ret = 0; + break; + case SNDCTL_TMR_TEMPO: + event[1] = TMR_TEMPO; + event[4] = *(int *)arg & 0xFF; + event[5] = (*(int *)arg >> 8) & 0xFF; + event[6] = (*(int *)arg >> 16) & 0xFF; + event[7] = (*(int *)arg >> 24) & 0xFF; + goto timerevent; + case SNDCTL_TMR_TIMEBASE: + event[1] = TMR_TIMERBASE; + event[4] = *(int *)arg & 0xFF; + event[5] = (*(int *)arg >> 8) & 0xFF; + event[6] = (*(int *)arg >> 16) & 0xFF; + event[7] = (*(int *)arg >> 24) & 0xFF; + goto timerevent; + case SNDCTL_TMR_START: + event[1] = TMR_START; + goto timerevent; + case SNDCTL_TMR_STOP: + event[1] = TMR_STOP; + goto timerevent; + case SNDCTL_TMR_CONTINUE: + event[1] = TMR_CONTINUE; +timerevent: + event[0] = EV_TIMING; + mtx_lock(&scp->seq_lock); + if (!scp->music) { + ret = EINVAL; + mtx_unlock(&scp->seq_lock); + break; + } + seq_processevent(scp, event); + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_TMR_SELECT: + SEQ_DEBUG(2, + printf("seq_ioctl: SNDCTL_TMR_SELECT not supported\n")); + ret = EINVAL; + break; + case SNDCTL_SEQ_SYNC: + if (mode == O_RDONLY) { + ret = 0; + break; + } + mtx_lock(&scp->seq_lock); + ret = seq_sync(scp); + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_SEQ_PANIC: + /* fallthrough */ + case SNDCTL_SEQ_RESET: + /* + * SNDCTL_SEQ_PANIC == SNDCTL_SEQ_RESET + */ + mtx_lock(&scp->seq_lock); + seq_reset(scp); + mtx_unlock(&scp->seq_lock); + ret = 0; + break; + case SNDCTL_SEQ_TESTMIDI: + mtx_lock(&scp->seq_lock); + /* + * TODO: SNDCTL_SEQ_TESTMIDI now means "can I write to the + * device?". + */ + mtx_unlock(&scp->seq_lock); + break; +#if 0 + case SNDCTL_SEQ_GETINCOUNT: + if (mode == O_WRONLY) + *(int *)arg = 0; + else { + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->in_q.rl; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(printf("seq_ioctl: incount %d.\n", + *(int *)arg)); + } + ret = 0; + break; + case SNDCTL_SEQ_GETOUTCOUNT: + if (mode == O_RDONLY) + *(int *)arg = 0; + else { + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->out_q.fl; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(printf("seq_ioctl: outcount %d.\n", + *(int *)arg)); + } + ret = 0; + break; +#endif + case SNDCTL_SEQ_CTRLRATE: + if (*(int *)arg != 0) { + ret = EINVAL; + break; + } + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->timerbase; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: ctrlrate %d.\n", *(int *)arg)); + ret = 0; + break; + /* + * TODO: ioctl SNDCTL_SEQ_RESETSAMPLES + */ +#if 0 + case SNDCTL_SEQ_RESETSAMPLES: + mtx_lock(&scp->seq_lock); + ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&scp->seq_lock); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, + SND_DEV_MIDIN), cmd, arg, mode, td); + break; +#endif + case SNDCTL_SEQ_NRSYNTHS: + mtx_lock(&scp->seq_lock); + *(int *)arg = scp->midi_number; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: synths %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_SEQ_NRMIDIS: + mtx_lock(&scp->seq_lock); + if (scp->music) + *(int *)arg = 0; + else { + /* + * TODO: count the numbder of devices that can WRITERAW + */ + *(int *)arg = scp->midi_number; + } + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: midis %d.\n", *(int *)arg)); + ret = 0; + break; + /* + * TODO: ioctl SNDCTL_SYNTH_MEMAVL + */ +#if 0 + case SNDCTL_SYNTH_MEMAVL: + mtx_lock(&scp->seq_lock); + ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); + mtx_unlock(&scp->seq_lock); + if (ret != 0) + break; + ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, + SND_DEV_MIDIN), cmd, arg, mode, td); + break; +#endif + case SNDCTL_SEQ_OUTOFBAND: + for (ret = 0; ret < EV_SZ; ret++) + event[ret] = (u_char)arg[0]; + + mtx_lock(&scp->seq_lock); + if (scp->music) + ret = seq_processevent(scp, event); + else { + if (seq_convertold(event, newevent) > 0) + ret = seq_processevent(scp, newevent); + else + ret = EINVAL; + } + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + midiunit = synthinfo->device; + mtx_lock(&scp->seq_lock); + if (seq_fetch_mid(scp, midiunit, &md) == 0) { + bzero(synthinfo, sizeof(*synthinfo)); + synthinfo->name[0] = 'f'; + synthinfo->name[1] = 'a'; + synthinfo->name[2] = 'k'; + synthinfo->name[3] = 'e'; + synthinfo->name[4] = 's'; + synthinfo->name[5] = 'y'; + synthinfo->name[6] = 'n'; + synthinfo->name[7] = 't'; + synthinfo->name[8] = 'h'; + synthinfo->device = midiunit; + synthinfo->synth_type = SYNTH_TYPE_MIDI; + synthinfo->capabilities = scp->midi_flags[midiunit]; + ret = 0; + } else + ret = EINVAL; + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + midiunit = midiinfo->device; + mtx_lock(&scp->seq_lock); + if (seq_fetch_mid(scp, midiunit, &md) == 0) { + bzero(midiinfo, sizeof(*midiinfo)); + midiinfo->name[0] = 'f'; + midiinfo->name[1] = 'a'; + midiinfo->name[2] = 'k'; + midiinfo->name[3] = 'e'; + midiinfo->name[4] = 'm'; + midiinfo->name[5] = 'i'; + midiinfo->name[6] = 'd'; + midiinfo->name[7] = 'i'; + midiinfo->device = midiunit; + midiinfo->capabilities = scp->midi_flags[midiunit]; + /* + * TODO: What devtype? + */ + midiinfo->dev_type = 0x01; + ret = 0; + } else + ret = EINVAL; + mtx_unlock(&scp->seq_lock); + break; + case SNDCTL_SEQ_THRESHOLD: + mtx_lock(&scp->seq_lock); + RANGE(*(int *)arg, 1, MIDIQ_SIZE(scp->out_q) - 1); + scp->out_water = *(int *)arg; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: water %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_MIDI_PRETIME: + tmp = *(int *)arg; + if (tmp < 0) + tmp = 0; + mtx_lock(&scp->seq_lock); + scp->pre_event_timeout = (hz * tmp) / 10; + *(int *)arg = scp->pre_event_timeout; + mtx_unlock(&scp->seq_lock); + SEQ_DEBUG(3, printf("seq_ioctl: pretime %d.\n", *(int *)arg)); + ret = 0; + break; + case SNDCTL_FM_4OP_ENABLE: + case SNDCTL_PMGR_IFACE: + case SNDCTL_PMGR_ACCESS: + /* + * Patch manager and fm are ded, ded, ded. + */ + /* fallthrough */ + default: + /* + * TODO: Consider ioctl default case. + * Old code used to + * if ((scp->fflags & O_ACCMODE) == FREAD) { + * ret = EIO; + * break; + * } + * Then pass on the ioctl to device 0 + */ + SEQ_DEBUG(2, + printf("seq_ioctl: unsupported IOCTL %ld.\n", cmd)); + ret = EINVAL; + break; + } + + return ret; +} + +int +seq_poll(struct cdev *i_dev, int events, struct thread *td) +{ + int ret, lim; + struct seq_softc *scp = i_dev->si_drv1; + + SEQ_DEBUG(3, printf("seq_poll: unit %d.\n", scp->unit)); + SEQ_DEBUG(1, printf("seq_poll: unit %d.\n", scp->unit)); + + mtx_lock(&scp->seq_lock); + + ret = 0; + + /* Look up the apropriate queue and select it. */ + if ((events & (POLLOUT | POLLWRNORM)) != 0) { + /* Start playing. */ + scp->playing = 1; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + + lim = scp->out_water; + + if (MIDIQ_AVAIL(scp->out_q) < lim) + /* No enough space, record select. */ + selrecord(td, &scp->out_sel); + else + /* We can write now. */ + ret |= events & (POLLOUT | POLLWRNORM); + } + if ((events & (POLLIN | POLLRDNORM)) != 0) { + /* TODO: Start recording. */ + + /* Find out the boundary. */ + lim = 1; + if (MIDIQ_LEN(scp->in_q) < lim) + /* No data ready, record select. */ + selrecord(td, &scp->in_sel); + else + /* We can read now. */ + ret |= events & (POLLIN | POLLRDNORM); + } + mtx_unlock(&scp->seq_lock); + + return (ret); +} + +#if 0 +static void +sein_qtr(void *p, void /* mididev_info */ *md) +{ + struct seq_softc *scp; + + scp = (struct seq_softc *)p; + + mtx_lock(&scp->seq_lock); + + /* Restart playing if we have the data to output. */ + if (scp->queueout_pending) + seq_callback(scp, SEQ_CB_START | SEQ_CB_WR); + /* Check the midi device if we are reading. */ + if ((scp->flags & SEQ_F_READING) != 0) + seq_midiinput(scp, md); + + mtx_unlock(&scp->seq_lock); +} + +#endif +/* + * seq_convertold + * Was the old playevent. Use this to convert and old + * style /dev/sequencer event to a /dev/music event + */ +static int +seq_convertold(u_char *event, u_char *out) +{ + int used; + u_char dev, chn, note, vel; + + out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = + out[7] = 0; + + dev = 0; + chn = event[1]; + note = event[2]; + vel = event[3]; + + used = 0; + +restart: + /* + * TODO: Debug statement + */ + switch (event[0]) { + case EV_TIMING: + case EV_CHN_VOICE: + case EV_CHN_COMMON: + case EV_SYSEX: + case EV_SEQ_LOCAL: + out[0] = event[0]; + out[1] = event[1]; + out[2] = event[2]; + out[3] = event[3]; + out[4] = event[4]; + out[5] = event[5]; + out[6] = event[6]; + out[7] = event[7]; + used += 8; + break; + case SEQ_NOTEOFF: + out[0] = EV_CHN_VOICE; + out[1] = dev; + out[2] = MIDI_NOTEOFF; + out[3] = chn; + out[4] = note; + out[5] = 255; + used += 4; + break; + + case SEQ_NOTEON: + out[0] = EV_CHN_VOICE; + out[1] = dev; + out[2] = MIDI_NOTEON; + out[3] = chn; + out[4] = note; + out[5] = vel; + used += 4; + break; + + /* + * wait delay = (event[2] << 16) + (event[3] << 8) + event[4] + */ + + case SEQ_PGMCHANGE: + out[0] = EV_CHN_COMMON; + out[1] = dev; + out[2] = MIDI_PGM_CHANGE; + out[3] = chn; + out[4] = note; + out[5] = vel; + used += 4; + break; +/* + out[0] = EV_TIMING; + out[1] = dev; + out[2] = MIDI_PGM_CHANGE; + out[3] = chn; + out[4] = note; + out[5] = vel; + SEQ_DEBUG(4,printf("seq_playevent: synctimer\n")); + break; +*/ + + case SEQ_MIDIPUTC: + SEQ_DEBUG(4, + printf("seq_playevent: put data 0x%02x, unit %d.\n", + event[1], event[2])); + /* + * Pass through to the midi device. + * device = event[2] + * data = event[1] + */ + out[0] = SEQ_MIDIPUTC; + out[1] = dev; + out[2] = chn; + used += 4; + break; +#ifdef notyet + case SEQ_ECHO: + /* + * This isn't handled here yet because I don't know if I can + * just use four bytes events. There might be consequences + * in the _read routing + */ + if (seq_copytoinput(scp, event, 4) == EAGAIN) { + ret = QUEUEFULL; + break; + } + ret = MORE; + break; +#endif + case SEQ_EXTENDED: + switch (event[1]) { + case SEQ_NOTEOFF: + case SEQ_NOTEON: + case SEQ_PGMCHANGE: + event++; + used = 4; + goto restart; + break; + case SEQ_AFTERTOUCH: + /* + * SYNTH_AFTERTOUCH(md, event[3], event[4]) + */ + case SEQ_BALANCE: + /* + * SYNTH_PANNING(md, event[3], (char)event[4]) + */ + case SEQ_CONTROLLER: + /* + * SYNTH_CONTROLLER(md, event[3], event[4], *(short *)&event[5]) + */ + case SEQ_VOLMODE: + /* + * SYNTH_VOLUMEMETHOD(md, event[3]) + */ + default: + SEQ_DEBUG(2, + printf("seq_convertold: SEQ_EXTENDED type %d" + "not handled\n", event[1])); + break; + } + break; + case SEQ_WAIT: + out[0] = EV_TIMING; + out[1] = TMR_WAIT_REL; + out[4] = event[2]; + out[5] = event[3]; + out[6] = event[4]; + + SEQ_DEBUG(5, printf("SEQ_WAIT %d", + event[2] + (event[3] << 8) + (event[4] << 24))); + + used += 4; + break; + + case SEQ_ECHO: + case SEQ_SYNCTIMER: + case SEQ_PRIVATE: + default: + SEQ_DEBUG(2, + printf("seq_convertold: event type %d not handled %d %d %d\n", + event[0], event[1], event[2], event[3])); + break; + } + return used; +} + +/* + * Writting to the sequencer buffer never blocks and drops + * input which cannot be queued + */ +void +seq_copytoinput(struct seq_softc *scp, u_char *event, int len) +{ + + mtx_assert(&scp->seq_lock, MA_OWNED); + + if (MIDIQ_AVAIL(scp->in_q) < len) { + /* + * ENOROOM? EINPUTDROPPED? ETOUGHLUCK? + */ + SEQ_DEBUG(2, printf("seq_copytoinput: queue full\n")); + } else { + MIDIQ_ENQ(scp->in_q, event, len); + selwakeup(&scp->in_sel); + cv_broadcast(&scp->in_cv); + } + +} + +static int +seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event) +{ + int ret, voice; + u_char cmd, chn, note, parm; + + ret = 0; + cmd = event[2]; + chn = event[3]; + note = event[4]; + parm = event[5]; + + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(5, printf("seq_chnvoice: unit %d, dev %d, cmd %s," + " chn %d, note %d, parm %d.\n", scp->unit, event[1], + midi_cmdname(cmd, cmdtab_seqcv), chn, note, parm)); + + voice = SYNTH_ALLOC(md, chn, note); + + mtx_unlock(&scp->seq_lock); + + switch (cmd) { + case MIDI_NOTEON: + if (note < 128 || note == 255) { +#if 0 + if (scp->music && chn == 9) { + /* + * This channel is a percussion. The note + * number is the patch number. + */ + /* + mtx_unlock(&scp->seq_lock); + if (SYNTH_SETINSTR(md, voice, 128 + note) + == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + */ + note = 60; /* Middle C. */ + } +#endif + if (scp->music) { + /* + mtx_unlock(&scp->seq_lock); + if (SYNTH_SETUPVOICE(md, voice, chn) + == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + */ + } + SYNTH_STARTNOTE(md, voice, note, parm); + } + break; + case MIDI_NOTEOFF: + SYNTH_KILLNOTE(md, voice, note, parm); + break; + case MIDI_KEY_PRESSURE: + SYNTH_AFTERTOUCH(md, voice, parm); + break; + default: + ret = 1; + SEQ_DEBUG(2, printf("seq_chnvoice event type %d not handled\n", + event[1])); + break; + } + + mtx_lock(&scp->seq_lock); + return ret; +} + +static int +seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event) +{ + int ret; + u_short w14; + u_char cmd, chn, p1; + + ret = 0; + cmd = event[2]; + chn = event[3]; + p1 = event[4]; + w14 = *(u_short *)&event[6]; + + SEQ_DEBUG(5, printf("seq_chncommon: unit %d, dev %d, cmd %s, chn %d," + " p1 %d, w14 %d.\n", scp->unit, event[1], + midi_cmdname(cmd, cmdtab_seqccmn), chn, p1, w14)); + mtx_unlock(&scp->seq_lock); + switch (cmd) { + case MIDI_PGM_CHANGE: + SEQ_DEBUG(4, printf("seq_chncommon pgmchn chn %d pg %d\n", + chn, p1)); + SYNTH_SETINSTR(md, chn, p1); + break; + case MIDI_CTL_CHANGE: + SEQ_DEBUG(4, printf("seq_chncommon ctlch chn %d pg %d %d\n", + chn, p1, w14)); + SYNTH_CONTROLLER(md, chn, p1, w14); + break; + case MIDI_PITCH_BEND: + if (scp->music) { + /* + * TODO: MIDI_PITCH_BEND + */ +#if 0 + mtx_lock(&md->synth.vc_mtx); + md->synth.chn_info[chn].bender_value = w14; + if (md->midiunit >= 0) { + /* + * Handle all of the notes playing on this + * channel. + */ + key = ((int)chn << 8); + for (i = 0; i < md->synth.alloc.max_voice; i++) + if ((md->synth.alloc.map[i] & 0xff00) == key) { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&scp->seq_lock); + if (md->synth.bender(md, i, w14) == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + } + } else { + mtx_unlock(&md->synth.vc_mtx); + mtx_unlock(&scp->seq_lock); + if (md->synth.bender(md, chn, w14) == EAGAIN) { + mtx_lock(&scp->seq_lock); + return (QUEUEFULL); + } + mtx_lock(&scp->seq_lock); + } +#endif + } else + SYNTH_BENDER(md, chn, w14); + break; + default: + ret = 1; + SEQ_DEBUG(2, + printf("seq_chncommon event type %d not handled.\n", + event[1])); + break; + + } + mtx_lock(&scp->seq_lock); + return ret; +} + +static int +seq_timing(struct seq_softc *scp, u_char *event) +{ + int param; + int ret; + + ret = 0; + param = event[4] + (event[5] << 8) + + (event[6] << 16) + (event[7] << 24); + + SEQ_DEBUG(5, printf("seq_timing: unit %d, cmd %d, param %d.\n", + scp->unit, event[1], param)); + switch (event[1]) { + case TMR_WAIT_REL: + timer_wait(scp, param, 0); + break; + case TMR_WAIT_ABS: + timer_wait(scp, param, 1); + break; + case TMR_START: + timer_start(scp); + cv_broadcast(&scp->reset_cv); + break; + case TMR_STOP: + timer_stop(scp); + /* + * The following cv_broadcast isn't needed since we only + * wait for 0->1 transitions. It probably won't hurt + */ + cv_broadcast(&scp->reset_cv); + break; + case TMR_CONTINUE: + timer_continue(scp); + cv_broadcast(&scp->reset_cv); + break; + case TMR_TEMPO: + if (param < 8) + param = 8; + if (param > 360) + param = 360; + SEQ_DEBUG(4, printf("Timer set tempo %d\n", param)); + timer_setvals(scp, param, scp->timerbase); + break; + case TMR_TIMERBASE: + if (param < 1) + param = 1; + if (param > 1000) + param = 1000; + SEQ_DEBUG(4, printf("Timer set timerbase %d\n", param)); + timer_setvals(scp, scp->tempo, param); + break; + case TMR_ECHO: + /* + * TODO: Consider making 4-byte events for /dev/sequencer + * PRO: Maybe needed by legacy apps + * CON: soundcard.h has been warning for a while many years + * to expect 8 byte events. + */ +#if 0 + if (scp->music) + seq_copytoinput(scp, event, 8); + else { + param = (param << 8 | SEQ_ECHO); + seq_copytoinput(scp, (u_char *)¶m, 4); + } +#else + seq_copytoinput(scp, event, 8); +#endif + break; + default: + SEQ_DEBUG(2, printf("seq_timing event type %d not handled.\n", + event[1])); + ret = 1; + break; + } + return ret; +} + +static int +seq_local(struct seq_softc *scp, u_char *event) +{ + int ret; + + ret = 0; + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(5, printf("seq_local: unit %d, cmd %d\n", scp->unit, + event[1])); + switch (event[1]) { + default: + SEQ_DEBUG(1, printf("seq_local event type %d not handled\n", + event[1])); + ret = 1; + break; + } + return ret; +} + +static int +seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event) +{ + int i, l; + + mtx_assert(&scp->seq_lock, MA_OWNED); + SEQ_DEBUG(5, printf("seq_sysex: unit %d device %d\n", scp->unit, + event[1])); + l = 0; + for (i = 0; i < 6 && event[i + 2] != 0xff; i++) + l = i + 1; + if (l > 0) { + mtx_unlock(&scp->seq_lock); + if (SYNTH_SENDSYSEX(md, &event[2], l) == EAGAIN) { + mtx_lock(&scp->seq_lock); + return 1; + } + mtx_lock(&scp->seq_lock); + } + return 0; +} + +/* + * Reset no longer closes the raw devices nor seq_sync's + * Callers are IOCTL and seq_close + */ +static void +seq_reset(struct seq_softc *scp) +{ + int chn, i; + kobj_t m; + + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(5, printf("seq_reset: unit %d.\n", scp->unit)); + + /* + * Stop reading and writing. + */ + + /* scp->recording = 0; */ + scp->playing = 0; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->reset_cv); + + /* + * For now, don't reset the timers. + */ + MIDIQ_CLEAR(scp->in_q); + MIDIQ_CLEAR(scp->out_q); + + for (i = 0; i < scp->midi_number; i++) { + m = scp->midis[i]; + mtx_unlock(&scp->seq_lock); + SYNTH_RESET(m); + for (chn = 0; chn < 16; chn++) { + SYNTH_CONTROLLER(m, chn, 123, 0); + SYNTH_CONTROLLER(m, chn, 121, 0); + SYNTH_BENDER(m, chn, 1 << 13); + } + mtx_lock(&scp->seq_lock); + } +} + +/* + * seq_sync + * *really* flush the output queue + * flush the event queue, then flush the synthsisers. + * Callers are IOCTL and close + */ + +#define SEQ_SYNC_TIMEOUT 8 +static int +seq_sync(struct seq_softc *scp) +{ + int i, rl, sync[16], done; + + mtx_assert(&scp->seq_lock, MA_OWNED); + + SEQ_DEBUG(4, printf("seq_sync: unit %d.\n", scp->unit)); + + /* + * Wait until output queue is empty. Check every so often to see if + * the queue is moving along. If it isn't just abort. + */ + while (!MIDIQ_EMPTY(scp->out_q)) { + + if (!scp->playing) { + scp->playing = 1; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + } + rl = MIDIQ_LEN(scp->out_q); + + i = cv_timedwait_sig(&scp->out_cv, + &scp->seq_lock, SEQ_SYNC_TIMEOUT * hz); + + if (i == EINTR || i == ERESTART) { + if (i == EINTR) { + /* + * XXX: I don't know why we stop playing + */ + scp->playing = 0; + cv_broadcast(&scp->out_cv); + } + return i; + } + if (i == EWOULDBLOCK && rl == MIDIQ_LEN(scp->out_q) && + scp->waiting == 0) { + /* + * A queue seems to be stuck up. Give up and clear + * queues. + */ + MIDIQ_CLEAR(scp->out_q); + scp->playing = 0; + cv_broadcast(&scp->state_cv); + cv_broadcast(&scp->out_cv); + cv_broadcast(&scp->reset_cv); + + /* + * TODO: Consider if the raw devices need to be flushed + */ + + SEQ_DEBUG(1, printf("seq_sync queue stuck, aborting\n")); + + return i; + } + } + + scp->playing = 0; + /* + * Since syncing a midi device might block, unlock scp->seq_lock. + */ + + mtx_unlock(&scp->seq_lock); + for (i = 0; i < scp->midi_number; i++) + sync[i] = 1; + + do { + done = 1; + for (i = 0; i < scp->midi_number; i++) + if (sync[i]) { + if (SYNTH_INSYNC(scp->midis[i]) == 0) + sync[i] = 0; + else + done = 0; + } + if (!done) + DELAY(5000); + + } while (!done); + + mtx_lock(&scp->seq_lock); + return 0; +} + +char * +midi_cmdname(int cmd, midi_cmdtab *tab) +{ + while (tab->name != NULL) { + if (cmd == tab->cmd) + return (tab->name); + tab++; + } + + return ("unknown"); +} --- sys/dev/sound/midi/sequencer.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/sequencer.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,90 @@ +/*- + * Copyright (c) 2003 Mathew Kanner + * Copyright (c) 1999 Seigo Tanimura + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/midi/sequencer.h,v 1.9 2007/02/25 13:51:52 netchild Exp $ + */ + +/* + * Include file for the midi sequence driver. + */ + +#ifndef _SEQUENCER_H_ +#define _SEQUENCER_H_ + + +#define NSEQ_MAX 16 + +/* + * many variables should be reduced to a range. Here define a macro + */ + +#define RANGE(var, low, high) (var) = \ +((var)<(low)?(low) : (var)>(high)?(high) : (var)) + +#ifdef _KERNEL + +void seq_timer(void *arg); + +SYSCTL_DECL(_hw_midi_seq); + +extern int seq_debug; + +#define SEQ_DEBUG(y, x) \ + do { \ + if (seq_debug >= y) { \ + (x); \ + } \ + } while(0) + +SYSCTL_DECL(_hw_midi); + +#endif /* _KERNEL */ + +#define SYNTHPROP_MIDI 1 +#define SYNTHPROP_SYNTH 2 +#define SYNTHPROP_RX 4 +#define SYNTHPROP_TX 8 + +struct _midi_cmdtab { + int cmd; + char *name; +}; +typedef struct _midi_cmdtab midi_cmdtab; +extern midi_cmdtab cmdtab_seqevent[]; +extern midi_cmdtab cmdtab_seqioctl[]; +extern midi_cmdtab cmdtab_timer[]; +extern midi_cmdtab cmdtab_seqcv[]; +extern midi_cmdtab cmdtab_seqccmn[]; + +char *midi_cmdname(int cmd, midi_cmdtab * tab); + +enum { + MORE, + TIMERARMED, + QUEUEFULL +}; + +#endif --- sys/dev/sound/midi/synth_if.m.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/midi/synth_if.m Thu Jul 12 12:04:19 2007 @@ -0,0 +1,313 @@ +#- +# Copyright (c) 2003 Mathew Kanner +# 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 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 AUTHOR 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. +# +# $FreeBSD: src/sys/dev/sound/midi/synth_if.m,v 1.3 2007/02/25 13:51:52 netchild Exp $ +# + +INTERFACE synth; + +#include + +CODE { + +synth_killnote_t nokillnote; +synth_startnote_t nostartnote; +synth_setinstr_t nosetinstr; +synth_hwcontrol_t nohwcontrol; +synth_aftertouch_t noaftertouch; +synth_panning_t nopanning; +synth_controller_t nocontroller; +synth_volumemethod_t novolumemethod; +synth_bender_t nobender; +synth_setupvoice_t nosetupvoice; +synth_sendsysex_t nosendsysex; +synth_allocvoice_t noallocvoice; +synth_writeraw_t nowriteraw; +synth_reset_t noreset; +synth_shortname_t noshortname; +synth_open_t noopen; +synth_close_t noclose; +synth_query_t noquery; +synth_insync_t noinsync; +synth_alloc_t noalloc; + + int + nokillnote(void *_kobj, uint8_t _chn, uint8_t _note, uint8_t _vel) + { + printf("nokillnote\n"); + return 0; + } + + int + noopen(void *_kobj, void *_arg, int mode) + { + printf("noopen\n"); + return 0; + } + + int + noquery(void *_kboj) + { + printf("noquery\n"); + return 0; + } + + int + nostartnote(void *_kb, uint8_t _voice, uint8_t _note, uint8_t _parm) + { + printf("nostartnote\n"); + return 0; + } + + int + nosetinstr(void *_kb, uint8_t _chn, uint16_t _patchno) + { + printf("nosetinstr\n"); + return 0; + } + + int + nohwcontrol(void *_kb, uint8_t *_event) + { + printf("nohwcontrol\n"); + return 0; + } + + int + noaftertouch ( void /* X */ * _kobj, uint8_t _x1, uint8_t _x2) + { + printf("noaftertouch\n"); + return 0; + } + + int + nopanning ( void /* X */ * _kobj, uint8_t _x1, uint8_t _x2) + { + printf("nopanning\n"); + return 0; + } + + int + nocontroller ( void /* X */ * _kobj, uint8_t _x1, uint8_t _x2, uint16_t _x3) + { + printf("nocontroller\n"); + return 0; + } + + int + novolumemethod ( + void /* X */ * _kobj, + uint8_t _x1) + { + printf("novolumemethod\n"); + return 0; + } + + int + nobender ( void /* X */ * _kobj, uint8_t _voice, uint16_t _bend) + { + printf("nobender\n"); + return 0; + } + + int + nosetupvoice ( void /* X */ * _kobj, uint8_t _voice, uint8_t _chn) + { + + printf("nosetupvoice\n"); + return 0; + } + + int + nosendsysex ( void /* X */ * _kobj, void * _buf, size_t _len) + { + printf("nosendsysex\n"); + return 0; + } + + int + noallocvoice ( void /* X */ * _kobj, uint8_t _chn, uint8_t _note, void *_x) + { + printf("noallocvoice\n"); + return 0; + } + + int + nowriteraw ( void /* X */ * _kobjt, uint8_t * _buf, size_t _len) + { + printf("nowriteraw\n"); + return 1; + } + + int + noreset ( void /* X */ * _kobjt) + { + + printf("noreset\n"); + return 0; + } + + char * + noshortname (void /* X */ * _kobjt) + { + printf("noshortname\n"); + return "noshortname"; + } + + int + noclose ( void /* X */ * _kobjt) + { + + printf("noclose\n"); + return 0; + } + + int + noinsync (void /* X */ * _kobjt) + { + + printf("noinsync\n"); + return 0; + } + + int + noalloc ( void /* x */ * _kbojt, uint8_t _chn, uint8_t _note) + { + printf("noalloc\n"); + return 0; + } +} + +METHOD int killnote { + void /* X */ *_kobj; + uint8_t _chan; + uint8_t _note; + uint8_t _vel; +} DEFAULT nokillnote; + +METHOD int startnote { + void /* X */ *_kobj; + uint8_t _voice; + uint8_t _note; + uint8_t _parm; +} DEFAULT nostartnote; + +METHOD int setinstr { + void /* X */ *_kobj; + uint8_t _chn; + uint16_t _patchno; +} DEFAULT nosetinstr; + +METHOD int hwcontrol { + void /* X */ *_kobj; + uint8_t *_event; +} DEFAULT nohwcontrol; + +METHOD int aftertouch { + void /* X */ *_kobj; + uint8_t _x1; + uint8_t _x2; +} DEFAULT noaftertouch; + +METHOD int panning { + void /* X */ *_kobj; + uint8_t _x1; + uint8_t _x2; +} DEFAULT nopanning; + +METHOD int controller { + void /* X */ *_kobj; + uint8_t _x1; + uint8_t _x2; + uint16_t _x3; +} DEFAULT nocontroller; + +METHOD int volumemethod { + void /* X */ *_kobj; + uint8_t _x1; +} DEFAULT novolumemethod; + +METHOD int bender { + void /* X */ *_kobj; + uint8_t _voice; + uint16_t _bend; +} DEFAULT nobender; + +METHOD int setupvoice { + void /* X */ *_kobj; + uint8_t _voice; + uint8_t _chn; +} DEFAULT nosetupvoice; + +METHOD int sendsysex { + void /* X */ *_kobj; + void *_buf; + size_t _len; +} DEFAULT nosendsysex; + +METHOD int allocvoice { + void /* X */ *_kobj; + uint8_t _chn; + uint8_t _note; + void *_x; +} DEFAULT noallocvoice; + +METHOD int writeraw { + void /* X */ *_kobjt; + uint8_t *_buf; + size_t _len; +} DEFAULT nowriteraw; + +METHOD int reset { + void /* X */ *_kobjt; +} DEFAULT noreset; + +METHOD char * shortname { + void /* X */ *_kobjt; +} DEFAULT noshortname; + +METHOD int open { + void /* X */ *_kobjt; + void *_sythn; + int _mode; +} DEFAULT noopen; + +METHOD int close { + void /* X */ *_kobjt; +} DEFAULT noclose; + +METHOD int query { + void /* X */ *_kobjt; +} DEFAULT noquery; + +METHOD int insync { + void /* X */ *_kobjt; +} DEFAULT noinsync; + +METHOD int alloc { + void /* x */ *_kbojt; + uint8_t _chn; + uint8_t _note; +} DEFAULT noalloc; --- sys/dev/sound/null.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/null.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,610 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD$"); + +#define SNDNULL_DESC "NULL Audio" + +#define SNDNULL_RATE_MIN 4000 +#define SNDNULL_RATE_MAX 192000 + +#define SNDNULL_RATE_DEFAULT 48000 +#define SNDNULL_FMT_DEFAULT (AFMT_STEREO | AFMT_S16_LE) +#define SNDNULL_FMTSTR_DEFAULT "s16le" + +#define SNDNULL_NPCHAN 1 +#define SNDNULL_NRCHAN 1 +#define SNDNULL_MAXCHAN (SNDNULL_NPCHAN + SNDNULL_NRCHAN) + +#define SNDNULL_BUFSZ_MIN 4096 +#define SNDNULL_BUFSZ_MAX 65536 +#define SNDNULL_BUFSZ_DEFAULT 4096 + +#define SNDNULL_BLKCNT_MIN 2 +#define SNDNULL_BLKCNT_MAX 512 +#define SNDNULL_BLKCNT_DEFAULT SNDNULL_BLKCNT_MIN + +#define SNDNULL_LOCK(sc) snd_mtxlock((sc)->lock) +#define SNDNULL_UNLOCK(sc) snd_mtxunlock((sc)->lock) + +struct sndnull_info; + +struct sndnull_chinfo { + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct pcmchan_caps *caps; + struct sndnull_info *parent; + uint32_t ptr, intrcnt; + int dir, active; +}; + +struct sndnull_info { + device_t dev; + struct sndnull_chinfo ch[SNDNULL_MAXCHAN]; + struct pcmchan_caps caps; + uint32_t bufsz; + uint32_t blkcnt; + uint32_t fmtlist[2]; + struct mtx *lock; + uint8_t *ringbuffer; + int chnum; + + struct callout poll_timer; + int poll_ticks, polling; +}; + +static void * +sndnull_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct sndnull_info *sc = devinfo; + struct sndnull_chinfo *ch; + + SNDNULL_LOCK(sc); + + ch = &sc->ch[sc->chnum++]; + ch->buffer = b; + ch->parent = sc; + ch->channel = c; + ch->dir = dir; + ch->caps = &sc->caps; + + SNDNULL_UNLOCK(sc); + + if (sndbuf_setup(ch->buffer, sc->ringbuffer, sc->bufsz) == -1) + return (NULL); + + return (ch); +} + +static int +sndnull_chan_setformat(kobj_t obj, void *data, uint32_t format) +{ + struct sndnull_chinfo *ch = data; + + if (ch->caps->fmtlist[0] != format) + return (-1); + + return (0); +} + +static int +sndnull_chan_setspeed(kobj_t obj, void *data, uint32_t spd) +{ + struct sndnull_chinfo *ch = data; + + if (spd < ch->caps->minspeed) + spd = ch->caps->minspeed; + if (spd > ch->caps->maxspeed) + spd = ch->caps->maxspeed; + + return (spd); +} + +static int +sndnull_chan_setfragments(kobj_t obj, void *data, + uint32_t blksz, uint32_t blkcnt) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + + blkcnt = sc->blkcnt; + blksz = sndbuf_getmaxsize(ch->buffer) / blkcnt; + blksz -= blksz % sndbuf_getbps(ch->buffer); + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + return (1); +} + +static int +sndnull_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + + sndnull_chan_setfragments(obj, data, blksz, sc->blkcnt); + + return (sndbuf_getblksz(ch->buffer)); +} + +#define SNDNULL_CHAN_ACTIVE(ch) ((ch)->active != 0) + +static __inline int +sndnull_anychan_active(struct sndnull_info *sc) +{ + int i; + + for (i = 0; i < sc->chnum; i++) { + if (SNDNULL_CHAN_ACTIVE(&sc->ch[i])) + return (1); + } + + return (0); +} + +static void +sndnull_poll_callback(void *arg) +{ + struct sndnull_info *sc = arg; + struct sndnull_chinfo *ch; + int i; + + if (sc == NULL) + return; + + SNDNULL_LOCK(sc); + + if (!sndnull_anychan_active(sc)) { + SNDNULL_UNLOCK(sc); + return; + } + + for (i = 0; i < sc->chnum; i++) { + ch = &sc->ch[i]; + if (SNDNULL_CHAN_ACTIVE(ch)) { + ch->ptr += sndbuf_getblksz(ch->buffer); + ch->intrcnt += 1; + SNDNULL_UNLOCK(sc); + chn_intr(ch->channel); + SNDNULL_LOCK(sc); + } + } + + callout_reset(&sc->poll_timer, sc->poll_ticks, + sndnull_poll_callback, sc); + + SNDNULL_UNLOCK(sc); +} + +static int +sndnull_chan_trigger(kobj_t obj, void *data, int go) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + int pollticks; + + if (!PCMTRIG_COMMON(go)) + return (0); + + SNDNULL_LOCK(sc); + + switch (go) { + case PCMTRIG_START: + if (!sndnull_anychan_active(sc)) { + pollticks = ((uint64_t)hz * + sndbuf_getblksz(ch->buffer)) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + if (pollticks < 1) + pollticks = 1; + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, 1, + sndnull_poll_callback, sc); + if (bootverbose) + device_printf(sc->dev, + "PCMTRIG_START: pollticks=%d\n", + pollticks); + } + if (ch->dir == PCMDIR_REC) + memset(sc->ringbuffer, sndbuf_zerodata( + sndbuf_getfmt(ch->buffer)), + sndbuf_getmaxsize(ch->buffer)); + ch->ptr = 0; + ch->intrcnt = 0; + ch->active = 1; + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + ch->active = 0; + if (!sndnull_anychan_active(sc)) + callout_stop(&sc->poll_timer); + if (ch->dir == PCMDIR_PLAY) + memset(sc->ringbuffer, sndbuf_zerodata( + sndbuf_getfmt(ch->buffer)), + sndbuf_getmaxsize(ch->buffer)); + break; + default: + break; + } + + SNDNULL_UNLOCK(sc); + + return (0); +} + +static int +sndnull_chan_getptr(kobj_t obj, void *data) +{ + struct sndnull_chinfo *ch = data; + struct sndnull_info *sc = ch->parent; + uint32_t ptr; + + SNDNULL_LOCK(sc); + ptr = (SNDNULL_CHAN_ACTIVE(ch)) ? ch->ptr : 0; + SNDNULL_UNLOCK(sc); + + return (ptr); +} + +static struct pcmchan_caps * +sndnull_chan_getcaps(kobj_t obj, void *data) +{ + return (((struct sndnull_chinfo *)data)->caps); +} + +static kobj_method_t sndnull_chan_methods[] = { + KOBJMETHOD(channel_init, sndnull_chan_init), + KOBJMETHOD(channel_setformat, sndnull_chan_setformat), + KOBJMETHOD(channel_setspeed, sndnull_chan_setspeed), + KOBJMETHOD(channel_setblocksize, sndnull_chan_setblocksize), + KOBJMETHOD(channel_setfragments, sndnull_chan_setfragments), + KOBJMETHOD(channel_trigger, sndnull_chan_trigger), + KOBJMETHOD(channel_getptr, sndnull_chan_getptr), + KOBJMETHOD(channel_getcaps, sndnull_chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(sndnull_chan); + +static const struct { + int ctl; + int rec; +} sndnull_mixer_ctls[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = { 1, 0 }, + [SOUND_MIXER_BASS] = { 1, 0 }, + [SOUND_MIXER_TREBLE] = { 1, 0 }, + [SOUND_MIXER_SYNTH] = { 1, 1 }, + [SOUND_MIXER_PCM] = { 1, 1 }, + [SOUND_MIXER_SPEAKER] = { 1, 0 }, + [SOUND_MIXER_LINE] = { 1, 1 }, + [SOUND_MIXER_MIC] = { 1, 1 }, + [SOUND_MIXER_CD] = { 1, 1 }, + [SOUND_MIXER_IMIX] = { 1, 1 }, + [SOUND_MIXER_RECLEV] = { 1, 0 }, +}; + +static int +sndnull_mixer_init(struct snd_mixer *m) +{ + uint32_t mask, recmask; + int i; + + mask = 0; + recmask = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (sndnull_mixer_ctls[i].ctl != 0) + mask |= 1 << i; + if (sndnull_mixer_ctls[i].rec != 0) + recmask |= 1 << i; + } + + mix_setdevs(m, mask); + mix_setrecdevs(m, recmask); + + return (0); +} + +static int +sndnull_mixer_set(struct snd_mixer *m, unsigned dev, + unsigned left, unsigned right) +{ + if (!(dev < SOUND_MIXER_NRDEVICES && sndnull_mixer_ctls[dev].ctl != 0)) + return (-1); + + return (left | (right << 8)); +} + +static int +sndnull_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) +{ + uint32_t recsrc; + int i; + + recsrc = src; + + if (recsrc & SOUND_MASK_IMIX) + recsrc &= SOUND_MASK_IMIX; + else { + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (sndnull_mixer_ctls[i].rec == 0) + recsrc &= ~(1 << i); + } + } + + return (recsrc); +} + +static kobj_method_t sndnull_mixer_methods[] = { + KOBJMETHOD(mixer_init, sndnull_mixer_init), + KOBJMETHOD(mixer_set, sndnull_mixer_set), + KOBJMETHOD(mixer_setrecsrc, sndnull_mixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(sndnull_mixer); + +static int +sysctl_sndnull_rate(SYSCTL_HANDLER_ARGS) +{ + struct sndnull_info *sc; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return (EINVAL); + + SNDNULL_LOCK(sc); + val = sc->caps.maxspeed; + SNDNULL_UNLOCK(sc); + + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err != 0 || req->newptr == NULL) + return (err); + + if (val < SNDNULL_RATE_MIN) + val = SNDNULL_RATE_MIN; + if (val > SNDNULL_RATE_MAX) + val = SNDNULL_RATE_MAX; + + SNDNULL_LOCK(sc); + if (sndnull_anychan_active(sc)) + err = EBUSY; + else { + sc->caps.minspeed = (uint32_t)val; + sc->caps.maxspeed = sc->caps.minspeed; + } + SNDNULL_UNLOCK(sc); + + return (err); +} + +static int +sysctl_sndnull_format(SYSCTL_HANDLER_ARGS) +{ + struct sndnull_info *sc; + device_t dev; + int err; + char fmtstr[AFMTSTR_MAXSZ]; + uint32_t fmt; + + dev = oidp->oid_arg1; + + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return (EINVAL); + + SNDNULL_LOCK(sc); + fmt = sc->fmtlist[0]; + if (fmt != afmt2afmtstr(NULL, fmt, fmtstr, sizeof(fmtstr), + AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) + strlcpy(fmtstr, SNDNULL_FMTSTR_DEFAULT, sizeof(fmtstr)); + SNDNULL_UNLOCK(sc); + + err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); + + if (err != 0 || req->newptr == NULL) + return (err); + + fmt = afmtstr2afmt(NULL, fmtstr, AFMTSTR_STEREO_RETURN); + if (fmt == 0) + return (EINVAL); + + SNDNULL_LOCK(sc); + if (fmt != sc->fmtlist[0]) { + if (sndnull_anychan_active(sc)) + err = EBUSY; + else + sc->fmtlist[0] = fmt; + } + SNDNULL_UNLOCK(sc); + + return (err); +} + +static device_t sndnull_dev = NULL; + +static void +sndnull_dev_identify(driver_t *driver, device_t parent) +{ + if (sndnull_dev == NULL) + sndnull_dev = BUS_ADD_CHILD(parent, 0, "pcm", -1); +} + +static int +sndnull_dev_probe(device_t dev) +{ + if (dev != NULL && dev == sndnull_dev) { + device_set_desc(dev, SNDNULL_DESC); + return (BUS_PROBE_DEFAULT); + } + + return (ENXIO); +} + +static int +sndnull_dev_attach(device_t dev) +{ + struct sndnull_info *sc; + char status[SND_STATUSLEN]; + int i; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_null softc"); + sc->dev = dev; + + callout_init(&sc->poll_timer, CALLOUT_MPSAFE); + sc->poll_ticks = 1; + + sc->caps.minspeed = SNDNULL_RATE_DEFAULT; + sc->caps.maxspeed = SNDNULL_RATE_DEFAULT; + sc->fmtlist[0] = SNDNULL_FMT_DEFAULT; + sc->fmtlist[1] = 0; + sc->caps.fmtlist = sc->fmtlist; + + sc->bufsz = pcm_getbuffersize(dev, SNDNULL_BUFSZ_MIN, + SNDNULL_BUFSZ_DEFAULT, SNDNULL_BUFSZ_MAX); + sc->blkcnt = SNDNULL_BLKCNT_DEFAULT; + + sc->ringbuffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO); + + if (mixer_init(dev, &sndnull_mixer_class, sc) != 0) + device_printf(dev, "mixer_init() failed\n"); + + if (pcm_register(dev, sc, SNDNULL_NPCHAN, SNDNULL_NRCHAN)) + return (ENXIO); + + for (i = 0; i < SNDNULL_NPCHAN; i++) + pcm_addchan(dev, PCMDIR_PLAY, &sndnull_chan_class, sc); + for (i = 0; i < SNDNULL_NRCHAN; i++) + pcm_addchan(dev, PCMDIR_REC, &sndnull_chan_class, sc); + + snprintf(status, SND_STATUSLEN, "at %s %s", + device_get_nameunit(device_get_parent(dev)), + PCM_KLDSTRING(snd_null)); + + pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); + pcm_setstatus(dev, status); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "rate", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_sndnull_rate, "I", "runtime rate"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "format", CTLTYPE_STRING | CTLFLAG_RW, dev, sizeof(dev), + sysctl_sndnull_format, "A", "runtime format"); + + return (0); +} + +static void +sndnull_release_resources(struct sndnull_info *sc) +{ + if (sc == NULL) + return; + if (sc->chnum != 0) { + SNDNULL_LOCK(sc); + callout_stop(&sc->poll_timer); + SNDNULL_UNLOCK(sc); + callout_drain(&sc->poll_timer); + } + if (sc->ringbuffer != NULL) { + free(sc->ringbuffer, M_DEVBUF); + sc->ringbuffer = NULL; + } + if (sc->lock != NULL) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } + free(sc, M_DEVBUF); +} + +static int +sndnull_dev_detach(device_t dev) +{ + struct sndnull_info *sc; + int err; + + sc = pcm_getdevinfo(dev); + if (sc != NULL) { + err = pcm_unregister(dev); + if (err != 0) + return (err); + sndnull_release_resources(sc); + } + + return (0); +} + +static device_method_t sndnull_methods[] = { + DEVMETHOD(device_identify, sndnull_dev_identify), + DEVMETHOD(device_probe, sndnull_dev_probe), + DEVMETHOD(device_attach, sndnull_dev_attach), + DEVMETHOD(device_detach, sndnull_dev_detach), + { 0, 0 } +}; + +static driver_t sndnull_driver = { + "pcm", + sndnull_methods, + PCM_SOFTC_SIZE, +}; + +static int +sndnull_modevent(module_t mod, int type, void *data) +{ + switch (type) { + case MOD_UNLOAD: + if (sndnull_dev != NULL) + device_delete_child(device_get_parent(sndnull_dev), + sndnull_dev); + sndnull_dev = NULL; + case MOD_LOAD: + return (0); + break; + default: + break; + } + + return (EOPNOTSUPP); +} + +DRIVER_MODULE(snd_null, nexus, sndnull_driver, pcm_devclass, sndnull_modevent, + 0); +MODULE_DEPEND(snd_null, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_null, 1); --- sys/dev/sound/pci/als4000.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/als4000.c Thu Jul 12 12:04:19 2007 @@ -42,7 +42,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/als4000.c,v 1.18.2.1 2005/12/30 19:55:53 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/als4000.c,v 1.27 2007/06/17 06:10:41 ariff Exp $"); /* Debugging macro's */ #undef DEB @@ -221,7 +221,7 @@ ch->buffer = b; snd_mtxunlock(sc->lock); - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; return ch; @@ -391,14 +391,20 @@ struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + if (!PCMTRIG_COMMON(go)) + return 0; + snd_mtxlock(sc->lock); switch(go) { case PCMTRIG_START: als_playback_start(ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: als_playback_stop(ch); break; + default: + break; } snd_mtxunlock(sc->lock); return 0; @@ -489,6 +495,7 @@ case PCMTRIG_START: als_capture_start(ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: als_capture_stop(ch); break; @@ -773,7 +780,7 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, ALS_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, @@ -798,12 +805,8 @@ u_int32_t data; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_als4000 softc"); sc->dev = dev; data = pci_read_config(dev, PCIR_COMMAND, 2); --- sys/dev/sound/pci/atiixp.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/atiixp.c Thu Jul 12 12:04:19 2007 @@ -63,7 +63,19 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/atiixp.c,v 1.2.2.7 2007/04/26 08:21:44 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/atiixp.c,v 1.19 2007/07/09 20:41:23 ariff Exp $"); + +#define ATI_IXP_DMA_RETRY_MAX 100 + +#define ATI_IXP_BUFSZ_MIN 4096 +#define ATI_IXP_BUFSZ_MAX 65536 +#define ATI_IXP_BUFSZ_DEFAULT 16384 + +#define ATI_IXP_BLK_MIN 32 +#define ATI_IXP_BLK_ALIGN (~(ATI_IXP_BLK_MIN - 1)) + +#define ATI_IXP_CHN_RUNNING 0x00000001 +#define ATI_IXP_CHN_SUSPEND 0x00000002 struct atiixp_dma_op { volatile uint32_t addr; @@ -80,10 +92,12 @@ struct atiixp_info *parent; struct atiixp_dma_op *sgd_table; bus_addr_t sgd_addr; - uint32_t enable_bit, flush_bit, linkptr_bit, dma_dt_cur_bit; - uint32_t dma_segs; + uint32_t enable_bit, flush_bit, linkptr_bit, dt_cur_bit; + uint32_t blksz, blkcnt; + uint32_t ptr, prevptr; uint32_t fmt; - int caps_32bit, dir, active; + uint32_t flags; + int caps_32bit, dir; }; struct atiixp_info { @@ -108,10 +122,12 @@ uint32_t bufsz; uint32_t codec_not_ready_bits, codec_idx, codec_found; - uint32_t dma_segs; + uint32_t blkcnt; int registered_channels; struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; #define atiixp_rd(_sc, _reg) \ @@ -141,7 +157,7 @@ }; static struct pcmchan_caps atiixp_caps = { - ATI_IXP_BASE_RATE, + ATI_IXP_BASE_RATE, ATI_IXP_BASE_RATE, atiixp_fmt, 0 }; @@ -159,9 +175,9 @@ static void atiixp_enable_interrupts(struct atiixp_info *); static void atiixp_disable_interrupts(struct atiixp_info *); static void atiixp_reset_aclink(struct atiixp_info *); -static void atiixp_flush_dma(struct atiixp_info *, struct atiixp_chinfo *); -static void atiixp_enable_dma(struct atiixp_info *, struct atiixp_chinfo *); -static void atiixp_disable_dma(struct atiixp_info *, struct atiixp_chinfo *); +static void atiixp_flush_dma(struct atiixp_chinfo *); +static void atiixp_enable_dma(struct atiixp_chinfo *); +static void atiixp_disable_dma(struct atiixp_chinfo *); static int atiixp_waitready_codec(struct atiixp_info *); static int atiixp_rdcd(kobj_t, void *, int); @@ -171,9 +187,11 @@ struct pcm_channel *, int); static int atiixp_chan_setformat(kobj_t, void *, uint32_t); static int atiixp_chan_setspeed(kobj_t, void *, uint32_t); +static int atiixp_chan_setfragments(kobj_t, void *, uint32_t, uint32_t); static int atiixp_chan_setblocksize(kobj_t, void *, uint32_t); static void atiixp_buildsgdt(struct atiixp_chinfo *); static int atiixp_chan_trigger(kobj_t, void *, int); +static __inline uint32_t atiixp_dmapos(struct atiixp_chinfo *); static int atiixp_chan_getptr(kobj_t, void *); static struct pcmchan_caps *atiixp_chan_getcaps(kobj_t, void *); @@ -208,7 +226,10 @@ * Disable / ignore internal xrun/spdf interrupt flags * since it doesn't interest us (for now). */ -#if 0 +#if 1 + value &= ~(ATI_REG_IER_IN_XRUN_EN | ATI_REG_IER_OUT_XRUN_EN | + ATI_REG_IER_SPDF_XRUN_EN | ATI_REG_IER_SPDF_STATUS_EN); +#else value |= ATI_REG_IER_IN_XRUN_EN; value |= ATI_REG_IER_OUT_XRUN_EN; @@ -243,7 +264,7 @@ /* have to wait at least 10 usec for it to initialise */ DELAY(20); - }; + } /* perform a soft reset */ value = atiixp_rd(sc, ATI_REG_CMD); @@ -262,8 +283,7 @@ /* check if the ac-link is working; reset device otherwise */ timeout = 10; value = atiixp_rd(sc, ATI_REG_CMD); - while (!(value & ATI_REG_CMD_ACLINK_ACTIVE) - && --timeout) { + while (!(value & ATI_REG_CMD_ACLINK_ACTIVE) && --timeout) { #if 0 device_printf(sc->dev, "not up; resetting aclink hardware\n"); #endif @@ -284,7 +304,7 @@ /* check if its active now */ value = atiixp_rd(sc, ATI_REG_CMD); - }; + } if (timeout == 0) device_printf(sc->dev, "giving up aclink reset\n"); @@ -300,32 +320,32 @@ } static void -atiixp_flush_dma(struct atiixp_info *sc, struct atiixp_chinfo *ch) +atiixp_flush_dma(struct atiixp_chinfo *ch) { - atiixp_wr(sc, ATI_REG_FIFO_FLUSH, ch->flush_bit); + atiixp_wr(ch->parent, ATI_REG_FIFO_FLUSH, ch->flush_bit); } static void -atiixp_enable_dma(struct atiixp_info *sc, struct atiixp_chinfo *ch) +atiixp_enable_dma(struct atiixp_chinfo *ch) { uint32_t value; - value = atiixp_rd(sc, ATI_REG_CMD); + value = atiixp_rd(ch->parent, ATI_REG_CMD); if (!(value & ch->enable_bit)) { value |= ch->enable_bit; - atiixp_wr(sc, ATI_REG_CMD, value); + atiixp_wr(ch->parent, ATI_REG_CMD, value); } } -static void -atiixp_disable_dma(struct atiixp_info *sc, struct atiixp_chinfo *ch) +static void +atiixp_disable_dma(struct atiixp_chinfo *ch) { uint32_t value; - value = atiixp_rd(sc, ATI_REG_CMD); + value = atiixp_rd(ch->parent, ATI_REG_CMD); if (value & ch->enable_bit) { value &= ~ch->enable_bit; - atiixp_wr(sc, ATI_REG_CMD, value); + atiixp_wr(ch->parent, ATI_REG_CMD, value); } } @@ -339,12 +359,12 @@ do { if ((atiixp_rd(sc, ATI_REG_PHYS_OUT_ADDR) & - ATI_REG_PHYS_OUT_ADDR_EN) == 0) - return 0; + ATI_REG_PHYS_OUT_ADDR_EN) == 0) + return (0); DELAY(1); - } while (timeout--); + } while (--timeout); - return -1; + return (-1); } static int @@ -355,29 +375,28 @@ int timeout; if (atiixp_waitready_codec(sc)) - return -1; + return (-1); data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | - ATI_REG_PHYS_OUT_ADDR_EN | - ATI_REG_PHYS_OUT_RW | sc->codec_idx; + ATI_REG_PHYS_OUT_ADDR_EN | ATI_REG_PHYS_OUT_RW | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); if (atiixp_waitready_codec(sc)) - return -1; + return (-1); timeout = 500; do { data = atiixp_rd(sc, ATI_REG_PHYS_IN_ADDR); if (data & ATI_REG_PHYS_IN_READ_FLAG) - return data >> ATI_REG_PHYS_IN_DATA_SHIFT; + return (data >> ATI_REG_PHYS_IN_DATA_SHIFT); DELAY(1); - } while (timeout--); + } while (--timeout); if (reg < 0x7c) device_printf(sc->dev, "codec read timeout! (reg 0x%x)\n", reg); - return -1; + return (-1); } static int @@ -386,20 +405,20 @@ struct atiixp_info *sc = devinfo; if (atiixp_waitready_codec(sc)) - return -1; + return (-1); data = (data << ATI_REG_PHYS_OUT_DATA_SHIFT) | - (((uint32_t)reg) << ATI_REG_PHYS_OUT_ADDR_SHIFT) | - ATI_REG_PHYS_OUT_ADDR_EN | sc->codec_idx; + (((uint32_t)reg) << ATI_REG_PHYS_OUT_ADDR_SHIFT) | + ATI_REG_PHYS_OUT_ADDR_EN | sc->codec_idx; atiixp_wr(sc, ATI_REG_PHYS_OUT_ADDR, data); - return 0; + return (0); } static kobj_method_t atiixp_ac97_methods[] = { - KOBJMETHOD(ac97_read, atiixp_rdcd), - KOBJMETHOD(ac97_write, atiixp_wrcd), + KOBJMETHOD(ac97_read, atiixp_rdcd), + KOBJMETHOD(ac97_write, atiixp_wrcd), { 0, 0 } }; AC97_DECLARE(atiixp_ac97); @@ -422,15 +441,15 @@ ch->linkptr_bit = ATI_REG_OUT_DMA_LINKPTR; ch->enable_bit = ATI_REG_CMD_OUT_DMA_EN | ATI_REG_CMD_SEND_EN; ch->flush_bit = ATI_REG_FIFO_OUT_FLUSH; - ch->dma_dt_cur_bit = ATI_REG_OUT_DMA_DT_CUR; + ch->dt_cur_bit = ATI_REG_OUT_DMA_DT_CUR; /* Native 32bit playback working properly */ ch->caps_32bit = 1; } else { ch = &sc->rch; ch->linkptr_bit = ATI_REG_IN_DMA_LINKPTR; - ch->enable_bit = ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_RECEIVE_EN; + ch->enable_bit = ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_RECEIVE_EN; ch->flush_bit = ATI_REG_FIFO_IN_FLUSH; - ch->dma_dt_cur_bit = ATI_REG_IN_DMA_DT_CUR; + ch->dt_cur_bit = ATI_REG_IN_DMA_DT_CUR; /* XXX Native 32bit recording appear to be broken */ ch->caps_32bit = 1; } @@ -439,22 +458,23 @@ ch->parent = sc; ch->channel = c; ch->dir = dir; - ch->dma_segs = sc->dma_segs; + ch->blkcnt = sc->blkcnt; + ch->blksz = sc->bufsz / ch->blkcnt; atiixp_unlock(sc); - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) == -1) - return NULL; + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) == -1) + return (NULL); atiixp_lock(sc); num = sc->registered_channels++; - ch->sgd_table = &sc->sgd_table[num * ch->dma_segs]; - ch->sgd_addr = sc->sgd_addr + - (num * ch->dma_segs * sizeof(struct atiixp_dma_op)); - atiixp_disable_dma(sc, ch); + ch->sgd_table = &sc->sgd_table[num * ATI_IXP_DMA_CHSEGS_MAX]; + ch->sgd_addr = sc->sgd_addr + (num * ATI_IXP_DMA_CHSEGS_MAX * + sizeof(struct atiixp_dma_op)); + atiixp_disable_dma(ch); atiixp_unlock(sc); - return ch; + return (ch); } static int @@ -476,7 +496,7 @@ value &= ~ATI_REG_OUT_DMA_SLOT_MASK; /* We do not have support for more than 2 channels, _yet_. */ value |= ATI_REG_OUT_DMA_SLOT_BIT(3) | - ATI_REG_OUT_DMA_SLOT_BIT(4); + ATI_REG_OUT_DMA_SLOT_BIT(4); value |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT; atiixp_wr(sc, ATI_REG_OUT_DMA_SLOT, value); value = atiixp_rd(sc, ATI_REG_CMD); @@ -491,83 +511,302 @@ ch->fmt = format; atiixp_unlock(sc); - return 0; + return (0); } static int atiixp_chan_setspeed(kobj_t obj, void *data, uint32_t spd) { /* XXX We're supposed to do VRA/DRA processing right here */ - return ATI_IXP_BASE_RATE; + return (ATI_IXP_BASE_RATE); } static int -atiixp_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +atiixp_chan_setfragments(kobj_t obj, void *data, + uint32_t blksz, uint32_t blkcnt) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; - if (blksz > (sc->bufsz / ch->dma_segs)) - blksz = sc->bufsz / ch->dma_segs; + blksz &= ATI_IXP_BLK_ALIGN; + + if (blksz > (sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN)) + blksz = sndbuf_getmaxsize(ch->buffer) / ATI_IXP_DMA_CHSEGS_MIN; + if (blksz < ATI_IXP_BLK_MIN) + blksz = ATI_IXP_BLK_MIN; + if (blkcnt > ATI_IXP_DMA_CHSEGS_MAX) + blkcnt = ATI_IXP_DMA_CHSEGS_MAX; + if (blkcnt < ATI_IXP_DMA_CHSEGS_MIN) + blkcnt = ATI_IXP_DMA_CHSEGS_MIN; + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= ATI_IXP_DMA_CHSEGS_MIN) + blkcnt >>= 1; + else if ((blksz >> 1) >= ATI_IXP_BLK_MIN) + blksz >>= 1; + else + break; + } + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); - sndbuf_resize(ch->buffer, ch->dma_segs, blksz); + ch->blksz = sndbuf_getblksz(ch->buffer); + ch->blkcnt = sndbuf_getblkcnt(ch->buffer); - return sndbuf_getblksz(ch->buffer); + return (1); +} + +static int +atiixp_chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct atiixp_chinfo *ch = data; + struct atiixp_info *sc = ch->parent; + + atiixp_chan_setfragments(obj, data, blksz, sc->blkcnt); + + return (ch->blksz); } static void atiixp_buildsgdt(struct atiixp_chinfo *ch) { - uint32_t addr, blksz; + struct atiixp_info *sc = ch->parent; + uint32_t addr, blksz, blkcnt; int i; addr = sndbuf_getbufaddr(ch->buffer); - blksz = sndbuf_getblksz(ch->buffer); - for (i = 0; i < ch->dma_segs; i++) { + if (sc->polling != 0) { + blksz = ch->blksz * ch->blkcnt; + blkcnt = 1; + } else { + blksz = ch->blksz; + blkcnt = ch->blkcnt; + } + + for (i = 0; i < blkcnt; i++) { ch->sgd_table[i].addr = htole32(addr + (i * blksz)); ch->sgd_table[i].status = htole16(0); ch->sgd_table[i].size = htole16(blksz >> 2); - ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + - (((i + 1) % ch->dma_segs) * - sizeof(struct atiixp_dma_op))); + ch->sgd_table[i].next = htole32((uint32_t)ch->sgd_addr + + (((i + 1) % blkcnt) * sizeof(struct atiixp_dma_op))); } } +static __inline uint32_t +atiixp_dmapos(struct atiixp_chinfo *ch) +{ + struct atiixp_info *sc = ch->parent; + uint32_t reg, addr, sz, retry; + volatile uint32_t ptr; + + reg = ch->dt_cur_bit; + addr = sndbuf_getbufaddr(ch->buffer); + sz = ch->blkcnt * ch->blksz; + retry = ATI_IXP_DMA_RETRY_MAX; + + do { + ptr = atiixp_rd(sc, reg); + if (ptr < addr) + continue; + ptr -= addr; + if (ptr < sz) { +#if 0 +#ifdef ATI_IXP_DEBUG + if ((ptr & ~(ch->blksz - 1)) != ch->ptr) { + uint32_t delta; + + delta = (sz + ptr - ch->prevptr) % sz; +#ifndef ATI_IXP_DEBUG_VERBOSE + if (delta < ch->blksz) +#endif + device_printf(sc->dev, + "PCMDIR_%s: incoherent DMA " + "prevptr=%u ptr=%u " + "ptr=%u blkcnt=%u " + "[delta=%u != blksz=%u] " + "(%s)\n", + (ch->dir == PCMDIR_PLAY) ? + "PLAY" : "REC", + ch->prevptr, ptr, + ch->ptr, ch->blkcnt, + delta, ch->blksz, + (delta < ch->blksz) ? + "OVERLAPPED!" : "Ok"); + ch->ptr = ptr & ~(ch->blksz - 1); + } + ch->prevptr = ptr; +#endif +#endif + return (ptr); + } + } while (--retry); + + device_printf(sc->dev, "PCMDIR_%s: invalid DMA pointer ptr=%u\n", + (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr); + + return (0); +} + +static __inline int +atiixp_poll_channel(struct atiixp_chinfo *ch) +{ + uint32_t sz, delta; + volatile uint32_t ptr; + + if (!(ch->flags & ATI_IXP_CHN_RUNNING)) + return (0); + + sz = ch->blksz * ch->blkcnt; + ptr = atiixp_dmapos(ch); + ch->ptr = ptr; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +#define atiixp_chan_active(sc) (((sc)->pch.flags | (sc)->rch.flags) & \ + ATI_IXP_CHN_RUNNING) + +static void +atiixp_poll_callback(void *arg) +{ + struct atiixp_info *sc = arg; + uint32_t trigger = 0; + + if (sc == NULL) + return; + + atiixp_lock(sc); + if (sc->polling == 0 || atiixp_chan_active(sc) == 0) { + atiixp_unlock(sc); + return; + } + + trigger |= (atiixp_poll_channel(&sc->pch) != 0) ? 1 : 0; + trigger |= (atiixp_poll_channel(&sc->rch) != 0) ? 2 : 0; + + /* XXX */ + callout_reset(&sc->poll_timer, 1/*sc->poll_ticks*/, + atiixp_poll_callback, sc); + + atiixp_unlock(sc); + + if (trigger & 1) + chn_intr(sc->pch.channel); + if (trigger & 2) + chn_intr(sc->rch.channel); +} + static int atiixp_chan_trigger(kobj_t obj, void *data, int go) { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; uint32_t value; + int pollticks; + + if (!PCMTRIG_COMMON(go)) + return (0); atiixp_lock(sc); switch (go) { - case PCMTRIG_START: - atiixp_flush_dma(sc, ch); - atiixp_buildsgdt(ch); - atiixp_wr(sc, ch->linkptr_bit, 0); - atiixp_enable_dma(sc, ch); - atiixp_wr(sc, ch->linkptr_bit, - (uint32_t)ch->sgd_addr | ATI_REG_LINKPTR_EN); - break; - case PCMTRIG_STOP: - case PCMTRIG_ABORT: - atiixp_disable_dma(sc, ch); - atiixp_flush_dma(sc, ch); - break; - default: - atiixp_unlock(sc); - return 0; - break; + case PCMTRIG_START: + atiixp_flush_dma(ch); + atiixp_buildsgdt(ch); + atiixp_wr(sc, ch->linkptr_bit, 0); + atiixp_enable_dma(ch); + atiixp_wr(sc, ch->linkptr_bit, + (uint32_t)ch->sgd_addr | ATI_REG_LINKPTR_EN); + if (sc->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (atiixp_chan_active(sc) == 0 || + pollticks < sc->poll_ticks) { + if (bootverbose) { + if (atiixp_chan_active(sc) == 0) + device_printf(sc->dev, + "%s: pollticks=%d\n", + __func__, pollticks); + else + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + } + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, 1, + atiixp_poll_callback, sc); + } + } + ch->flags |= ATI_IXP_CHN_RUNNING; + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + atiixp_disable_dma(ch); + atiixp_flush_dma(ch); + ch->flags &= ~ATI_IXP_CHN_RUNNING; + if (sc->polling != 0) { + if (atiixp_chan_active(sc) == 0) { + callout_stop(&sc->poll_timer); + sc->poll_ticks = 1; + } else { + if (sc->pch.flags & ATI_IXP_CHN_RUNNING) + ch = &sc->pch; + else + ch = &sc->rch; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (pollticks > sc->poll_ticks) { + if (bootverbose) + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_timer, + 1, atiixp_poll_callback, + sc); + } + } + } + break; + default: + atiixp_unlock(sc); + return (0); + break; } /* Update bus busy status */ value = atiixp_rd(sc, ATI_REG_IER); - if (atiixp_rd(sc, ATI_REG_CMD) & ( - ATI_REG_CMD_SEND_EN | ATI_REG_CMD_RECEIVE_EN | - ATI_REG_CMD_SPDF_OUT_EN)) + if (atiixp_rd(sc, ATI_REG_CMD) & (ATI_REG_CMD_SEND_EN | + ATI_REG_CMD_RECEIVE_EN | ATI_REG_CMD_SPDF_OUT_EN)) value |= ATI_REG_IER_SET_BUS_BUSY; else value &= ~ATI_REG_IER_SET_BUS_BUSY; @@ -575,7 +814,7 @@ atiixp_unlock(sc); - return 0; + return (0); } static int @@ -583,36 +822,16 @@ { struct atiixp_chinfo *ch = data; struct atiixp_info *sc = ch->parent; - uint32_t addr, align, retry, sz; - volatile uint32_t ptr; - - addr = sndbuf_getbufaddr(ch->buffer); - align = (ch->fmt & AFMT_32BIT) ? 7 : 3; - retry = 100; - sz = sndbuf_getblksz(ch->buffer) * ch->dma_segs; + uint32_t ptr; atiixp_lock(sc); - do { - ptr = atiixp_rd(sc, ch->dma_dt_cur_bit); - if (ptr < addr) - continue; - ptr -= addr; - if (ptr < sz && !(ptr & align)) - break; - } while (--retry); + if (sc->polling != 0) + ptr = ch->ptr; + else + ptr = atiixp_dmapos(ch); atiixp_unlock(sc); -#if 0 - if (retry != 100) { - device_printf(sc->dev, - "%saligned hwptr: dir=PCMDIR_%s ptr=%u fmt=0x%08x retry=%d\n", - (ptr & align) ? "un" : "", - (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", ptr, - ch->fmt, 100 - retry); - } -#endif - - return (retry > 0) ? ptr : 0; + return (ptr); } static struct pcmchan_caps * @@ -621,8 +840,8 @@ struct atiixp_chinfo *ch = data; if (ch->caps_32bit) - return &atiixp_caps_32bit; - return &atiixp_caps; + return (&atiixp_caps_32bit); + return (&atiixp_caps); } static kobj_method_t atiixp_chan_methods[] = { @@ -630,6 +849,7 @@ KOBJMETHOD(channel_setformat, atiixp_chan_setformat), KOBJMETHOD(channel_setspeed, atiixp_chan_setspeed), KOBJMETHOD(channel_setblocksize, atiixp_chan_setblocksize), + KOBJMETHOD(channel_setfragments, atiixp_chan_setfragments), KOBJMETHOD(channel_trigger, atiixp_chan_trigger), KOBJMETHOD(channel_getptr, atiixp_chan_getptr), KOBJMETHOD(channel_getcaps, atiixp_chan_getcaps), @@ -645,8 +865,13 @@ { struct atiixp_info *sc = p; uint32_t status, enable, detected_codecs; + uint32_t trigger = 0; atiixp_lock(sc); + if (sc->polling != 0) { + atiixp_unlock(sc); + return; + } status = atiixp_rd(sc, ATI_REG_ISR); if (status == 0) { @@ -654,16 +879,12 @@ return; } - if ((status & ATI_REG_ISR_IN_STATUS) && sc->rch.channel) { - atiixp_unlock(sc); - chn_intr(sc->rch.channel); - atiixp_lock(sc); - } - if ((status & ATI_REG_ISR_OUT_STATUS) && sc->pch.channel) { - atiixp_unlock(sc); - chn_intr(sc->pch.channel); - atiixp_lock(sc); - } + if ((status & ATI_REG_ISR_OUT_STATUS) && + (sc->pch.flags & ATI_IXP_CHN_RUNNING)) + trigger |= 1; + if ((status & ATI_REG_ISR_IN_STATUS) && + (sc->rch.flags & ATI_IXP_CHN_RUNNING)) + trigger |= 2; #if 0 if (status & ATI_REG_ISR_IN_XRUN) { @@ -685,11 +906,17 @@ enable = atiixp_rd(sc, ATI_REG_IER); enable &= ~detected_codecs; atiixp_wr(sc, ATI_REG_IER, enable); + wakeup(sc); } /* acknowledge */ atiixp_wr(sc, ATI_REG_ISR, status); atiixp_unlock(sc); + + if (trigger & 1) + chn_intr(sc->pch.channel); + if (trigger & 2) + chn_intr(sc->rch.channel); } static void @@ -712,7 +939,7 @@ /* clear all DMA enables (preserving rest of settings) */ value = atiixp_rd(sc, ATI_REG_CMD); value &= ~(ATI_REG_CMD_IN_DMA_EN | ATI_REG_CMD_OUT_DMA_EN | - ATI_REG_CMD_SPDF_OUT_EN ); + ATI_REG_CMD_SPDF_OUT_EN ); atiixp_wr(sc, ATI_REG_CMD, value); /* reset aclink */ @@ -726,12 +953,54 @@ atiixp_unlock(sc); } +#ifdef SND_DYNSYSCTL +static int +sysctl_atiixp_polling(SYSCTL_HANDLER_ARGS) +{ + struct atiixp_info *sc; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return (EINVAL); + atiixp_lock(sc); + val = sc->polling; + atiixp_unlock(sc); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + atiixp_lock(sc); + if (val != sc->polling) { + if (atiixp_chan_active(sc) != 0) + err = EBUSY; + else if (val == 0) { + atiixp_enable_interrupts(sc); + sc->polling = 0; + DELAY(1000); + } else { + atiixp_disable_interrupts(sc); + sc->polling = 1; + DELAY(1000); + } + } + atiixp_unlock(sc); + + return (err); +} +#endif + static void atiixp_chip_post_init(void *arg) { struct atiixp_info *sc = (struct atiixp_info *)arg; uint32_t subdev; - int i, timeout, found; + int i, timeout, found, polling; char status[SND_STATUSLEN]; atiixp_lock(sc); @@ -741,17 +1010,23 @@ sc->delayed_attach.ich_func = NULL; } - /* wait for the interrupts to happen */ - timeout = 100; - while (--timeout) { - msleep(sc, sc->lock, PWAIT, "ixpslp", 1); - if (sc->codec_not_ready_bits) - break; + polling = sc->polling; + sc->polling = 0; + + timeout = 10; + if (sc->codec_not_ready_bits == 0) { + /* wait for the interrupts to happen */ + do { + msleep(sc, sc->lock, PWAIT, "ixpslp", max(hz / 10, 1)); + if (sc->codec_not_ready_bits != 0) + break; + } while (--timeout); } + sc->polling = polling; atiixp_disable_interrupts(sc); - if (timeout == 0) { + if (sc->codec_not_ready_bits == 0 && timeout == 0) { device_printf(sc->dev, "WARNING: timeout during codec detection; " "codecs might be present but haven't interrupted\n"); @@ -765,22 +1040,19 @@ * ATI IXP can have upto 3 codecs, but single codec should be * suffice for now. */ - if (!(sc->codec_not_ready_bits & - ATI_REG_ISR_CODEC0_NOT_READY)) { + if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC0_NOT_READY)) { /* codec 0 present */ sc->codec_found++; sc->codec_idx = 0; found++; } - if (!(sc->codec_not_ready_bits & - ATI_REG_ISR_CODEC1_NOT_READY)) { + if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC1_NOT_READY)) { /* codec 1 present */ sc->codec_found++; } - if (!(sc->codec_not_ready_bits & - ATI_REG_ISR_CODEC2_NOT_READY)) { + if (!(sc->codec_not_ready_bits & ATI_REG_ISR_CODEC2_NOT_READY)) { /* codec 2 present */ sc->codec_found++; } @@ -795,11 +1067,13 @@ if (sc->codec == NULL) goto postinitbad; - subdev = (pci_get_subdevice(sc->dev) << 16) | pci_get_subvendor(sc->dev); + subdev = (pci_get_subdevice(sc->dev) << 16) | + pci_get_subvendor(sc->dev); switch (subdev) { case 0x11831043: /* ASUS A6R */ case 0x2043161f: /* Maxselect x710s - http://maxselect.ru/ */ - ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); + ac97_setflags(sc->codec, ac97_getflags(sc->codec) | + AC97_F_EAPD_INV); break; default: break; @@ -815,14 +1089,22 @@ for (i = 0; i < ATI_IXP_NRCHAN; i++) pcm_addchan(sc->dev, PCMDIR_REC, &atiixp_chan_class, sc); - snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s", - rman_get_start(sc->reg), rman_get_start(sc->irq), - PCM_KLDSTRING(snd_atiixp)); +#ifdef SND_DYNSYSCTL + SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), + sysctl_atiixp_polling, "I", "Enable polling mode"); +#endif + + snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s", + rman_get_start(sc->reg), rman_get_start(sc->irq), + PCM_KLDSTRING(snd_atiixp)); pcm_setstatus(sc->dev, status); atiixp_lock(sc); - atiixp_enable_interrupts(sc); + if (sc->polling == 0) + atiixp_enable_interrupts(sc); atiixp_unlock(sc); return; @@ -836,6 +1118,13 @@ { if (sc == NULL) return; + if (sc->registered_channels != 0) { + atiixp_lock(sc); + sc->polling = 0; + callout_stop(&sc->poll_timer); + atiixp_unlock(sc); + callout_drain(&sc->poll_timer); + } if (sc->codec) { ac97_destroy(sc->codec); sc->codec = NULL; @@ -871,6 +1160,7 @@ snd_mtxfree(sc->lock); sc->lock = NULL; } + free(sc, M_DEVBUF); } static int @@ -881,15 +1171,15 @@ vendor = pci_get_vendor(dev); devid = pci_get_device(dev); - for (i = 0; i < sizeof(atiixp_hw)/sizeof(atiixp_hw[0]); i++) { + for (i = 0; i < sizeof(atiixp_hw) / sizeof(atiixp_hw[0]); i++) { if (vendor == atiixp_hw[i].vendor && - devid == atiixp_hw[i].devid) { + devid == atiixp_hw[i].devid) { device_set_desc(dev, atiixp_hw[i].desc); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); } } - return ENXIO; + return (ENXIO); } static int @@ -898,25 +1188,26 @@ struct atiixp_info *sc; int i; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_atiixp softc"); sc->dev = dev; - /* - * Default DMA segments per playback / recording channel - */ - sc->dma_segs = ATI_IXP_DMA_CHSEGS; + + callout_init(&sc->poll_timer, CALLOUT_MPSAFE); + sc->poll_ticks = 1; + + if (resource_int_value(device_get_name(sc->dev), + device_get_unit(sc->dev), "polling", &i) == 0 && i != 0) + sc->polling = 1; + else + sc->polling = 0; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); sc->regid = PCIR_BAR(0); sc->regtype = SYS_RES_MEMORY; - sc->reg = bus_alloc_resource_any(dev, sc->regtype, &sc->regid, - RF_ACTIVE); + sc->reg = bus_alloc_resource_any(dev, sc->regtype, + &sc->regid, RF_ACTIVE); if (!sc->reg) { device_printf(dev, "unable to allocate register space\n"); @@ -926,14 +1217,14 @@ sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); - sc->bufsz = pcm_getbuffersize(dev, 4096, ATI_IXP_DEFAULT_BUFSZ, 65536); + sc->bufsz = pcm_getbuffersize(dev, ATI_IXP_BUFSZ_MIN, + ATI_IXP_BUFSZ_DEFAULT, ATI_IXP_BUFSZ_MAX); sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, - RF_ACTIVE | RF_SHAREABLE); - if (!sc->irq || - snd_setup_intr(dev, sc->irq, INTR_MPSAFE, - atiixp_intr, sc, &sc->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, + atiixp_intr, sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } @@ -941,32 +1232,29 @@ /* * Let the user choose the best DMA segments. */ - if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "dma_segs", - &i) == 0) { - if (i < ATI_IXP_DMA_CHSEGS_MIN) - i = ATI_IXP_DMA_CHSEGS_MIN; - if (i > ATI_IXP_DMA_CHSEGS_MAX) - i = ATI_IXP_DMA_CHSEGS_MAX; - sc->dma_segs = i; - } + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= ATI_IXP_BLK_ALIGN; + if (i < ATI_IXP_BLK_MIN) + i = ATI_IXP_BLK_MIN; + sc->blkcnt = sc->bufsz / i; + i = 0; + while (sc->blkcnt >> i) + i++; + sc->blkcnt = 1 << (i - 1); + if (sc->blkcnt < ATI_IXP_DMA_CHSEGS_MIN) + sc->blkcnt = ATI_IXP_DMA_CHSEGS_MIN; + else if (sc->blkcnt > ATI_IXP_DMA_CHSEGS_MAX) + sc->blkcnt = ATI_IXP_DMA_CHSEGS_MAX; - /* - * round the value to the nearest ^2 - */ - i = 0; - while (sc->dma_segs >> i) - i++; - sc->dma_segs = 1 << (i - 1); - if (sc->dma_segs < ATI_IXP_DMA_CHSEGS_MIN) - sc->dma_segs = ATI_IXP_DMA_CHSEGS_MIN; - else if (sc->dma_segs > ATI_IXP_DMA_CHSEGS_MAX) - sc->dma_segs = ATI_IXP_DMA_CHSEGS_MAX; + } else + sc->blkcnt = ATI_IXP_DMA_CHSEGS; /* * DMA tag for scatter-gather buffers and link pointers */ - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -977,12 +1265,13 @@ goto bad; } - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/sc->dma_segs * ATI_IXP_NCHANS * - sizeof(struct atiixp_dma_op), + /*maxsize*/ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * + sizeof(struct atiixp_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/NULL, /*lockarg*/NULL, &sc->sgd_dmat) != 0) { @@ -990,14 +1279,13 @@ goto bad; } - if (bus_dmamem_alloc(sc->sgd_dmat, (void **)&sc->sgd_table, - BUS_DMA_NOWAIT, &sc->sgd_dmamap) == -1) + if (bus_dmamem_alloc(sc->sgd_dmat, (void **)&sc->sgd_table, + BUS_DMA_NOWAIT, &sc->sgd_dmamap) == -1) goto bad; - if (bus_dmamap_load(sc->sgd_dmat, sc->sgd_dmamap, sc->sgd_table, - sc->dma_segs * ATI_IXP_NCHANS * - sizeof(struct atiixp_dma_op), - atiixp_dma_cb, sc, 0)) + if (bus_dmamap_load(sc->sgd_dmat, sc->sgd_dmamap, sc->sgd_table, + ATI_IXP_DMA_CHSEGS_MAX * ATI_IXP_NCHANS * + sizeof(struct atiixp_dma_op), atiixp_dma_cb, sc, 0)) goto bad; @@ -1006,16 +1294,16 @@ sc->delayed_attach.ich_func = atiixp_chip_post_init; sc->delayed_attach.ich_arg = sc; if (cold == 0 || - config_intrhook_establish(&sc->delayed_attach) != 0) { + config_intrhook_establish(&sc->delayed_attach) != 0) { sc->delayed_attach.ich_func = NULL; atiixp_chip_post_init(sc); } - return 0; + return (0); bad: atiixp_release_resource(sc); - return ENXIO; + return (ENXIO); } static int @@ -1029,14 +1317,14 @@ if (sc->codec != NULL) { r = pcm_unregister(dev); if (r) - return r; + return (r); } sc->codec = NULL; - atiixp_disable_interrupts(sc); + if (sc->st != 0 && sc->sh != 0) + atiixp_disable_interrupts(sc); atiixp_release_resource(sc); - free(sc, M_DEVBUF); } - return 0; + return (0); } static int @@ -1048,16 +1336,17 @@ /* quickly disable interrupts and save channels active state */ atiixp_lock(sc); atiixp_disable_interrupts(sc); - value = atiixp_rd(sc, ATI_REG_CMD); - sc->pch.active = (value & ATI_REG_CMD_SEND_EN) ? 1 : 0; - sc->rch.active = (value & ATI_REG_CMD_RECEIVE_EN) ? 1 : 0; atiixp_unlock(sc); /* stop everything */ - if (sc->pch.channel && sc->pch.active) + if (sc->pch.flags & ATI_IXP_CHN_RUNNING) { atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_STOP); - if (sc->rch.channel && sc->rch.active) + sc->pch.flags |= ATI_IXP_CHN_SUSPEND; + } + if (sc->rch.flags & ATI_IXP_CHN_RUNNING) { atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_STOP); + sc->rch.flags |= ATI_IXP_CHN_SUSPEND; + } /* power down aclink and pci bus */ atiixp_lock(sc); @@ -1067,7 +1356,7 @@ pci_set_powerstate(dev, PCI_POWERSTATE_D3); atiixp_unlock(sc); - return 0; + return (0); } static int @@ -1086,32 +1375,37 @@ if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); - return ENXIO; + return (ENXIO); } /* * Resume channel activities. Reset channel format regardless * of its previous state. */ - if (sc->pch.channel) { - if (sc->pch.fmt) + if (sc->pch.channel != NULL) { + if (sc->pch.fmt != 0) atiixp_chan_setformat(NULL, &sc->pch, sc->pch.fmt); - if (sc->pch.active) + if (sc->pch.flags & ATI_IXP_CHN_SUSPEND) { + sc->pch.flags &= ~ATI_IXP_CHN_SUSPEND; atiixp_chan_trigger(NULL, &sc->pch, PCMTRIG_START); + } } - if (sc->rch.channel) { - if (sc->rch.fmt) + if (sc->rch.channel != NULL) { + if (sc->rch.fmt != 0) atiixp_chan_setformat(NULL, &sc->rch, sc->rch.fmt); - if (sc->rch.active) + if (sc->rch.flags & ATI_IXP_CHN_SUSPEND) { + sc->rch.flags &= ~ATI_IXP_CHN_SUSPEND; atiixp_chan_trigger(NULL, &sc->rch, PCMTRIG_START); + } } /* enable interrupts */ atiixp_lock(sc); - atiixp_enable_interrupts(sc); + if (sc->polling == 0) + atiixp_enable_interrupts(sc); atiixp_unlock(sc); - return 0; + return (0); } static device_method_t atiixp_methods[] = { --- sys/dev/sound/pci/atiixp.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/atiixp.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/atiixp.h,v 1.1.2.2 2006/03/02 00:09:30 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/atiixp.h,v 1.3 2006/10/01 13:30:30 ariff Exp $ */ #ifndef _ATIIXP_H_ @@ -46,9 +46,8 @@ #define ATI_IXP_DMA_CHSEGS_MIN 2 #define ATI_IXP_DMA_CHSEGS_MAX 256 -#define ATI_IXP_DEFAULT_BUFSZ (1 << 13) /* 8192 */ - #define ATI_VENDOR_ID 0x1002 /* ATI Technologies */ + #define ATI_IXP_200_ID 0x4341 #define ATI_IXP_300_ID 0x4361 #define ATI_IXP_400_ID 0x4370 --- sys/dev/sound/pci/au88x0.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/au88x0.c Thu Jul 12 12:04:19 2007 @@ -25,7 +25,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/au88x0.c,v 1.10 2005/03/01 08:58:05 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/au88x0.c,v 1.13 2007/06/17 06:10:41 ariff Exp $ */ #include @@ -332,7 +332,7 @@ struct au88x0_info *aui = arg; struct au88x0_chan_info *auci = au88x0_channel(aui, dir); - if (sndbuf_alloc(buf, aui->aui_dmat, aui->aui_bufsize) != 0) + if (sndbuf_alloc(buf, aui->aui_dmat, 0, aui->aui_bufsize) != 0) return (NULL); auci->auci_aui = aui; auci->auci_pcmchan = chan; @@ -572,10 +572,7 @@ uint32_t config; int error; - if ((aui = malloc(sizeof *aui, M_DEVBUF, M_NOWAIT|M_ZERO)) == NULL) { - device_printf(dev, "failed to allocate softc\n"); - return (ENXIO); - } + aui = malloc(sizeof(*aui), M_DEVBUF, M_WAITOK | M_ZERO); aui->aui_dev = dev; /* Model-specific parameters */ @@ -636,7 +633,7 @@ /* DMA mapping */ aui->aui_bufsize = pcm_getbuffersize(dev, AU88X0_BUFSIZE_MIN, AU88X0_BUFSIZE_DFLT, AU88X0_BUFSIZE_MAX); - error = bus_dma_tag_create(NULL, + error = bus_dma_tag_create(bus_get_dma_tag(dev), 2, 0, /* 16-bit alignment, no boundary */ BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, /* restrict to 4GB */ NULL, NULL, /* no filter */ --- sys/dev/sound/pci/aureal.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/aureal.c Thu Jul 12 12:04:19 2007 @@ -31,7 +31,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/aureal.c,v 1.32 2005/03/01 08:58:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/aureal.c,v 1.36 2007/06/17 06:10:41 ariff Exp $"); /* PCI IDs of supported chips */ #define AU8820_PCI_ID 0x000112eb @@ -303,7 +303,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, au->parent_dmat, AU_BUFFSIZE) != 0) + if (sndbuf_alloc(ch->buffer, au->parent_dmat, 0, AU_BUFFSIZE) != 0) return NULL; return ch; } @@ -339,12 +339,12 @@ struct au_chinfo *ch = data; struct au_info *au = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (ch->dir == PCMDIR_PLAY) { au_setadb(au, 0x11, (go)? 1 : 0); - if (!go) { + if (go != PCMTRIG_START) { au_wr(au, 0, 0xf800, 0, 4); au_wr(au, 0, 0xf804, 0, 4); au_delroute(au, 0x58); @@ -558,11 +558,7 @@ struct ac97_info *codec; char status[SND_STATUSLEN]; - if ((au = malloc(sizeof(*au), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + au = malloc(sizeof(*au), M_DEVBUF, M_WAITOK | M_ZERO); au->unit = device_get_unit(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -637,7 +633,8 @@ if (codec == NULL) goto bad; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/cmi.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/cmi.c Thu Jul 12 12:04:19 2007 @@ -48,10 +48,12 @@ #include #include +#include #include "mixer_if.h" +#include "mpufoi_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cmi.c,v 1.32.2.2 2006/01/24 18:54:22 joel Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cmi.c,v 1.44 2007/06/17 06:10:41 ariff Exp $"); /* Supported chip ID's */ #define CMI8338A_PCI_ID 0x010013f6 @@ -112,6 +114,13 @@ int spdif_enabled; unsigned int bufsz; struct sc_chinfo pch, rch; + + struct mpu401 *mpu; + mpu401_intr_t *mpu_intr; + struct resource *mpu_reg; + int mpu_regid; + bus_space_tag_t mpu_bt; + bus_space_handle_t mpu_bh; }; /* Channel caps */ @@ -340,7 +349,7 @@ ch->spd = DSP_DEFAULT_SPEED; ch->buffer = b; ch->dma_active = 0; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("cmichan_init failed\n")); return NULL; } @@ -466,12 +475,16 @@ struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + if (!PCMTRIG_COMMON(go)) + return 0; + snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { switch(go) { case PCMTRIG_START: cmi_ch0_start(sc, ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch0_stop(sc, ch); break; @@ -481,6 +494,7 @@ case PCMTRIG_START: cmi_ch1_start(sc, ch); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: cmi_ch1_stop(sc, ch); break; @@ -551,6 +565,9 @@ } } + if(sc->mpu_intr) { + (sc->mpu_intr)(sc->mpu); + } snd_mtxunlock(sc->lock); return; } @@ -729,8 +746,13 @@ cmi_initsys(struct sc_info* sc) { #ifdef SND_DYNSYSCTL - SYSCTL_ADD_INT(snd_sysctl_tree(sc->dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(sc->dev)), + /* XXX: an user should be able to set this with a control tool, + if not done before 7.0-RELEASE, this needs to be converted + to a device specific sysctl "dev.pcm.X.yyy" via + device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "spdif_enabled", CTLFLAG_RW, &sc->spdif_enabled, 0, "enable SPDIF output at 44.1 kHz and above"); @@ -747,6 +769,74 @@ }; MIXER_DECLARE(cmi_mixer); +/* + * mpu401 functions + */ + +static unsigned char +cmi_mread(void *arg, struct sc_info *sc, int reg) +{ + unsigned int d; + + d = bus_space_read_1(0,0, 0x330 + reg); + /* printf("cmi_mread: reg %x %x\n",reg, d); + */ + return d; +} + +static void +cmi_mwrite(void *arg, struct sc_info *sc, int reg, unsigned char b) +{ + + bus_space_write_1(0,0,0x330 + reg , b); +} + +static int +cmi_muninit(void *arg, struct sc_info *sc) +{ + + snd_mtxlock(sc->lock); + sc->mpu_intr = 0; + sc->mpu = 0; + snd_mtxunlock(sc->lock); + + return 0; +} + +static kobj_method_t cmi_mpu_methods[] = { + KOBJMETHOD(mpufoi_read, cmi_mread), + KOBJMETHOD(mpufoi_write, cmi_mwrite), + KOBJMETHOD(mpufoi_uninit, cmi_muninit), + { 0, 0 } +}; + +static DEFINE_CLASS(cmi_mpu, cmi_mpu_methods, 0); + +static void +cmi_midiattach(struct sc_info *sc) { +/* + const struct { + int port,bits; + } *p, ports[] = { + {0x330,0}, + {0x320,1}, + {0x310,2}, + {0x300,3}, + {0,0} } ; + Notes, CMPCI_REG_VMPUSEL sets the io port for the mpu. Does + anyone know how to bus_space tag? +*/ + cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); + cmi_clr4(sc, CMPCI_REG_LEGACY_CTRL, + CMPCI_REG_VMPUSEL_MASK << CMPCI_REG_VMPUSEL_SHIFT); + cmi_set4(sc, CMPCI_REG_LEGACY_CTRL, + 0 << CMPCI_REG_VMPUSEL_SHIFT ); + cmi_set4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); + sc->mpu = mpu401_init(&cmi_mpu_class, sc, cmi_intr, &sc->mpu_intr); +} + + + /* ------------------------------------------------------------------------- */ /* Power and reset */ @@ -802,6 +892,10 @@ CMPCI_REG_TDMA_INTR_ENABLE); cmi_clr4(sc, CMPCI_REG_FUNC_0, CMPCI_REG_CH0_ENABLE | CMPCI_REG_CH1_ENABLE); + cmi_clr4(sc, CMPCI_REG_FUNC_1, CMPCI_REG_UART_ENABLE); + + if( sc->mpu ) + sc->mpu_intr = 0; } /* ------------------------------------------------------------------------- */ @@ -834,13 +928,8 @@ u_int32_t data; char status[SND_STATUSLEN]; - sc = malloc(sizeof(struct sc_info), M_DEVBUF, M_NOWAIT | M_ZERO); - if (sc == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_cmi softc"); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); @@ -857,6 +946,9 @@ sc->st = rman_get_bustag(sc->reg); sc->sh = rman_get_bushandle(sc->reg); + if (0) + cmi_midiattach(sc); + sc->irqid = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); @@ -868,7 +960,8 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, CMI_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -936,7 +1029,12 @@ bus_dma_tag_destroy(sc->parent_dmat); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + if(sc->mpu) + mpu401_uninit(sc->mpu); bus_release_resource(dev, SYS_RES_IOPORT, sc->regid, sc->reg); + if (sc->mpu_reg) + bus_release_resource(dev, SYS_RES_IOPORT, sc->mpu_regid, sc->mpu_reg); + snd_mtxfree(sc->lock); free(sc, M_DEVBUF); @@ -1007,4 +1105,5 @@ DRIVER_MODULE(snd_cmi, pci, cmi_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_cmi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_DEPEND(snd_cmi, midi, 1,1,1); MODULE_VERSION(snd_cmi, 1); --- sys/dev/sound/pci/cs4281.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/cs4281.c Thu Jul 12 12:04:19 2007 @@ -37,7 +37,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cs4281.c,v 1.22 2005/03/01 08:58:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/cs4281.c,v 1.26 2007/06/17 06:10:41 ariff Exp $"); #define CS4281_DEFAULT_BUFSZ 16384 @@ -314,7 +314,7 @@ struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; ch->buffer = b; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { return NULL; } ch->parent = sc; @@ -425,6 +425,7 @@ adcdac_prog(ch); adcdac_go(ch, 1); break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: adcdac_go(ch, 0); break; @@ -753,11 +754,7 @@ u_int32_t data; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); @@ -824,7 +821,8 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, CS4281_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/csa.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/csa.c Thu Jul 12 12:04:19 2007 @@ -48,7 +48,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csa.c,v 1.33.2.1 2005/12/30 19:55:53 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csa.c,v 1.37 2007/03/17 19:37:09 ariff Exp $"); /* This is the pci device id. */ #define CS4610_PCI_ID 0x60011013 @@ -82,7 +82,10 @@ struct resource *r); static int csa_setup_intr(device_t bus, device_t child, struct resource *irq, int flags, - driver_intr_t *intr, void *arg, void **cookiep); +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif + driver_intr_t *intr, void *arg, void **cookiep); static int csa_teardown_intr(device_t bus, device_t child, struct resource *irq, void *cookie); static driver_intr_t csa_intr; @@ -336,23 +339,31 @@ { csa_res *resp; sc_p scp; + struct sndcard_func *func; int err; scp = device_get_softc(dev); resp = &scp->res; - err = 0; - if (scp->midi != NULL) + if (scp->midi != NULL) { + func = device_get_ivars(scp->midi); err = device_delete_child(dev, scp->midi); - if (err) - return err; - scp->midi = NULL; + if (err != 0) + return err; + if (func != NULL) + free(func, M_DEVBUF); + scp->midi = NULL; + } - if (scp->pcm != NULL) + if (scp->pcm != NULL) { + func = device_get_ivars(scp->pcm); err = device_delete_child(dev, scp->pcm); - if (err) - return err; - scp->pcm = NULL; + if (err != 0) + return err; + if (func != NULL) + free(func, M_DEVBUF); + scp->pcm = NULL; + } bus_teardown_intr(dev, resp->irq, scp->ih); bus_release_resource(dev, SYS_RES_IRQ, resp->irq_rid, resp->irq); @@ -439,12 +450,21 @@ static int csa_setup_intr(device_t bus, device_t child, struct resource *irq, int flags, +#if __FreeBSD_version >= 700031 + driver_filter_t *filter, +#endif driver_intr_t *intr, void *arg, void **cookiep) { sc_p scp; csa_res *resp; struct sndcard_func *func; +#if __FreeBSD_version >= 700031 + if (filter != NULL) { + printf("ata-csa.c: we cannot use a filter here\n"); + return (EINVAL); + } +#endif scp = device_get_softc(bus); resp = &scp->res; --- sys/dev/sound/pci/csapcm.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/csapcm.c Thu Jul 12 12:04:19 2007 @@ -38,7 +38,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csapcm.c,v 1.34.2.2 2006/04/04 17:32:48 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/csapcm.c,v 1.41 2007/06/17 06:10:41 ariff Exp $"); /* Buffer size on dma transfer. Fixed for CS416x. */ #define CS461x_BUFFSIZE (4 * 1024) @@ -534,7 +534,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, csa->parent_dmat, CS461x_BUFFSIZE) != 0) + if (sndbuf_alloc(ch->buffer, csa->parent_dmat, 0, CS461x_BUFFSIZE) != 0) return NULL; return ch; } @@ -569,7 +569,7 @@ struct csa_chinfo *ch = data; struct csa_info *csa = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { @@ -704,7 +704,9 @@ if (resp->irq == NULL) return (1); } - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/CS461x_BUFFSIZE, /*boundary*/CS461x_BUFFSIZE, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), + /*alignment*/CS461x_BUFFSIZE, + /*boundary*/CS461x_BUFFSIZE, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -775,9 +777,7 @@ struct ac97_info *codec; struct sndcard_func *func; - csa = malloc(sizeof(*csa), M_DEVBUF, M_NOWAIT | M_ZERO); - if (csa == NULL) - return (ENOMEM); + csa = malloc(sizeof(*csa), M_DEVBUF, M_WAITOK | M_ZERO); unit = device_get_unit(dev); func = device_get_ivars(dev); csa->binfo = func->varinfo; --- sys/dev/sound/pci/ds1.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/ds1.c Thu Jul 12 12:04:19 2007 @@ -33,7 +33,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ds1.c,v 1.43.2.1 2006/01/18 01:05:34 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ds1.c,v 1.52 2007/06/17 06:10:41 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -490,7 +490,7 @@ ch->fmt = AFMT_U8; ch->spd = 8000; ch->run = 0; - if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, 0, sc->bufsz) != 0) return NULL; else { ch->lsnum = sc->pslotfree; @@ -545,7 +545,7 @@ struct sc_info *sc = ch->parent; int stereo; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; stereo = (ch->fmt & AFMT_STEREO)? 1 : 0; if (go == PCMTRIG_START) { @@ -621,7 +621,7 @@ ch->dir = dir; ch->fmt = AFMT_U8; ch->spd = 8000; - if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->buffer_dmat, 0, sc->bufsz) != 0) return NULL; else { ch->slot = (ch->num == DS1_RECPRIMARY)? sc->rbank + 2: sc->rbank; @@ -673,7 +673,7 @@ struct sc_info *sc = ch->parent; u_int32_t x; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { ch->run = 1; @@ -833,7 +833,9 @@ memsz += (64 + 1) * 4; if (sc->regbase == NULL) { - if (bus_dma_tag_create(NULL, 2, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, + if (bus_dma_tag_create(bus_get_dma_tag(sc->dev), 2, 0, + BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, NULL, NULL, memsz, 1, memsz, 0, NULL, NULL, &sc->control_dmat)) return -1; @@ -941,12 +943,8 @@ struct ac97_info *codec = NULL; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ds1 softc"); sc->dev = dev; subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); sc->type = ds_finddev(pci_get_devid(dev), subdev); @@ -970,7 +968,8 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, DS1_BUFFSIZE, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/emu10k1.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/emu10k1.c Thu Jul 12 12:04:19 2007 @@ -28,14 +28,16 @@ #include #include -#include #include "emu10k1-alsa%diked.h" #include #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/emu10k1.c,v 1.55.2.1 2005/12/30 19:55:53 netchild Exp $"); +#include +#include "mpufoi_if.h" + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/emu10k1.c,v 1.69 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -85,7 +87,7 @@ struct emu_voice { int vnum; - int b16:1, stereo:1, busy:1, running:1, ismaster:1; + unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; int speed; int start, end, vol; int fxrt1; /* FX routing */ @@ -137,6 +139,9 @@ struct emu_voice voice[64]; struct sc_pchinfo pch[EMU_MAX_CHANS]; struct sc_rchinfo rch[3]; + struct mpu401 *mpu; + mpu401_intr_t *mpu_intr; + int mputx; }; /* -------------------------------------------------------------------- */ @@ -272,7 +277,7 @@ static void emu_wrefx(struct sc_info *sc, unsigned int pc, unsigned int data) { - pc += sc->audigy ? AUDIGY_CODEBASE : MICROCODEBASE; + pc += sc->audigy ? A_MICROCODEBASE : MICROCODEBASE; emu_wrptr(sc, 0, pc, data); } @@ -790,7 +795,7 @@ struct sc_pchinfo *ch = data; struct sc_info *sc = ch->parent; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; snd_mtxlock(sc->lock); @@ -890,7 +895,7 @@ break; } sc->rnum++; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; else { snd_mtxlock(sc->lock); @@ -953,6 +958,9 @@ struct sc_info *sc = ch->parent; u_int32_t val, sz; + if (!PCMTRIG_COMMON(go)) + return 0; + switch(sc->bufsz) { case 4096: sz = ADCBS_BUFSIZE_4096; @@ -1059,8 +1067,65 @@ }; CHANNEL_DECLARE(emurchan); +static unsigned char +emu_mread(void *arg, struct sc_info *sc, int reg) +{ + unsigned int d; + + d = emu_rd(sc, 0x18 + reg, 1); + return d; +} + +static void +emu_mwrite(void *arg, struct sc_info *sc, int reg, unsigned char b) +{ + + emu_wr(sc, 0x18 + reg, b, 1); +} + +static int +emu_muninit(void *arg, struct sc_info *sc) +{ + + snd_mtxlock(sc->lock); + sc->mpu_intr = 0; + snd_mtxunlock(sc->lock); + + return 0; +} + +static kobj_method_t emu_mpu_methods[] = { + KOBJMETHOD(mpufoi_read, emu_mread), + KOBJMETHOD(mpufoi_write, emu_mwrite), + KOBJMETHOD(mpufoi_uninit, emu_muninit), + { 0, 0 } +}; + +static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); + +static void +emu_intr2(void *p) +{ + struct sc_info *sc = (struct sc_info *)p; + + if (sc->mpu_intr) + (sc->mpu_intr)(sc->mpu); +} + +static void +emu_midiattach(struct sc_info *sc) +{ + int i; + + i = emu_rd(sc, INTE, 4); + i |= INTE_MIDIRXENABLE; + emu_wr(sc, INTE, i, 4); + + sc->mpu = mpu401_init(&emu_mpu_class, sc, emu_intr2, &sc->mpu_intr); +} /* -------------------------------------------------------------------- */ /* The interrupt handler */ + static void emu_intr(void *data) { @@ -1100,6 +1165,11 @@ #endif } + if (stat & IPR_MIDIRECVBUFEMPTY) + if (sc->mpu_intr) { + (sc->mpu_intr)(sc->mpu); + ack |= IPR_MIDIRECVBUFEMPTY | IPR_MIDITRANSBUFEMPTY; + } if (stat & ~ack) device_printf(sc->dev, "dodgy irq: %x (harmless)\n", stat & ~ack); @@ -1312,12 +1382,12 @@ /* Audigy 2 (EMU10K2) DSP Registers: FX Bus - 0x000-0x00f : 16 registers (???) + 0x000-0x00f : 16 registers (?) Input 0x040/0x041 : AC97 Codec (l/r) 0x042/0x043 : ADC, S/PDIF (l/r) 0x044/0x045 : Optical S/PDIF in (l/r) - 0x046/0x047 : ??? + 0x046/0x047 : ? 0x048/0x049 : Line/Mic 2 (l/r) 0x04a/0x04b : RCA S/PDIF (l/r) 0x04c/0x04d : Aux 2 (l/r) @@ -1328,11 +1398,11 @@ 0x066/0x067 : Digital Rear (l/r) 0x068/0x069 : Analog Front (l/r) 0x06a/0x06b : Analog Center/LFE - 0x06c/0x06d : ??? + 0x06c/0x06d : ? 0x06e/0x06f : Analog Rear (l/r) 0x070/0x071 : AC97 Output (l/r) - 0x072/0x073 : ??? - 0x074/0x075 : ??? + 0x072/0x073 : ? + 0x074/0x075 : ? 0x076/0x077 : ADC Recording Buffer (l/r) Constants 0x0c0 - 0x0c4 = 0 - 4 @@ -1341,9 +1411,9 @@ 0x0cb = 0x10000000, 0x0cc = 0x20000000, 0x0cd = 0x40000000 0x0ce = 0x80000000, 0x0cf = 0x7fffffff, 0x0d0 = 0xffffffff 0x0d1 = 0xfffffffe, 0x0d2 = 0xc0000000, 0x0d3 = 0x41fbbcdc - 0x0d4 = 0x5a7ef9db, 0x0d5 = 0x00100000, 0x0dc = 0x00000001 (???) + 0x0d4 = 0x5a7ef9db, 0x0d5 = 0x00100000, 0x0dc = 0x00000001 (?) Temporary Values - 0x0d6 : Accumulator (???) + 0x0d6 : Accumulator (?) 0x0d7 : Condition Register 0x0d8 : Noise source 0x0d9 : Noise source @@ -1560,10 +1630,10 @@ emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_ALFE), C_00000000, GPR(1), GPR(2), &pc); /* Digital Center = GPR[0] + GPR[2] */ - emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_CENTER), C_00000000, + emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_CENTER), C_00000000, GPR(0), GPR(2), &pc); /* Digital Sub = GPR[1] + GPR[2] */ - emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_LFE), C_00000000, + emu_addefxop(sc, iACC3, EXTOUT(EXTOUT_AC97_LFE), C_00000000, GPR(1), GPR(2), &pc); /* Headphones[l/r] = GPR[0/1] */ @@ -1871,6 +1941,8 @@ emu_free(sc, sc->mem.ptb_pages); emu_free(sc, sc->mem.silent_page); + if(sc->mpu) + mpu401_uninit(sc->mpu); return 0; } @@ -1900,7 +1972,7 @@ } device_set_desc(dev, s); - return BUS_PROBE_DEFAULT; + return BUS_PROBE_LOW_PRIORITY; } static int @@ -1912,12 +1984,8 @@ int i, gotmic; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10k1 softc"); sc->dev = dev; sc->type = pci_get_devid(dev); sc->rev = pci_get_revid(dev); @@ -1942,7 +2010,8 @@ sc->bufsz = pcm_getbuffersize(dev, 4096, EMU_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/1 << 31, /* can only access 0-2gb */ /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -1963,6 +2032,8 @@ gotmic = (ac97_getcaps(codec) & AC97_CAP_MICCHANNEL) ? 1 : 0; if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto bad; + emu_midiattach(sc); + i = 0; sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); @@ -2041,6 +2112,7 @@ DRIVER_MODULE(snd_emu10k1, cardbus, emu_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_emu10k1, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_emu10k1, 1); +MODULE_DEPEND(snd_emu10k1, midi, 1, 1, 1); /* dummy driver to silence the joystick device */ static int --- sys/dev/sound/pci/emu10kx-midi.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx-midi.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,250 @@ +/*- + * Copyright (c) 1999 Seigo Tanimura + * (c) 2003 Mathew Kanner + * Copyright (c) 2003-2006 Yuriy Tsibizov + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx-midi.c,v 1.3 2006/07/16 20:10:08 netchild Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include "mpufoi_if.h" + +#include "opt_emu10kx.h" +#include +#include "emu10k1-alsa%diked.h" + +struct emu_midi_softc { + struct mtx mtx; + device_t dev; + struct mpu401 *mpu; + mpu401_intr_t *mpu_intr; + struct emu_sc_info *card; + int port; /* I/O port or I/O ptr reg */ + int is_emu10k1; + int fflags; /* File flags */ + int ihandle; /* interrupt manager handle */ +}; + +static uint32_t emu_midi_card_intr(void *p, uint32_t arg); +static devclass_t emu_midi_devclass; + +static unsigned char +emu_mread(void *arg __unused, struct emu_midi_softc *sc, int reg) +{ + unsigned int d; + + d = 0; + if (sc->is_emu10k1) + d = emu_rd(sc->card, 0x18 + reg, 1); + else + d = emu_rdptr(sc->card, 0, sc->port + reg); + + return (d); +} + +static void +emu_mwrite(void *arg __unused, struct emu_midi_softc *sc, int reg, unsigned char b) +{ + + if (sc->is_emu10k1) + emu_wr(sc->card, 0x18 + reg, b, 1); + else + emu_wrptr(sc->card, 0, sc->port + reg, b); +} + +static int +emu_muninit(void *arg __unused, struct emu_midi_softc *sc) +{ + + mtx_lock(&sc->mtx); + sc->mpu_intr = NULL; + mtx_unlock(&sc->mtx); + + return (0); +} + +static kobj_method_t emu_mpu_methods[] = { + KOBJMETHOD(mpufoi_read, emu_mread), + KOBJMETHOD(mpufoi_write, emu_mwrite), + KOBJMETHOD(mpufoi_uninit, emu_muninit), + {0, 0} +}; +static DEFINE_CLASS(emu_mpu, emu_mpu_methods, 0); + +static uint32_t +emu_midi_card_intr(void *p, uint32_t intr_status) +{ + struct emu_midi_softc *sc = (struct emu_midi_softc *)p; + if (sc->mpu_intr) + (sc->mpu_intr) (sc->mpu); + if (sc->mpu_intr == NULL) { + /* We should read MIDI event to unlock card after + * interrupt. XXX - check, why this happens. */ +#ifdef SND_EMU10KX_DEBUG + device_printf(sc->dev, "midi interrupt %08x without interrupt handler, force mread!\n", intr_status); +#endif + (void)emu_mread((void *)(NULL), sc, 0); + } + return (intr_status); /* Acknowledge everything */ +} + +static void +emu_midi_intr(void *p) +{ + (void)emu_midi_card_intr(p, 0); +} + +static int +emu_midi_probe(device_t dev) +{ + struct emu_midi_softc *scp; + uintptr_t func, r, is_emu10k1; + + r = BUS_READ_IVAR(device_get_parent(dev), dev, 0, &func); + if (func != SCF_MIDI) + return (ENXIO); + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &is_emu10k1); + scp->is_emu10k1 = is_emu10k1 ? 1 : 0; + + device_set_desc(dev, "EMU10Kx MIDI Interface"); + return (0); +} + +static int +emu_midi_attach(device_t dev) +{ + struct emu_midi_softc * scp; + struct sndcard_func *func; + struct emu_midiinfo *midiinfo; + uint32_t inte_val, ipr_val; + + scp = device_get_softc(dev); + func = device_get_ivars(dev); + + scp->dev = dev; + midiinfo = (struct emu_midiinfo *)func->varinfo; + scp->port = midiinfo->port; + scp->card = midiinfo->card; + + mtx_init(&scp->mtx, "emu10kx_midi", NULL, MTX_DEF); + + if (scp->is_emu10k1) { + /* SB Live! - only one MIDI device here */ + inte_val = 0; + /* inte_val |= INTE_MIDITXENABLE;*/ + inte_val |= INTE_MIDIRXENABLE; + ipr_val = IPR_MIDITRANSBUFEMPTY; + ipr_val |= IPR_MIDIRECVBUFEMPTY; + } else { + if (scp->port == A_MUDATA1) { + /* EXTERNAL MIDI (AudigyDrive) */ + inte_val = 0; + /* inte_val |= A_INTE_MIDITXENABLE1;*/ + inte_val |= INTE_MIDIRXENABLE; + ipr_val = IPR_MIDITRANSBUFEMPTY; + ipr_val |= IPR_MIDIRECVBUFEMPTY; + } else { + /* MIDI hw config port 2 */ + inte_val = 0; + /* inte_val |= A_INTE_MIDITXENABLE2;*/ + inte_val |= INTE_A_MIDIRXENABLE2; + ipr_val = IPR_A_MIDITRANSBUFEMPTY2; + ipr_val |= IPR_A_MIDIRECVBUFEMPTY2; + } + } + if (inte_val == 0) + return (ENXIO); + + scp->ihandle = emu_intr_register(scp->card, inte_val, ipr_val, &emu_midi_card_intr, scp); + /* Init the interface. */ + scp->mpu = mpu401_init(&emu_mpu_class, scp, emu_midi_intr, &scp->mpu_intr); + if (scp->mpu == NULL) { + emu_intr_unregister(scp->card, scp->ihandle); + mtx_destroy(&scp->mtx); + return (ENOMEM); + } + /* + * XXX I don't know how to check for Live!Drive / AudigyDrive + * presence. Let's hope that IR enabling code will not harm if + * it is not present. + */ + if (scp->is_emu10k1) + emu_enable_ir(scp->card); + else { + if (scp->port == A_MUDATA1) + emu_enable_ir(scp->card); + } + + return (0); +} + + +static int +emu_midi_detach(device_t dev) +{ + struct emu_midi_softc *scp; + + scp = device_get_softc(dev); + mpu401_uninit(scp->mpu); + emu_intr_unregister(scp->card, scp->ihandle); + mtx_destroy(&scp->mtx); + return (0); +} + +static device_method_t emu_midi_methods[] = { + DEVMETHOD(device_probe, emu_midi_probe), + DEVMETHOD(device_attach, emu_midi_attach), + DEVMETHOD(device_detach, emu_midi_detach), + + {0, 0}, +}; + +static driver_t emu_midi_driver = { + "midi", + emu_midi_methods, + sizeof(struct emu_midi_softc), +}; +DRIVER_MODULE(snd_emu10kx_midi, emu10kx, emu_midi_driver, emu_midi_devclass, 0, 0); +MODULE_DEPEND(snd_emu10kx_midi, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); +MODULE_DEPEND(snd_emu10kx_midi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_emu10kx_midi, SND_EMU10KX_PREFVER); --- sys/dev/sound/pci/emu10kx-pcm.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx-pcm.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,1190 @@ +/*- + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003-2006 Yuriy Tsibizov + * 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 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 AUTHOR 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, WHETHERIN 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. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx-pcm.c,v 1.10 2007/06/17 06:10:42 ariff Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mixer_if.h" + +#include "opt_emu10kx.h" +#include +#include "emu10k1-alsa%diked.h" + +struct emu_pcm_pchinfo { + int spd; + int fmt; + int blksz; + int run; + struct emu_voice *master; + struct emu_voice *slave; + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct emu_pcm_info *pcm; + int timer; +}; + +struct emu_pcm_rchinfo { + int spd; + int fmt; + int blksz; + int run; + uint32_t idxreg; + uint32_t basereg; + uint32_t sizereg; + uint32_t setupreg; + uint32_t irqmask; + uint32_t iprmask; + int ihandle; + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct emu_pcm_info *pcm; +}; + +/* XXX Hardware playback channels */ +#define MAX_CHANNELS 4 + +#if MAX_CHANNELS > 13 +#error Too many hardware channels defined. 13 is the maximum +#endif + +struct emu_pcm_info { + struct mtx *lock; + device_t dev; /* device information */ + struct snddev_info *devinfo; /* pcm device information */ + struct emu_sc_info *card; + struct emu_pcm_pchinfo pch[MAX_CHANNELS]; /* hardware channels */ + int pnum; /* next free channel number */ + struct emu_pcm_rchinfo rch_adc; + struct emu_pcm_rchinfo rch_efx; + struct emu_route rt; + struct emu_route rt_mono; + int route; + int ihandle; /* interrupt handler */ + unsigned int bufsz; + int is_emu10k1; + struct ac97_info *codec; + uint32_t ac97_state[0x7F]; +}; + + +static uint32_t emu_rfmt_adc[] = { + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; +static struct pcmchan_caps emu_reccaps_adc = { + 8000, 48000, emu_rfmt_adc, 0 +}; + +static uint32_t emu_rfmt_efx[] = { + AFMT_S16_LE, + 0 +}; + +static struct pcmchan_caps emu_reccaps_efx_live = { + 48000*32, 48000*32, emu_rfmt_efx, 0 +}; + +static struct pcmchan_caps emu_reccaps_efx_audigy = { + 48000*64, 48000*64, emu_rfmt_efx, 0 +}; + +static uint32_t emu_pfmt[] = { + AFMT_U8, + AFMT_STEREO | AFMT_U8, + AFMT_S16_LE, + AFMT_STEREO | AFMT_S16_LE, + 0 +}; +static uint32_t emu_pfmt_mono[] = { + AFMT_U8, + AFMT_S16_LE, + 0 +}; + +static struct pcmchan_caps emu_playcaps = {4000, 48000, emu_pfmt, 0}; +static struct pcmchan_caps emu_playcaps_mono = {4000, 48000, emu_pfmt_mono, 0}; + +static int emu10k1_adcspeed[8] = {48000, 44100, 32000, 24000, 22050, 16000, 11025, 8000}; +/* audigy supports 12kHz. */ +static int emu10k2_adcspeed[9] = {48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000}; + +static uint32_t emu_pcm_intr(void *pcm, uint32_t stat); + +static const struct emu_dspmix_props { + u_int8_t present; +} dspmix [SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_VOLUME] = {1}, + [SOUND_MIXER_PCM] = {1}, +}; + +static int +emu_dspmixer_init(struct snd_mixer *m) +{ + int i; + int v; + + v = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (dspmix[i].present) + v |= 1 << i; + } + mix_setdevs(m, v); + + mix_setrecdevs(m, 0); + return (0); +} + +static int +emu_dspmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct emu_pcm_info *sc; + + sc = mix_getdevinfo(m); + + switch (dev) { + case SOUND_MIXER_VOLUME: + switch (sc->route) { + case RT_REAR: + emumix_set_volume(sc->card, M_MASTER_REAR_L, left); + emumix_set_volume(sc->card, M_MASTER_REAR_R, right); + break; + case RT_CENTER: + emumix_set_volume(sc->card, M_MASTER_CENTER, (left+right)/2); + break; + case RT_SUB: + emumix_set_volume(sc->card, M_MASTER_SUBWOOFER, (left+right)/2); + break; + } + break; + case SOUND_MIXER_PCM: + switch (sc->route) { + case RT_REAR: + emumix_set_volume(sc->card, M_FX2_REAR_L, left); + emumix_set_volume(sc->card, M_FX3_REAR_R, right); + break; + case RT_CENTER: + emumix_set_volume(sc->card, M_FX4_CENTER, (left+right)/2); + break; + case RT_SUB: + emumix_set_volume(sc->card, M_FX5_SUBWOOFER, (left+right)/2); + break; + } + break; + default: + device_printf(sc->dev, "mixer error: unknown device %d\n", dev); + } + return (0); +} + +static int +emu_dspmixer_setrecsrc(struct snd_mixer *m __unused, u_int32_t src __unused) +{ + return (0); +} + +static kobj_method_t emudspmixer_methods[] = { + KOBJMETHOD(mixer_init, emu_dspmixer_init), + KOBJMETHOD(mixer_set, emu_dspmixer_set), + KOBJMETHOD(mixer_setrecsrc, emu_dspmixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(emudspmixer); + +/* + * AC97 emulation code for Audigy and later cards. + * Some parts of AC97 codec are not used by hardware, but can be used + * to change some DSP controls via AC97 mixer interface. This includes: + * - master volume controls MASTER_FRONT_[R|L] + * - pcm volume controls FX[0|1]_FRONT_[R|L] + * - rec volume controls MASTER_REC_[R|L] + * We do it because we need to put it under user control.... + * We also keep some parts of AC97 disabled to get better sound quality + */ + +#define AC97LEFT(x) ((x & 0x7F00)>>8) +#define AC97RIGHT(x) (x & 0x007F) +#define AC97MUTE(x) ((x & 0x8000)>>15) +#define BIT4_TO100(x) (100-(x)*100/(0x0f)) +#define BIT6_TO100(x) (100-(x)*100/(0x3f)) +#define BIT4_TO255(x) (255-(x)*255/(0x0f)) +#define BIT6_TO255(x) (255-(x)*255/(0x3f)) +#define V100_TOBIT6(x) (0x3f*(100-x)/100) +#define V100_TOBIT4(x) (0x0f*(100-x)/100) +#define AC97ENCODE(x_muted,x_left,x_right) (((x_muted&1)<<15) | ((x_left&0x3f)<<8) | (x_right&0x3f)) + +static int +emu_ac97_read_emulation(struct emu_pcm_info *sc, int regno) +{ + int use_ac97; + int emulated; + int tmp; + + use_ac97 = 1; + emulated = 0; + + switch (regno) { + case AC97_MIX_MASTER: + emulated = sc->ac97_state[AC97_MIX_MASTER]; + use_ac97 = 0; + break; + case AC97_MIX_PCM: + emulated = sc->ac97_state[AC97_MIX_PCM]; + use_ac97 = 0; + break; + case AC97_REG_RECSEL: + emulated = 0x0505; + use_ac97 = 0; + break; + case AC97_MIX_RGAIN: + emulated = sc->ac97_state[AC97_MIX_RGAIN]; + use_ac97 = 0; + break; + } + + emu_wr(sc->card, AC97ADDRESS, regno, 1); + tmp = emu_rd(sc->card, AC97DATA, 2); + + if (use_ac97) + emulated = tmp; + + return (emulated); +} + +static void +emu_ac97_write_emulation(struct emu_pcm_info *sc, int regno, uint32_t data) +{ + int write_ac97; + int left, right; + uint32_t emu_left, emu_right; + int is_mute; + + write_ac97 = 1; + + left = AC97LEFT(data); + emu_left = BIT6_TO100(left); /* We show us as 6-bit AC97 mixer */ + right = AC97RIGHT(data); + emu_right = BIT6_TO100(right); + is_mute = AC97MUTE(data); + if (is_mute) + emu_left = emu_right = 0; + + switch (regno) { + /* TODO: reset emulator on AC97_RESET */ + case AC97_MIX_MASTER: + emumix_set_volume(sc->card, M_MASTER_FRONT_L, emu_left); + emumix_set_volume(sc->card, M_MASTER_FRONT_R, emu_right); + sc->ac97_state[AC97_MIX_MASTER] = data & (0x8000 | 0x3f3f); + data = 0x8000; /* Mute AC97 main out */ + break; + case AC97_MIX_PCM: /* PCM OUT VOL */ + emumix_set_volume(sc->card, M_FX0_FRONT_L, emu_left); + emumix_set_volume(sc->card, M_FX1_FRONT_R, emu_right); + sc->ac97_state[AC97_MIX_PCM] = data & (0x8000 | 0x3f3f); + data = 0x8000; /* Mute AC97 PCM out */ + break; + case AC97_REG_RECSEL: + /* + * PCM recording source is set to "stereo mix" (labeled "vol" + * in mixer) XXX !I can't remember why! + */ + data = 0x0505; + break; + case AC97_MIX_RGAIN: /* RECORD GAIN */ + emu_left = BIT4_TO100(left); /* rgain is 4-bit */ + emu_right = BIT4_TO100(right); + emumix_set_volume(sc->card, M_MASTER_REC_L, 100-emu_left); + emumix_set_volume(sc->card, M_MASTER_REC_R, 100-emu_right); + /* + * Record gain on AC97 should stay zero to get AC97 sound on + * AC97_[RL] connectors on EMU10K2 chip. AC97 on Audigy is not + * directly connected to any output, only to EMU10K2 chip Use + * this control to set AC97 mix volume inside EMU10K2 chip + */ + sc->ac97_state[AC97_MIX_RGAIN] = data & (0x8000 | 0x0f0f); + data = 0x0000; + break; + } + if (write_ac97) { + emu_wr(sc->card, AC97ADDRESS, regno, 1); + emu_wr(sc->card, AC97DATA, data, 2); + } +} + +static int +emu_erdcd(kobj_t obj __unused, void *devinfo, int regno) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + return (emu_ac97_read_emulation(sc, regno)); +} + +static int +emu_ewrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + emu_ac97_write_emulation(sc, regno, data); + return (0); +} + +static kobj_method_t emu_eac97_methods[] = { + KOBJMETHOD(ac97_read, emu_erdcd), + KOBJMETHOD(ac97_write, emu_ewrcd), + {0, 0} +}; +AC97_DECLARE(emu_eac97); + +/* real ac97 codec */ +static int +emu_rdcd(kobj_t obj __unused, void *devinfo, int regno) +{ + int rd; + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + KASSERT(sc->card != NULL, ("emu_rdcd: no soundcard")); + emu_wr(sc->card, AC97ADDRESS, regno, 1); + rd = emu_rd(sc->card, AC97DATA, 2); + return (rd); +} + +static int +emu_wrcd(kobj_t obj __unused, void *devinfo, int regno, uint32_t data) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)devinfo; + + KASSERT(sc->card != NULL, ("emu_wrcd: no soundcard")); + emu_wr(sc->card, AC97ADDRESS, regno, 1); + emu_wr(sc->card, AC97DATA, data, 2); + return (0); +} + +static kobj_method_t emu_ac97_methods[] = { + KOBJMETHOD(ac97_read, emu_rdcd), + KOBJMETHOD(ac97_write, emu_wrcd), + {0, 0} +}; +AC97_DECLARE(emu_ac97); + + +static int +emu_k1_recval(int speed) +{ + int val; + + val = 0; + while ((val < 7) && (speed < emu10k1_adcspeed[val])) + val++; + if (val == 6) val=5; /* XXX 8kHz does not work */ + return (val); +} + +static int +emu_k2_recval(int speed) +{ + int val; + + val = 0; + while ((val < 8) && (speed < emu10k2_adcspeed[val])) + val++; + if (val == 7) val=6; /* XXX 8kHz does not work */ + return (val); +} + +static void * +emupchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) +{ + struct emu_pcm_info *sc = devinfo; + struct emu_pcm_pchinfo *ch; + void *r; + + KASSERT(dir == PCMDIR_PLAY, ("emupchan_init: bad direction")); + KASSERT(sc->card != NULL, ("empchan_init: no soundcard")); + + + if (sc->pnum >= MAX_CHANNELS) + return (NULL); + ch = &(sc->pch[sc->pnum++]); + ch->buffer = b; + ch->pcm = sc; + ch->channel = c; + ch->blksz = sc->bufsz; + ch->fmt = AFMT_U8; + ch->spd = 8000; + ch->master = emu_valloc(sc->card); + /* + * XXX we have to allocate slave even for mono channel until we + * fix emu_vfree to handle this case. + */ + ch->slave = emu_valloc(sc->card); + ch->timer = emu_timer_create(sc->card); + r = (emu_vinit(sc->card, ch->master, ch->slave, EMU_PLAY_BUFSZ, ch->buffer)) ? NULL : ch; + return (r); +} + +static int +emupchan_free(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + emu_timer_clear(sc->card, ch->timer); + if (ch->slave != NULL) + emu_vfree(sc->card, ch->slave); + emu_vfree(sc->card, ch->master); + return (0); +} + +static int +emupchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + + ch->fmt = format; + return (0); +} + +static int +emupchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + + ch->spd = speed; + return (ch->spd); +} + +static int +emupchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + if (blocksize > ch->pcm->bufsz) + blocksize = ch->pcm->bufsz; + snd_mtxlock(sc->lock); + ch->blksz = blocksize; + emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer)); + snd_mtxunlock(sc->lock); + return (blocksize); +} + +static int +emupchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + if (!PCMTRIG_COMMON(go)) + return (0); + + snd_mtxlock(sc->lock); /* XXX can we trigger on parallel threads ? */ + if (go == PCMTRIG_START) { + emu_vsetup(ch->master, ch->fmt, ch->spd); + if ((ch->fmt & AFMT_STEREO) == AFMT_STEREO) + emu_vroute(sc->card, &(sc->rt), ch->master); + else + emu_vroute(sc->card, &(sc->rt_mono), ch->master); + emu_vwrite(sc->card, ch->master); + emu_timer_set(sc->card, ch->timer, ch->blksz / sndbuf_getbps(ch->buffer)); + emu_timer_enable(sc->card, ch->timer, 1); + } + /* PCM interrupt handler will handle PCMTRIG_STOP event */ + ch->run = (go == PCMTRIG_START) ? 1 : 0; + emu_vtrigger(sc->card, ch->master, ch->run); + snd_mtxunlock(sc->lock); + return (0); +} + +static int +emupchan_getptr(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + int r; + + r = emu_vpos(sc->card, ch->master); + + return (r); +} + +static struct pcmchan_caps * +emupchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) +{ + struct emu_pcm_pchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + switch (sc->route) { + case RT_FRONT: + /* FALLTHROUGH */ + case RT_REAR: + /* FALLTHROUGH */ + case RT_SIDE: + return (&emu_playcaps); + break; + case RT_CENTER: + /* FALLTHROUGH */ + case RT_SUB: + return (&emu_playcaps_mono); + break; + } + return (NULL); +} + +static kobj_method_t emupchan_methods[] = { + KOBJMETHOD(channel_init, emupchan_init), + KOBJMETHOD(channel_free, emupchan_free), + KOBJMETHOD(channel_setformat, emupchan_setformat), + KOBJMETHOD(channel_setspeed, emupchan_setspeed), + KOBJMETHOD(channel_setblocksize, emupchan_setblocksize), + KOBJMETHOD(channel_trigger, emupchan_trigger), + KOBJMETHOD(channel_getptr, emupchan_getptr), + KOBJMETHOD(channel_getcaps, emupchan_getcaps), + {0, 0} +}; +CHANNEL_DECLARE(emupchan); + +static void * +emurchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) +{ + struct emu_pcm_info *sc = devinfo; + struct emu_pcm_rchinfo *ch; + + KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); + ch = &sc->rch_adc; + ch->buffer = b; + ch->pcm = sc; + ch->channel = c; + ch->blksz = sc->bufsz / 2; /* We rise interrupt for half-full buffer */ + ch->fmt = AFMT_U8; + ch->spd = 8000; + ch->idxreg = sc->is_emu10k1 ? ADCIDX : A_ADCIDX; + ch->basereg = ADCBA; + ch->sizereg = ADCBS; + ch->setupreg = ADCCR; + ch->irqmask = INTE_ADCBUFENABLE; + ch->iprmask = IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL; + + if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) + return (NULL); + else { + emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); + emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ + return (ch); + } +} + +static int +emurchan_setformat(kobj_t obj __unused, void *c_devinfo, uint32_t format) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + ch->fmt = format; + return (0); +} + +static int +emurchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + if (ch->pcm->is_emu10k1) { + speed = emu10k1_adcspeed[emu_k1_recval(speed)]; + } else { + speed = emu10k2_adcspeed[emu_k2_recval(speed)]; + } + ch->spd = speed; + return (ch->spd); +} + +static int +emurchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + ch->blksz = blocksize; + /* If blocksize is less than half of buffer size we will not get + interrupt in time and channel will die due to interrupt timeout */ + if(ch->blksz < (ch->pcm->bufsz / 2)) + ch->blksz = ch->pcm->bufsz / 2; + return (ch->blksz); +} + +static int +emurchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + uint32_t val, sz; + + if (!PCMTRIG_COMMON(go)) + return (0); + + switch (sc->bufsz) { + case 4096: + sz = ADCBS_BUFSIZE_4096; + break; + case 8192: + sz = ADCBS_BUFSIZE_8192; + break; + case 16384: + sz = ADCBS_BUFSIZE_16384; + break; + case 32768: + sz = ADCBS_BUFSIZE_32768; + break; + case 65536: + sz = ADCBS_BUFSIZE_65536; + break; + default: + sz = ADCBS_BUFSIZE_4096; + } + + snd_mtxlock(sc->lock); + switch (go) { + case PCMTRIG_START: + ch->run = 1; + emu_wrptr(sc->card, 0, ch->sizereg, sz); + val = sc->is_emu10k1 ? ADCCR_LCHANENABLE : A_ADCCR_LCHANENABLE; + if (ch->fmt & AFMT_STEREO) + val |= sc->is_emu10k1 ? ADCCR_RCHANENABLE : A_ADCCR_RCHANENABLE; + val |= sc->is_emu10k1 ? emu_k1_recval(ch->spd) : emu_k2_recval(ch->spd); + emu_wrptr(sc->card, 0, ch->setupreg, 0); + emu_wrptr(sc->card, 0, ch->setupreg, val); + ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); + break; + case PCMTRIG_STOP: + /* FALLTHROUGH */ + case PCMTRIG_ABORT: + ch->run = 0; + emu_wrptr(sc->card, 0, ch->sizereg, 0); + if (ch->setupreg) + emu_wrptr(sc->card, 0, ch->setupreg, 0); + (void)emu_intr_unregister(sc->card, ch->ihandle); + break; + case PCMTRIG_EMLDMAWR: + /* FALLTHROUGH */ + case PCMTRIG_EMLDMARD: + /* FALLTHROUGH */ + default: + break; + } + snd_mtxunlock(sc->lock); + + return (0); +} + +static int +emurchan_getptr(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + int r; + + r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; + + return (r); +} + +static struct pcmchan_caps * +emurchan_getcaps(kobj_t obj __unused, void *c_devinfo __unused) +{ + return (&emu_reccaps_adc); +} + +static kobj_method_t emurchan_methods[] = { + KOBJMETHOD(channel_init, emurchan_init), + KOBJMETHOD(channel_setformat, emurchan_setformat), + KOBJMETHOD(channel_setspeed, emurchan_setspeed), + KOBJMETHOD(channel_setblocksize, emurchan_setblocksize), + KOBJMETHOD(channel_trigger, emurchan_trigger), + KOBJMETHOD(channel_getptr, emurchan_getptr), + KOBJMETHOD(channel_getcaps, emurchan_getcaps), + {0, 0} +}; +CHANNEL_DECLARE(emurchan); + +static void * +emufxrchan_init(kobj_t obj __unused, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir __unused) +{ + struct emu_pcm_info *sc = devinfo; + struct emu_pcm_rchinfo *ch; + + KASSERT(dir == PCMDIR_REC, ("emurchan_init: bad direction")); + + if (sc == NULL) return (NULL); + + ch = &(sc->rch_efx); + ch->fmt = AFMT_S16_LE; + ch->spd = sc->is_emu10k1 ? 48000*32 : 48000 * 64; + ch->idxreg = FXIDX; + ch->basereg = FXBA; + ch->sizereg = FXBS; + ch->irqmask = INTE_EFXBUFENABLE; + ch->iprmask = IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL; + ch->buffer = b; + ch->pcm = sc; + ch->channel = c; + ch->blksz = sc->bufsz; + + if (sndbuf_alloc(ch->buffer, emu_gettag(sc->card), 0, sc->bufsz) != 0) + return (NULL); + else { + emu_wrptr(sc->card, 0, ch->basereg, sndbuf_getbufaddr(ch->buffer)); + emu_wrptr(sc->card, 0, ch->sizereg, 0); /* off */ + return (ch); + } +} + +static int +emufxrchan_setformat(kobj_t obj __unused, void *c_devinfo __unused, uint32_t format) +{ + if (format == AFMT_S16_LE) return (0); + return (-1); +} + +static int +emufxrchan_setspeed(kobj_t obj __unused, void *c_devinfo, uint32_t speed) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + /* FIXED RATE CHANNEL */ + return (ch->spd); +} + +static int +emufxrchan_setblocksize(kobj_t obj __unused, void *c_devinfo, uint32_t blocksize) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + + ch->blksz = blocksize; + /* If blocksize is less than half of buffer size we will not get + interrupt in time and channel will die due to interrupt timeout */ + if(ch->blksz < (ch->pcm->bufsz / 2)) + ch->blksz = ch->pcm->bufsz / 2; + return (ch->blksz); +} + +static int +emufxrchan_trigger(kobj_t obj __unused, void *c_devinfo, int go) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + uint32_t sz; + + if (!PCMTRIG_COMMON(go)) + return (0); + + switch (sc->bufsz) { + case 4096: + sz = ADCBS_BUFSIZE_4096; + break; + case 8192: + sz = ADCBS_BUFSIZE_8192; + break; + case 16384: + sz = ADCBS_BUFSIZE_16384; + break; + case 32768: + sz = ADCBS_BUFSIZE_32768; + break; + case 65536: + sz = ADCBS_BUFSIZE_65536; + break; + default: + sz = ADCBS_BUFSIZE_4096; + } + + snd_mtxlock(sc->lock); + switch (go) { + case PCMTRIG_START: + ch->run = 1; + emu_wrptr(sc->card, 0, ch->sizereg, sz); + ch->ihandle = emu_intr_register(sc->card, ch->irqmask, ch->iprmask, &emu_pcm_intr, sc); + /* + SB Live! is limited to 32 mono channels. Audigy + has 64 mono channels, each of them is selected from + one of two A_FXWC[1|2] registers. + */ + /* XXX there is no way to demultiplex this streams for now */ + if(sc->is_emu10k1) { + emu_wrptr(sc->card, 0, FXWC, 0xffffffff); + } else { + emu_wrptr(sc->card, 0, A_FXWC1, 0xffffffff); + emu_wrptr(sc->card, 0, A_FXWC2, 0xffffffff); + } + break; + case PCMTRIG_STOP: + /* FALLTHROUGH */ + case PCMTRIG_ABORT: + ch->run = 0; + if(sc->is_emu10k1) { + emu_wrptr(sc->card, 0, FXWC, 0x0); + } else { + emu_wrptr(sc->card, 0, A_FXWC1, 0x0); + emu_wrptr(sc->card, 0, A_FXWC2, 0x0); + } + emu_wrptr(sc->card, 0, ch->sizereg, 0); + (void)emu_intr_unregister(sc->card, ch->ihandle); + break; + case PCMTRIG_EMLDMAWR: + /* FALLTHROUGH */ + case PCMTRIG_EMLDMARD: + /* FALLTHROUGH */ + default: + break; + } + snd_mtxunlock(sc->lock); + + return (0); +} + +static int +emufxrchan_getptr(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + int r; + + r = emu_rdptr(sc->card, 0, ch->idxreg) & 0x0000ffff; + + return (r); +} + +static struct pcmchan_caps * +emufxrchan_getcaps(kobj_t obj __unused, void *c_devinfo) +{ + struct emu_pcm_rchinfo *ch = c_devinfo; + struct emu_pcm_info *sc = ch->pcm; + + if(sc->is_emu10k1) + return (&emu_reccaps_efx_live); + return (&emu_reccaps_efx_audigy); + +} + +static kobj_method_t emufxrchan_methods[] = { + KOBJMETHOD(channel_init, emufxrchan_init), + KOBJMETHOD(channel_setformat, emufxrchan_setformat), + KOBJMETHOD(channel_setspeed, emufxrchan_setspeed), + KOBJMETHOD(channel_setblocksize, emufxrchan_setblocksize), + KOBJMETHOD(channel_trigger, emufxrchan_trigger), + KOBJMETHOD(channel_getptr, emufxrchan_getptr), + KOBJMETHOD(channel_getcaps, emufxrchan_getcaps), + {0, 0} +}; +CHANNEL_DECLARE(emufxrchan); + + +static uint32_t +emu_pcm_intr(void *pcm, uint32_t stat) +{ + struct emu_pcm_info *sc = (struct emu_pcm_info *)pcm; + uint32_t ack; + int i; + + ack = 0; + + if (stat & IPR_INTERVALTIMER) { + ack |= IPR_INTERVALTIMER; + for (i = 0; i < MAX_CHANNELS; i++) + if (sc->pch[i].channel) { + if (sc->pch[i].run == 1) + chn_intr(sc->pch[i].channel); + else + emu_timer_enable(sc->card, sc->pch[i].timer, 0); + } + } + + + if (stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL)) { + ack |= stat & (IPR_ADCBUFFULL | IPR_ADCBUFHALFFULL); + if (sc->rch_adc.channel) + chn_intr(sc->rch_adc.channel); + } + + if (stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL)) { + ack |= stat & (IPR_EFXBUFFULL | IPR_EFXBUFHALFFULL); + if (sc->rch_efx.channel) + chn_intr(sc->rch_efx.channel); + } + return (ack); +} + +static int +emu_pcm_init(struct emu_pcm_info *sc) +{ + sc->bufsz = pcm_getbuffersize(sc->dev, EMUPAGESIZE, EMU_REC_BUFSZ, EMU_MAX_BUFSZ); + return (0); +} + +static int +emu_pcm_uninit(struct emu_pcm_info *sc __unused) +{ + return (0); +} + +static int +emu_pcm_probe(device_t dev) +{ + uintptr_t func, route, r; + const char *rt; + char buffer[255]; + + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_FUNC, &func); + + if (func != SCF_PCM) + return (ENXIO); + + rt = "UNKNOWN"; + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); + switch (route) { + case RT_FRONT: + rt = "front"; + break; + case RT_REAR: + rt = "rear"; + break; + case RT_CENTER: + rt = "center"; + break; + case RT_SUB: + rt = "subwoofer"; + break; + case RT_SIDE: + rt = "side"; + break; + case RT_MCHRECORD: + rt = "multichannel recording"; + break; + } + + snprintf(buffer, 255, "EMU10Kx DSP %s PCM interface", rt); + device_set_desc_copy(dev, buffer); + return (0); +} + +static int +emu_pcm_attach(device_t dev) +{ + struct emu_pcm_info *sc; + unsigned int i; + char status[SND_STATUSLEN]; + uint32_t inte, ipr; + uintptr_t route, r, is_emu10k1; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->card = (struct emu_sc_info *)(device_get_softc(device_get_parent(dev))); + if (sc->card == NULL) { + device_printf(dev, "cannot get bridge conf\n"); + free(sc, M_DEVBUF); + return (ENXIO); + } + + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_emu10kx softc"); + sc->dev = dev; + + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ISEMU10K1, &is_emu10k1); + sc->is_emu10k1 = is_emu10k1 ? 1 : 0; + + sc->codec = NULL; + + for (i = 0; i < 8; i++) { + sc->rt.routing_left[i] = i; + sc->rt.amounts_left[i] = 0x00; + sc->rt.routing_right[i] = i; + sc->rt.amounts_right[i] = 0x00; + } + + for (i = 0; i < 8; i++) { + sc->rt_mono.routing_left[i] = i; + sc->rt_mono.amounts_left[i] = 0x00; + sc->rt_mono.routing_right[i] = i; + sc->rt_mono.amounts_right[i] = 0x00; + } + + r = BUS_READ_IVAR(device_get_parent(dev), dev, EMU_VAR_ROUTE, &route); + sc->route = route; + switch (route) { + case RT_FRONT: + sc->rt.amounts_left[0] = 0xff; + sc->rt.amounts_right[1] = 0xff; + sc->rt_mono.amounts_left[0] = 0xff; + sc->rt_mono.amounts_left[1] = 0xff; + if (sc->is_emu10k1) + sc->codec = AC97_CREATE(dev, sc, emu_ac97); + else + sc->codec = AC97_CREATE(dev, sc, emu_eac97); + if (sc->codec == NULL) { + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize DSP mixer\n"); + goto bad; + } + } else + if (mixer_init(dev, ac97_getmixerclass(), sc->codec) == -1) { + device_printf(dev, "can't initialize AC97 mixer!\n"); + goto bad; + } + break; + case RT_REAR: + sc->rt.amounts_left[2] = 0xff; + sc->rt.amounts_right[3] = 0xff; + sc->rt_mono.amounts_left[2] = 0xff; + sc->rt_mono.amounts_left[3] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_CENTER: + sc->rt.amounts_left[4] = 0xff; + sc->rt_mono.amounts_left[4] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_SUB: + sc->rt.amounts_left[5] = 0xff; + sc->rt_mono.amounts_left[5] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_SIDE: + sc->rt.amounts_left[6] = 0xff; + sc->rt.amounts_right[7] = 0xff; + sc->rt_mono.amounts_left[6] = 0xff; + sc->rt_mono.amounts_left[7] = 0xff; + if (mixer_init(dev, &emudspmixer_class, sc)) { + device_printf(dev, "failed to initialize mixer\n"); + goto bad; + } + break; + case RT_MCHRECORD: + /* XXX add mixer here */ + break; + default: + device_printf(dev, "invalid default route\n"); + goto bad; + } + + inte = INTE_INTERVALTIMERENB; + ipr = IPR_INTERVALTIMER; /* Used by playback */ + sc->ihandle = emu_intr_register(sc->card, inte, ipr, &emu_pcm_intr, sc); + + if (emu_pcm_init(sc) == -1) { + device_printf(dev, "unable to initialize PCM part of the card\n"); + goto bad; + } + + /* XXX we should better get number of available channels from parent */ + if (pcm_register(dev, sc, (route == RT_FRONT) ? MAX_CHANNELS : 1, (route == RT_FRONT) ? 1 : 0)) { + device_printf(dev, "can't register PCM channels!\n"); + goto bad; + } + sc->pnum = 0; + if (route != RT_MCHRECORD) + pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); + if (route == RT_FRONT) { + for (i = 1; i < MAX_CHANNELS; i++) + pcm_addchan(dev, PCMDIR_PLAY, &emupchan_class, sc); + pcm_addchan(dev, PCMDIR_REC, &emurchan_class, sc); + } + if (route == RT_MCHRECORD) + pcm_addchan(dev, PCMDIR_REC, &emufxrchan_class, sc); + + snprintf(status, SND_STATUSLEN, "on %s", device_get_nameunit(device_get_parent(dev))); + pcm_setstatus(dev, status); + + return (0); + +bad: + if (sc->codec) + ac97_destroy(sc->codec); + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_DEVBUF); + return (ENXIO); +} + +static int +emu_pcm_detach(device_t dev) +{ + int r; + struct emu_pcm_info *sc; + + sc = pcm_getdevinfo(dev); + + r = pcm_unregister(dev); + + if (r) return (r); + + emu_pcm_uninit(sc); + + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_DEVBUF); + + return (0); +} + +static device_method_t emu_pcm_methods[] = { + DEVMETHOD(device_probe, emu_pcm_probe), + DEVMETHOD(device_attach, emu_pcm_attach), + DEVMETHOD(device_detach, emu_pcm_detach), + + {0, 0} +}; + +static driver_t emu_pcm_driver = { + "pcm", + emu_pcm_methods, + PCM_SOFTC_SIZE, + NULL, + 0, + NULL +}; +DRIVER_MODULE(snd_emu10kx_pcm, emu10kx, emu_pcm_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_emu10kx_pcm, snd_emu10kx, SND_EMU10KX_MINVER, SND_EMU10KX_PREFVER, SND_EMU10KX_MAXVER); +MODULE_DEPEND(snd_emu10kx_pcm, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_emu10kx_pcm, SND_EMU10KX_PREFVER); --- sys/dev/sound/pci/emu10kx.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,3167 @@ +/*- + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003-2006 Yuriy Tsibizov + * 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 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 AUTHOR 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, WHETHERIN 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. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx.c,v 1.11 2007/06/04 18:25:04 dwmalone Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include /* for DELAY */ + +#include +#include +#include + +#include "opt_emu10kx.h" +#include + +/* hw flags */ +#define HAS_51 0x0001 +#define HAS_71 0x0002 +#define HAS_AC97 0x0004 + +#define IS_EMU10K1 0x0008 +#define IS_EMU10K2 0x0010 +#define IS_CA0102 0x0020 +#define IS_CA0108 0x0040 +#define IS_UNKNOWN 0x0080 + +#define BROKEN_DIGITAL 0x0100 +#define DIGITAL_ONLY 0x0200 + +#define IS_CARDBUS 0x0400 + +#define MODE_ANALOG 1 +#define MODE_DIGITAL 2 +#define SPDIF_MODE_PCM 1 +#define SPDIF_MODE_AC3 2 + +#define MACS 0x0 +#define MACS1 0x1 +#define MACW 0x2 +#define MACW1 0x3 +#define MACINTS 0x4 +#define MACINTW 0x5 +#define ACC3 0x6 +#define MACMV 0x7 +#define ANDXOR 0x8 +#define TSTNEG 0x9 +#define LIMIT 0xA +#define LIMIT1 0xB +#define LOG 0xC +#define EXP 0xD +#define INTERP 0xE +#define SKIP 0xF + +#define GPR(i) (sc->gpr_base+(i)) +#define INP(i) (sc->input_base+(i)) +#define OUTP(i) (sc->output_base+(i)) +#define FX(i) (i) +#define FX2(i) (sc->efxc_base+(i)) +#define DSP_CONST(i) (sc->dsp_zero+(i)) + +#define COND_NORMALIZED DSP_CONST(0x1) +#define COND_BORROW DSP_CONST(0x2) +#define COND_MINUS DSP_CONST(0x3) +#define COND_LESS_ZERO DSP_CONST(0x4) +#define COND_EQ_ZERO DSP_CONST(0x5) +#define COND_SATURATION DSP_CONST(0x6) +#define COND_NEQ_ZERO DSP_CONST(0x8) + +/* Live! Inputs */ +#define IN_AC97_L 0x00 +#define IN_AC97_R 0x01 +#define IN_AC97 IN_AC97_L +#define IN_SPDIF_CD_L 0x02 +#define IN_SPDIF_CD_R 0x03 +#define IN_SPDIF_CD IN_SPDIF_CD_L +#define IN_ZOOM_L 0x04 +#define IN_ZOOM_R 0x05 +#define IN_ZOOM IN_ZOOM_L +#define IN_TOSLINK_L 0x06 +#define IN_TOSLINK_R 0x07 +#define IN_TOSLINK IN_TOSLINK_L +#define IN_LINE1_L 0x08 +#define IN_LINE1_R 0x09 +#define IN_LINE1 IN_LINE1_L +#define IN_COAX_SPDIF_L 0x0a +#define IN_COAX_SPDIF_R 0x0b +#define IN_COAX_SPDIF IN_COAX_SPDIF_L +#define IN_LINE2_L 0x0c +#define IN_LINE2_R 0x0d +#define IN_LINE2 IN_LINE2_L +#define IN_0E 0x0e +#define IN_0F 0x0f + +/* Outputs */ +#define OUT_AC97_L 0x00 +#define OUT_AC97_R 0x01 +#define OUT_AC97 OUT_AC97_L +#define OUT_A_FRONT OUT_AC97 +#define OUT_TOSLINK_L 0x02 +#define OUT_TOSLINK_R 0x03 +#define OUT_TOSLINK OUT_TOSLINK_L +#define OUT_D_CENTER 0x04 +#define OUT_D_SUB 0x05 +#define OUT_HEADPHONE_L 0x06 +#define OUT_HEADPHONE_R 0x07 +#define OUT_HEADPHONE OUT_HEADPHONE_L +#define OUT_REAR_L 0x08 +#define OUT_REAR_R 0x09 +#define OUT_REAR OUT_REAR_L +#define OUT_ADC_REC_L 0x0a +#define OUT_ADC_REC_R 0x0b +#define OUT_ADC_REC OUT_ADC_REC_L +#define OUT_MIC_CAP 0x0c + +/* Live! 5.1 Digital, non-standart 5.1 (center & sub) outputs */ +#define OUT_A_CENTER 0x11 +#define OUT_A_SUB 0x12 + +/* Audigy Inputs */ +#define A_IN_AC97_L 0x00 +#define A_IN_AC97_R 0x01 +#define A_IN_AC97 A_IN_AC97_L +#define A_IN_SPDIF_CD_L 0x02 +#define A_IN_SPDIF_CD_R 0x03 +#define A_IN_SPDIF_CD A_IN_SPDIF_CD_L +#define A_IN_O_SPDIF_L 0x04 +#define A_IN_O_SPDIF_R 0x05 +#define A_IN_O_SPDIF A_IN_O_SPDIF_L +#define A_IN_LINE2_L 0x08 +#define A_IN_LINE2_R 0x09 +#define A_IN_LINE2 A_IN_LINE2_L +#define A_IN_R_SPDIF_L 0x0a +#define A_IN_R_SPDIF_R 0x0b +#define A_IN_R_SPDIF A_IN_R_SPDIF_L +#define A_IN_AUX2_L 0x0c +#define A_IN_AUX2_R 0x0d +#define A_IN_AUX2 A_IN_AUX2_L + +/* Audigiy Outputs */ +#define A_OUT_D_FRONT_L 0x00 +#define A_OUT_D_FRONT_R 0x01 +#define A_OUT_D_FRONT A_OUT_D_FRONT_L +#define A_OUT_D_CENTER 0x02 +#define A_OUT_D_SUB 0x03 +#define A_OUT_D_SIDE_L 0x04 +#define A_OUT_D_SIDE_R 0x05 +#define A_OUT_D_SIDE A_OUT_D_SIDE_L +#define A_OUT_D_REAR_L 0x06 +#define A_OUT_D_REAR_R 0x07 +#define A_OUT_D_REAR A_OUT_D_REAR_L + +/* on Audigy Platinum only */ +#define A_OUT_HPHONE_L 0x04 +#define A_OUT_HPHONE_R 0x05 +#define A_OUT_HPHONE A_OUT_HPHONE_L + +#define A_OUT_A_FRONT_L 0x08 +#define A_OUT_A_FRONT_R 0x09 +#define A_OUT_A_FRONT A_OUT_A_FRONT_L +#define A_OUT_A_CENTER 0x0a +#define A_OUT_A_SUB 0x0b +#define A_OUT_A_SIDE_L 0x0c +#define A_OUT_A_SIDE_R 0x0d +#define A_OUT_A_SIDE A_OUT_A_SIDE_L +#define A_OUT_A_REAR_L 0x0e +#define A_OUT_A_REAR_R 0x0f +#define A_OUT_A_REAR A_OUT_A_REAR_L +#define A_OUT_AC97_L 0x10 +#define A_OUT_AC97_R 0x11 +#define A_OUT_AC97 A_OUT_AC97_L +#define A_OUT_ADC_REC_L 0x16 +#define A_OUT_ADC_REC_R 0x17 +#define A_OUT_ADC_REC A_OUT_ADC_REC_L + +#include "emu10k1-alsa%diked.h" +#include "p16v-alsa%diked.h" +#include "p17v-alsa%diked.h" + +#define C_FRONT_L 0 +#define C_FRONT_R 1 +#define C_REC_L 2 +#define C_REC_R 3 +#define C_REAR_L 4 +#define C_REAR_R 5 +#define C_CENTER 6 +#define C_SUB 7 +#define C_SIDE_L 8 +#define C_SIDE_R 9 +#define NUM_CACHES 10 + +#define NUM_DUMMIES 64 + +#define EMU_MAX_GPR 512 +#define EMU_MAX_IRQ_CONSUMERS 32 + +struct emu_voice { + int vnum; + unsigned int b16:1, stereo:1, busy:1, running:1, ismaster:1; + int speed; + int start; + int end; + int vol; + uint32_t buf; + void *vbuf; + struct emu_voice *slave; + uint32_t sa; + uint32_t ea; +}; + +struct emu_memblk { + SLIST_ENTRY(emu_memblk) link; + void *buf; + char owner[16]; + bus_addr_t buf_addr; + uint32_t pte_start, pte_size; +}; + +struct emu_mem { + uint8_t bmap[EMU_MAXPAGES / 8]; + uint32_t *ptb_pages; + void *silent_page; + bus_addr_t silent_page_addr; + bus_addr_t ptb_pages_addr; + bus_dma_tag_t dmat; + SLIST_HEAD(, emu_memblk) blocks; +}; + +/* rm */ +struct emu_rm { + struct emu_sc_info *card; + struct mtx gpr_lock; + signed int allocmap[EMU_MAX_GPR]; + int num_gprs; + int last_free_gpr; + int num_used; +}; + +struct emu_intr_handler { + void* softc; + uint32_t intr_mask; + uint32_t inte_mask; + uint32_t(*irq_func) (void *softc, uint32_t irq); +}; + +struct emu_sc_info { + struct mtx lock; + struct mtx rw; /* Hardware exclusive access lock */ + + /* Hardware and subdevices */ + device_t dev; + device_t pcm[RT_COUNT]; + device_t midi[2]; + uint32_t type; + uint32_t rev; + + bus_space_tag_t st; + bus_space_handle_t sh; + + struct cdev *cdev; /* /dev/emu10k character device */ + struct mtx emu10kx_lock; + int emu10kx_isopen; + struct sbuf emu10kx_sbuf; + int emu10kx_bufptr; + + + /* Resources */ + struct resource *reg; + struct resource *irq; + void *ih; + + /* IRQ handlers */ + struct emu_intr_handler ihandler[EMU_MAX_IRQ_CONSUMERS]; + + /* Card HW configuration */ + unsigned int mchannel_fx; + unsigned int dsp_zero; + unsigned int code_base; + unsigned int code_size; + unsigned int gpr_base; + unsigned int num_gprs; + unsigned int input_base; + unsigned int output_base; + unsigned int efxc_base; + unsigned int opcode_shift; + unsigned int high_operand_shift; + unsigned int address_mask; + uint32_t is_emu10k1:1, is_emu10k2, is_ca0102, is_ca0108:1, + has_ac97:1, has_51:1, has_71:1, + enable_ir:1, enable_debug:1, + broken_digital:1, is_cardbus:1; + + unsigned int num_inputs; + unsigned int num_outputs; + unsigned int num_fxbuses; + unsigned int routing_code_start; + unsigned int routing_code_end; + + /* HW resources */ + struct emu_voice voice[NUM_G]; /* Hardware voices */ + uint32_t irq_mask[EMU_MAX_IRQ_CONSUMERS]; /* IRQ manager data */ + int timer[EMU_MAX_IRQ_CONSUMERS]; /* timer */ + int timerinterval; + struct emu_rm *rm; + struct emu_mem mem; /* memory */ + + /* Mixer */ + int mixer_gpr[NUM_MIXERS]; + int mixer_volcache[NUM_MIXERS]; + int cache_gpr[NUM_CACHES]; + int dummy_gpr[NUM_DUMMIES]; + struct sysctl_ctx_list *ctx; + struct sysctl_oid *root; +}; + +static void emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error); +static void* emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr); +static void emu_free(struct emu_mem *mem, void *dmabuf); +static void* emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char * owner); +static int emu_memfree(struct emu_mem *mem, void *membuf); +static int emu_memstart(struct emu_mem *mem, void *membuf); + +/* /dev */ +static int emu10kx_dev_init(struct emu_sc_info *sc); +static int emu10kx_dev_uninit(struct emu_sc_info *sc); +static int emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s); + +static void emumix_set_mode(struct emu_sc_info *sc, int mode); +static void emumix_set_spdif_mode(struct emu_sc_info *sc, int mode); +static void emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol); +static void emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val); +static int sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS); + +static int emu_rm_init(struct emu_sc_info *sc); +static int emu_rm_uninit(struct emu_sc_info *sc); +static int emu_rm_gpr_alloc(struct emu_rm *rm, int count); + +static unsigned int emu_getcard(device_t dev); +static uint32_t emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size); +static void emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size); +static void emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data); + +static void emu_vstop(struct emu_sc_info *sc, char channel, int enable); + +static void emu_intr(void *p); +static void emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data); +static void emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc); +static void emu_initefx(struct emu_sc_info *sc); + +static int emu_cardbus_init(struct emu_sc_info *sc); +static int emu_init(struct emu_sc_info *sc); +static int emu_uninit(struct emu_sc_info *sc); + +static int emu_read_ivar(device_t bus __unused, device_t dev, int ivar_index, uintptr_t * result); +static int emu_write_ivar(device_t bus __unused, device_t dev __unused, + int ivar_index, uintptr_t value __unused); + +static int emu_pci_probe(device_t dev); +static int emu_pci_attach(device_t dev); +static int emu_pci_detach(device_t dev); +static int emu_modevent(module_t mod __unused, int cmd, void *data __unused); + +/* Supported cards */ +struct emu_hwinfo { + uint16_t vendor; + uint16_t device; + uint16_t subvendor; + uint16_t subdevice; + char SBcode[8]; + char desc[32]; + int flags; +}; + +static struct emu_hwinfo emu_cards[] = { + {0xffff, 0xffff, 0xffff, 0xffff, "BADCRD", "Not a compatible card", 0}, + /* 0x0020..0x002f 4.0 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x0020, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x0021, "CT4620", "SBLive!", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x002f, "CT????", "SBLive! mainboard implementation", HAS_AC97 | IS_EMU10K1}, + + /* (range unknown) 5.1 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x100a, "CT????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, + + /* 0x80??..0x805? 4.0 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x8022, "CT4780", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8023, "CT4790", "SB PCI512", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8024, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8025, "CT????", "SBLive! Mainboard Implementation", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8026, "CT4830", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8027, "CT4832", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8028, "CT4760", "SBLive! OEM version", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8031, "CT4831", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8040, "CT4760", "SBLive!", HAS_AC97 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8051, "CT4850", "SBLive! Value", HAS_AC97 | IS_EMU10K1}, + + /* 0x8061..0x???? 5.1 EMU10K1 cards */ + {0x1102, 0x0002, 0x1102, 0x8061, "SB????", "SBLive! Player 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8064, "SB????", "SBLive! 5.1", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8065, "SB0220", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8066, "CT4780", "SBLive! 5.1 Digital", HAS_AC97 | HAS_51 | IS_EMU10K1}, + {0x1102, 0x0002, 0x1102, 0x8067, "SB????", "SBLive!", HAS_AC97 | HAS_51 | IS_EMU10K1}, + + /* Generic SB Live! */ + {0x1102, 0x0002, 0x1102, 0x0000, "SB????", "SBLive! (Unknown model)", HAS_AC97 | IS_EMU10K1}, + + /* 0x0041..0x0043 EMU10K2 (some kind of Audigy) cards */ + + /* 0x0051..0x0051 5.1 CA0100-IAF cards */ + {0x1102, 0x0004, 0x1102, 0x0051, "SB0090", "Audigy", HAS_AC97 | HAS_51 | IS_EMU10K2}, + /* ES is CA0100-IDF chip that don't work in digital mode */ + {0x1102, 0x0004, 0x1102, 0x0052, "SB0160", "Audigy ES", HAS_AC97 | HAS_71 | IS_EMU10K2 | BROKEN_DIGITAL}, + /* 0x0053..0x005C 5.1 CA0101-NAF cards */ + {0x1102, 0x0004, 0x1102, 0x0053, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2}, + {0x1102, 0x0004, 0x1102, 0x0058, "SB0090", "Audigy Player/OEM", HAS_AC97 | HAS_51 | IS_EMU10K2}, + + /* 0x1002..0x1009 5.1 CA0102-IAT cards */ + {0x1102, 0x0004, 0x1102, 0x1002, "SB????", "Audigy 2 Platinum", HAS_51 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x1005, "SB????", "Audigy 2 Platinum EX", HAS_51 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x1007, "SB0240", "Audigy 2", HAS_AC97 | HAS_51 | IS_CA0102}, + + /* 0x2001..0x2003 7.1 CA0102-ICT cards */ + {0x1102, 0x0004, 0x1102, 0x2001, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x2002, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + /* XXX No reports about 0x2003 & 0x2004 cards */ + {0x1102, 0x0004, 0x1102, 0x2003, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x2004, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + {0x1102, 0x0004, 0x1102, 0x2005, "SB0350", "Audigy 2 ZS", HAS_AC97 | HAS_71 | IS_CA0102}, + + /* (range unknown) 7.1 CA0102-xxx Audigy 4 cards */ + {0x1102, 0x0004, 0x1102, 0x2007, "SB0380", "Audigy 4 Pro", HAS_AC97 | HAS_71 | IS_CA0102}, + + /* Generic Audigy or Audigy 2 */ + {0x1102, 0x0004, 0x1102, 0x0000, "SB????", "Audigy (Unknown model)", HAS_AC97 | HAS_51 | IS_EMU10K2}, + + /* We don't support CA0103-DAT (Audigy LS) cards */ + /* There is NO CA0104-xxx cards */ + /* There is NO CA0105-xxx cards */ + /* We don't support CA0106-DAT (SB Live! 24 bit) cards */ + /* There is NO CA0107-xxx cards */ + + /* 0x1000..0x1001 7.1 CA0108-IAT cards */ + {0x1102, 0x0008, 0x1102, 0x1000, "SB????", "Audigy 2 LS", HAS_AC97 | HAS_51 | IS_CA0108 | DIGITAL_ONLY}, + {0x1102, 0x0008, 0x1102, 0x1001, "SB0400", "Audigy 2 Value", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY}, + {0x1102, 0x0008, 0x1102, 0x1021, "SB0610", "Audigy 4", HAS_AC97 | HAS_71 | IS_CA0108 | DIGITAL_ONLY}, + + {0x1102, 0x0008, 0x1102, 0x2001, "SB0530", "Audigy 2 ZS CardBus", HAS_AC97 | HAS_71 | IS_CA0108 | IS_CARDBUS}, + + {0x1102, 0x0008, 0x0000, 0x0000, "SB????", "Audigy 2 Value (Unknown model)", HAS_AC97 | HAS_51 | IS_CA0108}, +}; +/* Unsupported cards */ + +static struct emu_hwinfo emu_bad_cards[] = { + /* APS cards should be possible to support */ + {0x1102, 0x0002, 0x1102, 0x4001, "EMUAPS", "E-mu APS", 0}, + {0x1102, 0x0002, 0x1102, 0x4002, "EMUAPS", "E-mu APS", 0}, + {0x1102, 0x0004, 0x1102, 0x4001, "EMU???", "E-mu 1212m [4001]", 0}, + /* Similar-named ("Live!" or "Audigy") cards on different chipsets */ + {0x1102, 0x8064, 0x0000, 0x0000, "SB0100", "SBLive! 5.1 OEM", 0}, + {0x1102, 0x0006, 0x0000, 0x0000, "SB0200", "DELL OEM SBLive! Value", 0}, + {0x1102, 0x0007, 0x0000, 0x0000, "SB0310", "Audigy LS", 0}, +}; + +/* + * Get best known information about device. + */ +static unsigned int +emu_getcard(device_t dev) +{ + uint16_t device; + uint16_t subdevice; + int n_cards; + unsigned int thiscard; + int i; + + device = pci_read_config(dev, PCIR_DEVICE, /* bytes */ 2); + subdevice = pci_read_config(dev, PCIR_SUBDEV_0, /* bytes */ 2); + + n_cards = sizeof(emu_cards) / sizeof(struct emu_hwinfo); + thiscard = 0; + for (i = 1; i < n_cards; i++) { + if (device == emu_cards[i].device) { + if (subdevice == emu_cards[i].subdevice) { + thiscard = i; + break; + } + if (0x0000 == emu_cards[i].subdevice) { + thiscard = i; + /* don't break, we can get more specific card + * later in the list */ + } + } + } + + n_cards = sizeof(emu_bad_cards) / sizeof(struct emu_hwinfo); + for (i = 0; i < n_cards; i++) { + if (device == emu_bad_cards[i].device) { + if (subdevice == emu_bad_cards[i].subdevice) { + thiscard = 0; + break; + } + if (0x0000 == emu_bad_cards[i].subdevice) { + thiscard = 0; + break; /* we avoid all this cards */ + } + } + } + return (thiscard); +} + + +/* + * Base hardware interface are 32 (Audigy) or 64 (Audigy2) registers. + * Some of them are used directly, some of them provide pointer / data pairs. + */ +static uint32_t +emu_rd_nolock(struct emu_sc_info *sc, unsigned int regno, unsigned int size) +{ + + KASSERT(sc != NULL, ("emu_rd: NULL sc")); + switch (size) { + case 1: + return (bus_space_read_1(sc->st, sc->sh, regno)); + case 2: + return (bus_space_read_2(sc->st, sc->sh, regno)); + case 4: + return (bus_space_read_4(sc->st, sc->sh, regno)); + } + return (0xffffffff); +} + +static void +emu_wr_nolock(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size) +{ + + KASSERT(sc != NULL, ("emu_rd: NULL sc")); + switch (size) { + case 1: + bus_space_write_1(sc->st, sc->sh, regno, data); + break; + case 2: + bus_space_write_2(sc->st, sc->sh, regno, data); + break; + case 4: + bus_space_write_4(sc->st, sc->sh, regno, data); + break; + } +} +/* + * PTR / DATA interface. Access to EMU10Kx is made + * via (channel, register) pair. Some registers are channel-specific, + * some not. + */ +uint32_t +emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg) +{ + uint32_t ptr, val, mask, size, offset; + + ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK); + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR, ptr, 4); + val = emu_rd_nolock(sc, DATA, 4); + mtx_unlock(&sc->rw); + /* + * XXX Some register numbers has data size and offset encoded in + * it to get only part of 32bit register. This use is not described + * in register name, be careful! + */ + if (reg & 0xff000000) { + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + val &= mask; + val >>= offset; + } + return (val); +} + +void +emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data) +{ + uint32_t ptr, mask, size, offset; + ptr = ((reg << 16) & sc->address_mask) | (chn & PTR_CHANNELNUM_MASK); + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR, ptr, 4); + /* + * XXX Another kind of magic encoding in register number. This can + * give you side effect - it will read previous data from register + * and change only required bits. + */ + if (reg & 0xff000000) { + size = (reg >> 24) & 0x3f; + offset = (reg >> 16) & 0x1f; + mask = ((1 << size) - 1) << offset; + data <<= offset; + data &= mask; + data |= emu_rd_nolock(sc, DATA, 4) & ~mask; + } + emu_wr_nolock(sc, DATA, data, 4); + mtx_unlock(&sc->rw); +} +/* + * PTR2 / DATA2 interface. Access to P16v is made + * via (channel, register) pair. Some registers are channel-specific, + * some not. This interface is supported by CA0102 and CA0108 chips only. + */ +uint32_t +emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg) +{ + uint32_t val; + + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4); + val = emu_rd_nolock(sc, DATA2, 4); + mtx_unlock(&sc->rw); + return (val); +} + +void +emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data) +{ + + mtx_lock(&sc->rw); + emu_wr_nolock(sc, PTR2, (reg << 16) | chn, 4); + emu_wr_nolock(sc, DATA2, data, 4); + mtx_unlock(&sc->rw); +} +/* + * XXX CardBus interface. Not tested on any real hardware. + */ +static void +emu_wr_cbptr(struct emu_sc_info *sc, uint32_t data) +{ + uint32_t val; + + /* 0x38 is IPE3 (CD S/PDIF interrupt pending register) on CA0102 Seems + * to be some reg/value accessible kind of config register on CardBus + * CA0108, with value(?) in top 16 bit, address(?) in low 16 */ + mtx_lock(&sc->rw); + val = emu_rd_nolock(sc, 0x38, 4); + emu_wr_nolock(sc, 0x38, data, 4); + val = emu_rd_nolock(sc, 0x38, 4); + mtx_unlock(&sc->rw); +} + +/* + * Direct hardware register access + */ +void +emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size) +{ + + mtx_lock(&sc->rw); + emu_wr_nolock(sc, regno, data, size); + mtx_unlock(&sc->rw); +} + +uint32_t +emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size) +{ + uint32_t rd; + + mtx_lock(&sc->rw); + rd = emu_rd_nolock(sc, regno, size); + mtx_unlock(&sc->rw); + return (rd); +} + +/* + * Enabling IR MIDI messages is another kind of black magic. It just + * has to be made this way. It really do it. + */ +void +emu_enable_ir(struct emu_sc_info *sc) +{ + uint32_t iocfg; + + mtx_lock(&sc->rw); + if (sc->is_emu10k2 || sc->is_ca0102) { + iocfg = emu_rd_nolock(sc, A_IOCFG, 2); + emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT2, 2); + DELAY(500); + emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2, 2); + DELAY(500); + emu_wr_nolock(sc, A_IOCFG, iocfg | A_IOCFG_GPOUT1, 2); + DELAY(100); + emu_wr_nolock(sc, A_IOCFG, iocfg, 2); + device_printf(sc->dev, "Audigy IR MIDI events enabled.\n"); + sc->enable_ir = 1; + } + if (sc->is_emu10k1) { + iocfg = emu_rd_nolock(sc, HCFG, 4); + emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT2, 4); + DELAY(500); + emu_wr_nolock(sc, HCFG, iocfg | HCFG_GPOUT1 | HCFG_GPOUT2, 4); + DELAY(100); + emu_wr_nolock(sc, HCFG, iocfg, 4); + device_printf(sc->dev, "SB Live! IR MIDI events enabled.\n"); + sc->enable_ir = 1; + } + mtx_unlock(&sc->rw); +} + + +/* + * emu_timer_ - HW timer managment + */ +int +emu_timer_create(struct emu_sc_info *sc) +{ + int i, timer; + + timer = -1; + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->timer[i] == 0) { + sc->timer[i] = -1; /* disable it */ + timer = i; + return (timer); + } + + return (-1); +} + +int +emu_timer_set(struct emu_sc_info *sc, int timer, int delay) +{ + int i; + + if(timer < 0) + return (-1); + + RANGE(delay, 16, 1024); + RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); + + sc->timer[timer] = delay; + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->timerinterval > sc->timer[i]) + sc->timerinterval = sc->timer[i]; + + emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2); + return (timer); +} + +int +emu_timer_enable(struct emu_sc_info *sc, int timer, int go) +{ + uint32_t x; + int ena_int; + int i; + + if(timer < 0) + return (-1); + + RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); + + mtx_lock(&sc->lock); + + if ((go == 1) && (sc->timer[timer] < 0)) + sc->timer[timer] = -sc->timer[timer]; + if ((go == 0) && (sc->timer[timer] > 0)) + sc->timer[timer] = -sc->timer[timer]; + + ena_int = 0; + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) { + if (sc->timerinterval > sc->timer[i]) + sc->timerinterval = sc->timer[i]; + if (sc->timer[i] > 0) + ena_int = 1; + } + + emu_wr(sc, TIMER, sc->timerinterval & 0x03ff, 2); + + if (ena_int == 1) { + x = emu_rd(sc, INTE, 4); + x |= INTE_INTERVALTIMERENB; + emu_wr(sc, INTE, x, 4); + } else { + x = emu_rd(sc, INTE, 4); + x &= ~INTE_INTERVALTIMERENB; + emu_wr(sc, INTE, x, 4); + } + mtx_unlock(&sc->lock); + return (0); +} + +int +emu_timer_clear(struct emu_sc_info *sc, int timer) +{ + if(timer < 0) + return (-1); + + RANGE(timer, 0, EMU_MAX_IRQ_CONSUMERS-1); + + emu_timer_enable(sc, timer, 0); + + mtx_lock(&sc->lock); + if (sc->timer[timer] != 0) + sc->timer[timer] = 0; + mtx_unlock(&sc->lock); + + return (timer); +} + +/* + * emu_intr_ - HW interrupt handler managment + */ +int +emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc) +{ + int i; + uint32_t x; + + mtx_lock(&sc->lock); + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->ihandler[i].inte_mask == 0) { + sc->ihandler[i].inte_mask = inte_mask; + sc->ihandler[i].intr_mask = intr_mask; + sc->ihandler[i].softc = isc; + sc->ihandler[i].irq_func = func; + x = emu_rd(sc, INTE, 4); + x |= inte_mask; + emu_wr(sc, INTE, x, 4); + mtx_unlock(&sc->lock); +#ifdef SND_EMU10KX_DEBUG + device_printf(sc->dev, "ihandle %d registered\n", i); +#endif + return (i); + } + mtx_unlock(&sc->lock); +#ifdef SND_EMU10KX_DEBUG + device_printf(sc->dev, "ihandle not registered\n"); +#endif + return (-1); +} + +int +emu_intr_unregister(struct emu_sc_info *sc, int hnumber) +{ + uint32_t x; + int i; + + mtx_lock(&sc->lock); + + if (sc->ihandler[hnumber].inte_mask == 0) { + mtx_unlock(&sc->lock); + return (-1); + } + + x = emu_rd(sc, INTE, 4); + x &= ~sc->ihandler[hnumber].inte_mask; + + sc->ihandler[hnumber].inte_mask = 0; + sc->ihandler[hnumber].intr_mask = 0; + sc->ihandler[hnumber].softc = NULL; + sc->ihandler[hnumber].irq_func = NULL; + + /* other interupt handlers may use this INTE value */ + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + if (sc->ihandler[i].inte_mask != 0) + x |= sc->ihandler[i].inte_mask; + + emu_wr(sc, INTE, x, 4); + + mtx_unlock(&sc->lock); + return (hnumber); +} + +static void +emu_intr(void *p) +{ + struct emu_sc_info *sc = (struct emu_sc_info *)p; + uint32_t stat, ack; + int i; + + for (;;) { + stat = emu_rd(sc, IPR, 4); + ack = 0; + if (stat == 0) + break; + emu_wr(sc, IPR, stat, 4); + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) { + if ((((sc->ihandler[i].intr_mask) & stat) != 0) && + (((void *)sc->ihandler[i].irq_func) != NULL)) { + ack |= sc->ihandler[i].irq_func(sc->ihandler[i].softc, + (sc->ihandler[i].intr_mask) & stat); + } + } +#ifdef SND_EMU10KX_DEBUG + if(stat & (~ack)) + device_printf(sc->dev, "Unhandled interrupt: %08x\n", stat & (~ack)); +#endif + } + + if ((sc->is_ca0102) || (sc->is_ca0108)) + for (;;) { + stat = emu_rd(sc, IPR2, 4); + ack = 0; + if (stat == 0) + break; + emu_wr(sc, IPR2, stat, 4); + device_printf(sc->dev, "IPR2: %08x\n", stat); + break; /* to avoid infinite loop. shoud be removed + * after completion of P16V interface. */ + } + + if (sc->is_ca0102) + for (;;) { + stat = emu_rd(sc, IPR3, 4); + ack = 0; + if (stat == 0) + break; + emu_wr(sc, IPR3, stat, 4); + device_printf(sc->dev, "IPR3: %08x\n", stat); + break; /* to avoid infinite loop. should be removed + * after completion of S/PDIF interface */ + } +} + + +/* + * Get data from private emu10kx structure for PCM buffer allocation. + * Used by PCM code only. + */ +bus_dma_tag_t +emu_gettag(struct emu_sc_info *sc) +{ + return (sc->mem.dmat); +} + +static void +emu_setmap(void *arg, bus_dma_segment_t * segs, int nseg, int error) +{ + bus_addr_t *phys = (bus_addr_t *) arg; + + *phys = error ? 0 : (bus_addr_t) segs->ds_addr; + + if (bootverbose) { + printf("emu10kx: setmap (%lx, %lx), nseg=%d, error=%d\n", + (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len, + nseg, error); + } +} + +static void * +emu_malloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr) +{ + void *dmabuf; + bus_dmamap_t map; + + *addr = 0; + if (bus_dmamem_alloc(mem->dmat, &dmabuf, BUS_DMA_NOWAIT, &map)) + return (NULL); + if (bus_dmamap_load(mem->dmat, map, dmabuf, sz, emu_setmap, addr, 0) || !*addr) + return (NULL); + return (dmabuf); +} + +static void +emu_free(struct emu_mem *mem, void *dmabuf) +{ + bus_dmamem_free(mem->dmat, dmabuf, NULL); +} + +static void * +emu_memalloc(struct emu_mem *mem, uint32_t sz, bus_addr_t * addr, const char *owner) +{ + uint32_t blksz, start, idx, ofs, tmp, found; + struct emu_memblk *blk; + void *membuf; + + blksz = sz / EMUPAGESIZE; + if (sz > (blksz * EMUPAGESIZE)) + blksz++; + if (blksz > EMU_MAX_BUFSZ / EMUPAGESIZE) + return (NULL); + /* find a free block in the bitmap */ + found = 0; + start = 1; + while (!found && start + blksz < EMU_MAXPAGES) { + found = 1; + for (idx = start; idx < start + blksz; idx++) + if (mem->bmap[idx >> 3] & (1 << (idx & 7))) + found = 0; + if (!found) + start++; + } + if (!found) + return (NULL); + blk = malloc(sizeof(*blk), M_DEVBUF, M_NOWAIT); + if (blk == NULL) + return (NULL); + bzero(blk, sizeof(*blk)); + membuf = emu_malloc(mem, sz, &blk->buf_addr); + *addr = blk->buf_addr; + if (membuf == NULL) { + free(blk, M_DEVBUF); + return (NULL); + } + blk->buf = membuf; + blk->pte_start = start; + blk->pte_size = blksz; + strncpy(blk->owner, owner, 15); + blk->owner[15] = '\0'; +#ifdef SND_EMU10KX_DEBUG + printf("emu10kx emu_memalloc: allocating %d for %s\n", blk->pte_size, blk->owner); +#endif + ofs = 0; + for (idx = start; idx < start + blksz; idx++) { + mem->bmap[idx >> 3] |= 1 << (idx & 7); + tmp = (uint32_t) (u_long) ((uint8_t *) blk->buf_addr + ofs); + mem->ptb_pages[idx] = (tmp << 1) | idx; + ofs += EMUPAGESIZE; + } + SLIST_INSERT_HEAD(&mem->blocks, blk, link); + return (membuf); +} + +static int +emu_memfree(struct emu_mem *mem, void *membuf) +{ + uint32_t idx, tmp; + struct emu_memblk *blk, *i; + + blk = NULL; + SLIST_FOREACH(i, &mem->blocks, link) { + if (i->buf == membuf) + blk = i; + } + if (blk == NULL) + return (EINVAL); +#ifdef SND_EMU10KX_DEBUG + printf("emu10kx emu_memfree: freeing %d for %s\n", blk->pte_size, blk->owner); +#endif + SLIST_REMOVE(&mem->blocks, blk, emu_memblk, link); + emu_free(mem, membuf); + tmp = (uint32_t) (mem->silent_page_addr) << 1; + for (idx = blk->pte_start; idx < blk->pte_start + blk->pte_size; idx++) { + mem->bmap[idx >> 3] &= ~(1 << (idx & 7)); + mem->ptb_pages[idx] = tmp | idx; + } + free(blk, M_DEVBUF); + return (0); +} + +static int +emu_memstart(struct emu_mem *mem, void *membuf) +{ + struct emu_memblk *blk, *i; + + blk = NULL; + SLIST_FOREACH(i, &mem->blocks, link) { + if (i->buf == membuf) + blk = i; + } + if (blk == NULL) + return (-1); + return (blk->pte_start); +} + + +static uint32_t +emu_rate_to_pitch(uint32_t rate) +{ + static uint32_t logMagTable[128] = { + 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, + 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, + 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, + 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, + 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, + 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, + 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, + 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, + 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, + 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, + 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, + 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, + 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, + 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, + 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, + 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df + }; + static char logSlopeTable[128] = { + 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, + 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, + 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, + 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, + 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, + 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, + 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, + 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, + 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, + 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, + 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, + 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f + }; + int i; + + if (rate == 0) + return (0); + rate *= 11185; /* Scale 48000 to 0x20002380 */ + for (i = 31; i > 0; i--) { + if (rate & 0x80000000) { /* Detect leading "1" */ + return (((uint32_t) (i - 15) << 20) + + logMagTable[0x7f & (rate >> 24)] + + (0x7f & (rate >> 17)) * + logSlopeTable[0x7f & (rate >> 24)]); + } + rate <<= 1; + } + /* NOTREACHED */ + return (0); +} + +static uint32_t +emu_rate_to_linearpitch(uint32_t rate) +{ + rate = (rate << 8) / 375; + return ((rate >> 1) + (rate & 1)); +} + +struct emu_voice * +emu_valloc(struct emu_sc_info *sc) +{ + struct emu_voice *v; + int i; + + v = NULL; + mtx_lock(&sc->lock); + for (i = 0; i < NUM_G && sc->voice[i].busy; i++); + if (i < NUM_G) { + v = &sc->voice[i]; + v->busy = 1; + } + mtx_unlock(&sc->lock); + return (v); +} + +void +emu_vfree(struct emu_sc_info *sc, struct emu_voice *v) +{ + int i, r; + + mtx_lock(&sc->lock); + for (i = 0; i < NUM_G; i++) { + if (v == &sc->voice[i] && sc->voice[i].busy) { + v->busy = 0; + /* XXX What we should do with mono channels? + See -pcm.c emupchan_init for other side of + this problem */ + if (v->slave != NULL) + r = emu_memfree(&sc->mem, v->vbuf); + } + } + mtx_unlock(&sc->lock); +} + +int +emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s, + uint32_t sz, struct snd_dbuf *b) +{ + void *vbuf; + bus_addr_t tmp_addr; + + vbuf = emu_memalloc(&sc->mem, sz, &tmp_addr, "vinit"); + if (vbuf == NULL) + return (ENOMEM); + if (b != NULL) + sndbuf_setup(b, vbuf, sz); + m->start = emu_memstart(&sc->mem, vbuf) * EMUPAGESIZE; + if (m->start == -1) { + emu_memfree(&sc->mem, vbuf); + return (ENOMEM); + } + m->end = m->start + sz; + m->speed = 0; + m->b16 = 0; + m->stereo = 0; + m->running = 0; + m->ismaster = 1; + m->vol = 0xff; + m->buf = tmp_addr; + m->vbuf = vbuf; + m->slave = s; + if (s != NULL) { + s->start = m->start; + s->end = m->end; + s->speed = 0; + s->b16 = 0; + s->stereo = 0; + s->running = 0; + s->ismaster = 0; + s->vol = m->vol; + s->buf = m->buf; + s->vbuf = NULL; + s->slave = NULL; + } + return (0); +} + +void +emu_vsetup(struct emu_voice *v, int fmt, int spd) +{ + if (fmt) { + v->b16 = (fmt & AFMT_16BIT) ? 1 : 0; + v->stereo = (fmt & AFMT_STEREO) ? 1 : 0; + if (v->slave != NULL) { + v->slave->b16 = v->b16; + v->slave->stereo = v->stereo; + } + } + if (spd) { + v->speed = spd; + if (v->slave != NULL) + v->slave->speed = v->speed; + } +} + +void +emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v) +{ + unsigned int routing[8], amounts[8]; + int i; + + for (i = 0; i < 8; i++) { + routing[i] = rt->routing_left[i]; + amounts[i] = rt->amounts_left[i]; + } + if ((v->stereo) && (v->ismaster == 0)) + for (i = 0; i < 8; i++) { + routing[i] = rt->routing_right[i]; + amounts[i] = rt->amounts_right[i]; + } + + if (sc->is_emu10k1) { + emu_wrptr(sc, v->vnum, FXRT, ((routing[3] << 12) | + (routing[2] << 8) | + (routing[1] << 4) | + (routing[0] << 0)) << 16); + } else { + emu_wrptr(sc, v->vnum, A_FXRT1, (routing[3] << 24) | + (routing[2] << 16) | + (routing[1] << 8) | + (routing[0] << 0)); + emu_wrptr(sc, v->vnum, A_FXRT2, (routing[7] << 24) | + (routing[6] << 16) | + (routing[5] << 8) | + (routing[4] << 0)); + emu_wrptr(sc, v->vnum, A_SENDAMOUNTS, (amounts[7] << 24) | + (amounts[6] << 26) | + (amounts[5] << 8) | + (amounts[4] << 0)); + } + emu_wrptr(sc, v->vnum, PTRX, (amounts[0] << 8) | (amounts[1] << 0)); + emu_wrptr(sc, v->vnum, DSL, v->ea | (amounts[3] << 24)); + emu_wrptr(sc, v->vnum, PSST, v->sa | (amounts[2] << 24)); + if ((v->stereo) && (v->slave != NULL)) + emu_vroute(sc, rt, v->slave); +} + +void +emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v) +{ + int s; + uint32_t am_2, am_3, start, val, silent_page; + + s = (v->stereo ? 1 : 0) + (v->b16 ? 1 : 0); + + v->sa = v->start >> s; + v->ea = v->end >> s; + + + if (v->stereo) { + emu_wrptr(sc, v->vnum, CPF, CPF_STEREO_MASK); + } else { + emu_wrptr(sc, v->vnum, CPF, 0); + } + val = v->stereo ? 28 : 30; + val *= v->b16 ? 1 : 2; + start = v->sa + val; + + am_3 = emu_rdptr(sc, v->vnum, DSL) & 0xff000000; + emu_wrptr(sc, v->vnum, DSL, v->ea | am_3); + am_2 = emu_rdptr(sc, v->vnum, PSST) & 0xff000000; + emu_wrptr(sc, v->vnum, PSST, v->sa | am_2); + + emu_wrptr(sc, v->vnum, CCCA, start | (v->b16 ? 0 : CCCA_8BITSELECT)); + emu_wrptr(sc, v->vnum, Z1, 0); + emu_wrptr(sc, v->vnum, Z2, 0); + + silent_page = ((uint32_t) (sc->mem.silent_page_addr) << 1) | MAP_PTI_MASK; + emu_wrptr(sc, v->vnum, MAPA, silent_page); + emu_wrptr(sc, v->vnum, MAPB, silent_page); + + emu_wrptr(sc, v->vnum, CVCF, CVCF_CURRENTFILTER_MASK); + emu_wrptr(sc, v->vnum, VTFT, VTFT_FILTERTARGET_MASK); + emu_wrptr(sc, v->vnum, ATKHLDM, 0); + emu_wrptr(sc, v->vnum, DCYSUSM, DCYSUSM_DECAYTIME_MASK); + emu_wrptr(sc, v->vnum, LFOVAL1, 0x8000); + emu_wrptr(sc, v->vnum, LFOVAL2, 0x8000); + emu_wrptr(sc, v->vnum, FMMOD, 0); + emu_wrptr(sc, v->vnum, TREMFRQ, 0); + emu_wrptr(sc, v->vnum, FM2FRQ2, 0); + emu_wrptr(sc, v->vnum, ENVVAL, 0x8000); + + emu_wrptr(sc, v->vnum, ATKHLDV, ATKHLDV_HOLDTIME_MASK | ATKHLDV_ATTACKTIME_MASK); + emu_wrptr(sc, v->vnum, ENVVOL, 0x8000); + + emu_wrptr(sc, v->vnum, PEFE_FILTERAMOUNT, 0x7f); + emu_wrptr(sc, v->vnum, PEFE_PITCHAMOUNT, 0); + if ((v->stereo) && (v->slave != NULL)) + emu_vwrite(sc, v->slave); +} + +static void +emu_vstop(struct emu_sc_info *sc, char channel, int enable) +{ + int reg; + + reg = (channel & 0x20) ? SOLEH : SOLEL; + channel &= 0x1f; + reg |= 1 << 24; + reg |= channel << 16; + emu_wrptr(sc, 0, reg, enable); +} + +void +emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go) +{ + uint32_t pitch_target, initial_pitch; + uint32_t cra, cs, ccis; + uint32_t sample, i; + + if (go) { + cra = 64; + cs = v->stereo ? 4 : 2; + ccis = v->stereo ? 28 : 30; + ccis *= v->b16 ? 1 : 2; + sample = v->b16 ? 0x00000000 : 0x80808080; + for (i = 0; i < cs; i++) + emu_wrptr(sc, v->vnum, CD0 + i, sample); + emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, 0); + emu_wrptr(sc, v->vnum, CCR_READADDRESS, cra); + emu_wrptr(sc, v->vnum, CCR_CACHEINVALIDSIZE, ccis); + + emu_wrptr(sc, v->vnum, IFATN, 0xff00); + emu_wrptr(sc, v->vnum, VTFT, 0xffffffff); + emu_wrptr(sc, v->vnum, CVCF, 0xffffffff); + emu_wrptr(sc, v->vnum, DCYSUSV, 0x00007f7f); + emu_vstop(sc, v->vnum, 0); + + pitch_target = emu_rate_to_linearpitch(v->speed); + initial_pitch = emu_rate_to_pitch(v->speed) >> 8; + emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, pitch_target); + emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, pitch_target); + emu_wrptr(sc, v->vnum, IP, initial_pitch); + } else { + emu_wrptr(sc, v->vnum, PTRX_PITCHTARGET, 0); + emu_wrptr(sc, v->vnum, CPF_CURRENTPITCH, 0); + emu_wrptr(sc, v->vnum, IFATN, 0xffff); + emu_wrptr(sc, v->vnum, VTFT, 0x0000ffff); + emu_wrptr(sc, v->vnum, CVCF, 0x0000ffff); + emu_wrptr(sc, v->vnum, IP, 0); + emu_vstop(sc, v->vnum, 1); + } + if ((v->stereo) && (v->slave != NULL)) + emu_vtrigger(sc, v->slave, go); +} + +int +emu_vpos(struct emu_sc_info *sc, struct emu_voice *v) +{ + int s, ptr; + + s = (v->b16 ? 1 : 0) + (v->stereo ? 1 : 0); + ptr = (emu_rdptr(sc, v->vnum, CCCA_CURRADDR) - (v->start >> s)) << s; + return (ptr & ~0x0000001f); +} + + +/* fx */ +static void +emu_wrefx(struct emu_sc_info *sc, unsigned int pc, unsigned int data) +{ + emu_wrptr(sc, 0, sc->code_base + pc, data); +} + + +static void +emu_addefxop(struct emu_sc_info *sc, unsigned int op, unsigned int z, unsigned int w, unsigned int x, unsigned int y, uint32_t * pc) +{ + if ((*pc) + 1 > sc->code_size) { + device_printf(sc->dev, "DSP CODE OVERRUN: attept to write past code_size (pc=%d)\n", (*pc)); + return; + } + emu_wrefx(sc, (*pc) * 2, (x << sc->high_operand_shift) | y); + emu_wrefx(sc, (*pc) * 2 + 1, (op << sc->opcode_shift) | (z << sc->high_operand_shift) | w); + (*pc)++; +} + +static int +sysctl_emu_mixer_control(SYSCTL_HANDLER_ARGS) +{ + struct emu_sc_info *sc; + int mixer_id; + int new_vol; + int err; + + sc = arg1; + mixer_id = arg2; + + new_vol = emumix_get_volume(sc, mixer_id); + err = sysctl_handle_int(oidp, &new_vol, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (new_vol < 0 || new_vol > 100) + return (EINVAL); + emumix_set_volume(sc, mixer_id, new_vol); + + return (0); +} + +static int +emu_addefxmixer(struct emu_sc_info *sc, const char *mix_name, const int mix_id, uint32_t defvolume) +{ + int volgpr; + char sysctl_name[32]; + + volgpr = emu_rm_gpr_alloc(sc->rm, 1); + emumix_set_fxvol(sc, volgpr, defvolume); + /* Mixer controls with NULL mix_name are handled by AC97 emulation + code or PCM mixer. */ + if (mix_name != NULL) { + /* Temporary sysctls should start with underscore, + * see freebsd-current mailing list, emu10kx driver + * discussion around 2006-05-24. */ + snprintf(sysctl_name, 32, "_%s", mix_name); + SYSCTL_ADD_PROC(sc->ctx, + SYSCTL_CHILDREN(sc->root), + OID_AUTO, sysctl_name, + CTLTYPE_INT | CTLFLAG_RW, sc, mix_id, + sysctl_emu_mixer_control, "I",""); + } + + return (volgpr); +} + +/* allocate cache GPRs that will hold mixed output channels + * and clear it on every DSP run. + */ +#define EFX_CACHE(CACHE_IDX) do { \ + sc->cache_gpr[CACHE_IDX] = emu_rm_gpr_alloc(sc->rm, 1); \ + emu_addefxop(sc, ACC3, \ + GPR(sc->cache_gpr[CACHE_IDX]), \ + DSP_CONST(0), \ + DSP_CONST(0), \ + DSP_CONST(0), \ + &pc); \ +} while (0) + +/* Allocate GPR for volume control and route sound: OUT = OUT + IN * VOL */ +#define EFX_ROUTE(TITLE, INP_NR, IN_GPR_IDX, OUT_CACHE_IDX, DEF) do { \ + sc->mixer_gpr[IN_GPR_IDX] = emu_addefxmixer(sc, TITLE, IN_GPR_IDX, DEF); \ + sc->mixer_volcache[IN_GPR_IDX] = DEF; \ + emu_addefxop(sc, MACS, \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + INP_NR, \ + GPR(sc->mixer_gpr[IN_GPR_IDX]), \ + &pc); \ +} while (0) + +/* allocate GPR, OUT = IN * VOL */ +#define EFX_OUTPUT(TITLE,OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR, DEF) do { \ + sc->mixer_gpr[OUT_GPR_IDX] = emu_addefxmixer(sc, TITLE, OUT_GPR_IDX, DEF); \ + sc->mixer_volcache[OUT_GPR_IDX] = DEF; \ + emu_addefxop(sc, MACS, \ + OUTP(OUTP_NR), \ + DSP_CONST(0), \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + GPR(sc->mixer_gpr[OUT_GPR_IDX]), \ + &pc); \ +} while (0) + +/* like EFX_OUTPUT, but don't allocate mixer gpr */ +#define EFX_OUTPUTD(OUT_CACHE_IDX, OUT_GPR_IDX, OUTP_NR) do{ \ + emu_addefxop(sc, MACS, \ + OUTP(OUTP_NR), \ + DSP_CONST(0), \ + GPR(sc->cache_gpr[OUT_CACHE_IDX]), \ + GPR(sc->mixer_gpr[OUT_GPR_IDX]), \ + &pc); \ +} while(0) + +/* mute, if FLAG != 0 */ +/* XXX */ +#define EFX_MUTEIF(GPR_IDX, FLAG) do { \ +} while(0) + +/* allocate dummy GPR. It's content will be used somewhere */ +#define EFX_DUMMY(DUMMY_IDX, DUMMY_VALUE) do { \ + sc->dummy_gpr[DUMMY_IDX] = emu_rm_gpr_alloc(sc->rm, 1); \ + emumix_set_gpr(sc, sc->dummy_gpr[DUMMY_IDX], DUMMY_VALUE); \ + emu_addefxop(sc, ACC3, \ + FX2(DUMMY_IDX), \ + GPR(sc->dummy_gpr[DUMMY_IDX]), \ + DSP_CONST(0), \ + DSP_CONST(0), \ + &pc); \ +} while (0) + + +static void +emu_initefx(struct emu_sc_info *sc) +{ + unsigned int i; + uint32_t pc; + + /* stop DSP */ + if (sc->is_emu10k1) { + emu_wrptr(sc, 0, DBG, EMU10K1_DBG_SINGLE_STEP); + } else { + emu_wrptr(sc, 0, A_DBG, A_DBG_SINGLE_STEP); + } + + /* code size is in instructions */ + pc = 0; + for (i = 0; i < sc->code_size; i++) { + if (sc->is_emu10k1) { + emu_addefxop(sc, ACC3, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0x0), &pc); + } else { + emu_addefxop(sc, SKIP, DSP_CONST(0x0), DSP_CONST(0x0), DSP_CONST(0xf), DSP_CONST(0x0), &pc); + } + } + + pc = 0; + + /* + * DSP code below is not good, because: + * 1. It can be written smaller, if it can use DSP accumulator register + * instead of cache_gpr[]. + * 2. It can be more careful when volume is 100%, because in DSP + * x*0x7fffffff may not be equal to x ! + */ + + /* clean outputs */ + for (i = 0; i < 16 ; i++) { + emu_addefxop(sc, ACC3, OUTP(i), DSP_CONST(0), DSP_CONST(0), DSP_CONST(0), &pc); + } + + + if (sc->is_emu10k1) { + EFX_CACHE(C_FRONT_L); + EFX_CACHE(C_FRONT_R); + EFX_CACHE(C_REC_L); + EFX_CACHE(C_REC_R); + + /* fx0 to front/record, 100%/muted by default */ + EFX_ROUTE("pcm_front_l", FX(0), M_FX0_FRONT_L, C_FRONT_L, 100); + EFX_ROUTE("pcm_front_r", FX(1), M_FX1_FRONT_R, C_FRONT_R, 100); + EFX_ROUTE("pcm_rec_l", FX(0), M_FX0_REC_L, C_REC_L, 0); + EFX_ROUTE("pcm_rec_r", FX(1), M_FX1_REC_R, C_REC_R, 0); + + /* in0, from AC97 codec output */ + EFX_ROUTE("ac97_front_l", INP(IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("ac97_front_r", INP(IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("ac97_rec_l", INP(IN_AC97_L), M_IN0_REC_L, C_REC_L, 0); + EFX_ROUTE("ac97_rec_r", INP(IN_AC97_R), M_IN0_REC_R, C_REC_R, 0); + + /* in1, from CD S/PDIF */ + EFX_ROUTE("cdspdif_front_l", INP(IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0); + EFX_MUTEIF(M_IN1_FRONT_L, CDSPDIFMUTE); + EFX_ROUTE("cdspdif_front_r", INP(IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0); + EFX_MUTEIF(M_IN1_FRONT_R, CDSPDIFMUTE); + EFX_ROUTE("cdspdif_rec_l", INP(IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0); + EFX_MUTEIF(M_IN1_REC_L, CDSPDIFMUTE); + EFX_ROUTE("cdspdif_rec_r", INP(IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0); + EFX_MUTEIF(M_IN1_REC_L, CDSPDIFMUTE); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in2, ZoomVide (???) */ + EFX_ROUTE("zoom_front_l", INP(IN_ZOOM_L), M_IN2_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("zoom_front_r", INP(IN_ZOOM_R), M_IN2_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("zoom_rec_l", INP(IN_ZOOM_L), M_IN2_REC_L, C_REC_L, 0); + EFX_ROUTE("zoom_rec_r", INP(IN_ZOOM_R), M_IN2_REC_R, C_REC_R, 0); +#endif +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in3, TOSLink (???) */ + EFX_ROUTE("toslink_front_l", INP(IN_TOSLINK_L), M_IN3_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("toslink_front_r", INP(IN_TOSLINK_R), M_IN3_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("toslink_rec_l", INP(IN_TOSLINK_L), M_IN3_REC_L, C_REC_L, 0); + EFX_ROUTE("toslink_rec_r", INP(IN_TOSLINK_R), M_IN3_REC_R, C_REC_R, 0); +#endif + /* in4, LineIn */ + EFX_ROUTE("linein_front_l", INP(IN_LINE1_L), M_IN4_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("linein_front_r", INP(IN_LINE1_R), M_IN4_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("linein_rec_l", INP(IN_LINE1_L), M_IN4_REC_L, C_REC_L, 0); + EFX_ROUTE("linein_rec_r", INP(IN_LINE1_R), M_IN4_REC_R, C_REC_R, 0); + + /* in5, on-card S/PDIF */ + EFX_ROUTE("spdif_front_l", INP(IN_COAX_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("spdif_front_r", INP(IN_COAX_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("spdif_rec_l", INP(IN_COAX_SPDIF_L), M_IN5_REC_L, C_REC_L, 0); + EFX_ROUTE("spdif_rec_r", INP(IN_COAX_SPDIF_R), M_IN5_REC_R, C_REC_R, 0); + + /* in6, Line2 on Live!Drive */ + EFX_ROUTE("line2_front_l", INP(IN_LINE2_L), M_IN6_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("line2_front_r", INP(IN_LINE2_R), M_IN6_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("line2_rec_l", INP(IN_LINE2_L), M_IN6_REC_L, C_REC_L, 0); + EFX_ROUTE("line2_rec_r", INP(IN_LINE2_R), M_IN6_REC_R, C_REC_R, 0); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in7, unknown */ + EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0); + EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0); +#endif + /* front output to hedaphones and both analog and digital */ + EFX_OUTPUT("master_front_l", C_FRONT_L, M_MASTER_FRONT_L, OUT_AC97_L, 100); + EFX_OUTPUT("master_front_r", C_FRONT_R, M_MASTER_FRONT_R, OUT_AC97_R, 100); + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_HEADPHONE_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_HEADPHONE_R); + + /* rec output to "ADC" */ + EFX_OUTPUT("master_rec_l", C_REC_L, M_MASTER_REC_L, OUT_ADC_REC_L, 100); + EFX_OUTPUT("master_rec_r", C_REC_R, M_MASTER_REC_R, OUT_ADC_REC_R, 100); +#ifdef SND_EMU10KX_MULTICHANNEL + /* + * Additional channel volume is controlled by mixer in + * emu_dspmixer_set() in -pcm.c + */ + + /* fx2/3 (pcm1) to rear */ + EFX_CACHE(C_REAR_L); + EFX_CACHE(C_REAR_R); + EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100); + EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100); + + EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, OUT_REAR_L, 100); + EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, OUT_REAR_R, 100); + if (sc->has_51) { + /* fx4 (pcm2) to center */ + EFX_CACHE(C_CENTER); + EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100); + EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, OUT_D_CENTER, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, OUT_A_CENTER); +#endif + /* fx5 (pcm3) to sub */ + EFX_CACHE(C_SUB); + EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100); + EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, OUT_D_SUB, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, OUT_A_SUB); +#endif + } +#ifdef SND_EMU10KX_MCH_RECORDING + /* MCH RECORDING , hight 16 slots. On 5.1 cards first 4 slots are used + as outputs and already filled with data */ + for(i = (sc->has_51 ? 4 : 0); i < 16; i++) { + /* XXX fill with dummy data */ + EFX_DUMMY(i,i*0x10000); + emu_addefxop(sc, ACC3, + FX2(i), + DSP_CONST(0), + DSP_CONST(0), + GPR(sc->dummy_gpr[i]), + &pc); + + } +#endif +#else /* !SND_EMU10KX_MULTICHANNEL */ + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, OUT_REAR_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, OUT_REAR_R); +#endif + } else /* emu10k2 and later */ { + EFX_CACHE(C_FRONT_L); + EFX_CACHE(C_FRONT_R); + EFX_CACHE(C_REC_L); + EFX_CACHE(C_REC_R); + + /* fx0 to front/record, 100%/muted by default */ + /* + * FRONT_[L|R] is controlled by AC97 emulation in + * emu_ac97_[read|write]_emulation in -pcm.c + */ + EFX_ROUTE(NULL, FX(0), M_FX0_FRONT_L, C_FRONT_L, 100); + EFX_ROUTE(NULL, FX(1), M_FX1_FRONT_R, C_FRONT_R, 100); + EFX_ROUTE("pcm_rec_l", FX(0), M_FX0_REC_L, C_REC_L, 0); + EFX_ROUTE("pcm_rec_r", FX(1), M_FX1_REC_R, C_REC_R, 0); + + /* in0, from AC97 codec output */ + EFX_ROUTE("ac97_front_l", INP(A_IN_AC97_L), M_IN0_FRONT_L, C_FRONT_L, 100); + EFX_ROUTE("ac97_front_r", INP(A_IN_AC97_R), M_IN0_FRONT_R, C_FRONT_R, 100); + EFX_ROUTE("ac97_rec_l", INP(A_IN_AC97_L), M_IN0_REC_L, C_REC_L, 0); + EFX_ROUTE("ac97_rec_r", INP(A_IN_AC97_R), M_IN0_REC_R, C_REC_R, 0); + + /* in1, from CD S/PDIF */ + EFX_ROUTE("cdspdif_front_l", INP(A_IN_SPDIF_CD_L), M_IN1_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("cdspdif_front_r", INP(A_IN_SPDIF_CD_R), M_IN1_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("cdspdif_rec_l", INP(A_IN_SPDIF_CD_L), M_IN1_REC_L, C_REC_L, 0); + EFX_ROUTE("cdspdif_rec_r", INP(A_IN_SPDIF_CD_R), M_IN1_REC_R, C_REC_R, 0); + + /* in2, optical & coax S/PDIF on AudigyDrive*/ + /* XXX Should be muted when GPRSCS valid stream == 0 */ + EFX_ROUTE("ospdif_front_l", INP(A_IN_O_SPDIF_L), M_IN2_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("ospdif_front_r", INP(A_IN_O_SPDIF_R), M_IN2_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("ospdif_rec_l", INP(A_IN_O_SPDIF_L), M_IN2_REC_L, C_REC_L, 0); + EFX_ROUTE("ospdif_rec_r", INP(A_IN_O_SPDIF_R), M_IN2_REC_R, C_REC_R, 0); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in3, unknown */ + EFX_ROUTE("in3_front_l", INP(0x6), M_IN3_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("in3_front_r", INP(0x7), M_IN3_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("in3_rec_l", INP(0x6), M_IN3_REC_L, C_REC_L, 0); + EFX_ROUTE("in3_rec_r", INP(0x7), M_IN3_REC_R, C_REC_R, 0); +#endif + /* in4, LineIn 2 on AudigyDrive */ + EFX_ROUTE("linein2_front_l", INP(A_IN_LINE2_L), M_IN4_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("linein2_front_r", INP(A_IN_LINE2_R), M_IN4_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("linein2_rec_l", INP(A_IN_LINE2_L), M_IN4_REC_L, C_REC_L, 0); + EFX_ROUTE("linein2_rec_r", INP(A_IN_LINE2_R), M_IN4_REC_R, C_REC_R, 0); + + /* in5, on-card S/PDIF */ + EFX_ROUTE("spdif_front_l", INP(A_IN_R_SPDIF_L), M_IN5_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("spdif_front_r", INP(A_IN_R_SPDIF_R), M_IN5_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("spdif_rec_l", INP(A_IN_R_SPDIF_L), M_IN5_REC_L, C_REC_L, 0); + EFX_ROUTE("spdif_rec_r", INP(A_IN_R_SPDIF_R), M_IN5_REC_R, C_REC_R, 0); + + /* in6, AUX2 on AudigyDrive */ + EFX_ROUTE("aux2_front_l", INP(A_IN_AUX2_L), M_IN6_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("aux2_front_r", INP(A_IN_AUX2_R), M_IN6_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("aux2_rec_l", INP(A_IN_AUX2_L), M_IN6_REC_L, C_REC_L, 0); + EFX_ROUTE("aux2_rec_r", INP(A_IN_AUX2_R), M_IN6_REC_R, C_REC_R, 0); +#ifdef SND_EMU10KX_DEBUG_OUTPUTS + /* in7, unknown */ + EFX_ROUTE("in7_front_l", INP(0xE), M_IN7_FRONT_L, C_FRONT_L, 0); + EFX_ROUTE("in7_front_r", INP(0xF), M_IN7_FRONT_R, C_FRONT_R, 0); + EFX_ROUTE("in7_rec_l", INP(0xE), M_IN7_REC_L, C_REC_L, 0); + EFX_ROUTE("in7_rec_r", INP(0xF), M_IN7_REC_R, C_REC_R, 0); +#endif + /* front output to headphones and alog and digital *front */ + /* volume controlled by AC97 emulation */ + EFX_OUTPUT(NULL, C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_FRONT_L, 100); + EFX_OUTPUT(NULL, C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_FRONT_R, 100); + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_FRONT_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_FRONT_R); + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_HPHONE_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_HPHONE_R); + + /* rec output to "ADC" */ + /* volume controlled by AC97 emulation */ + EFX_OUTPUT(NULL, C_REC_L, M_MASTER_REC_L, A_OUT_ADC_REC_L, 100); + EFX_OUTPUT(NULL, C_REC_R, M_MASTER_REC_R, A_OUT_ADC_REC_R, 100); +#ifdef SND_EMU10KX_MULTICHANNEL + /* + * Additional channel volume is controlled by mixer in + * emu_dspmixer_set() in -pcm.c + */ + + /* fx2/3 (pcm1) to rear */ + EFX_CACHE(C_REAR_L); + EFX_CACHE(C_REAR_R); + EFX_ROUTE(NULL, FX(2), M_FX2_REAR_L, C_REAR_L, 100); + EFX_ROUTE(NULL, FX(3), M_FX3_REAR_R, C_REAR_R, 100); + + EFX_OUTPUT(NULL, C_REAR_L, M_MASTER_REAR_L, A_OUT_A_REAR_L, 100); + EFX_OUTPUT(NULL, C_REAR_R, M_MASTER_REAR_R, A_OUT_A_REAR_R, 100); + EFX_OUTPUTD(C_REAR_L, M_MASTER_REAR_L, A_OUT_D_REAR_L); + EFX_OUTPUTD(C_REAR_R, M_MASTER_REAR_R, A_OUT_D_REAR_R); + + /* fx4 (pcm2) to center */ + EFX_CACHE(C_CENTER); + EFX_ROUTE(NULL, FX(4), M_FX4_CENTER, C_CENTER, 100); + EFX_OUTPUT(NULL, C_CENTER, M_MASTER_CENTER, A_OUT_D_CENTER, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_CENTER, M_MASTER_CENTER, A_OUT_A_CENTER); +#endif + /* fx5 (pcm3) to sub */ + EFX_CACHE(C_SUB); + EFX_ROUTE(NULL, FX(5), M_FX5_SUBWOOFER, C_SUB, 100); + EFX_OUTPUT(NULL, C_SUB, M_MASTER_SUBWOOFER, A_OUT_D_SUB, 100); +#if 0 + /* XXX in digital mode (default) this should be muted because + this output is shared with digital out */ + EFX_OUTPUTD(C_SUB, M_MASTER_SUBWOOFER, A_OUT_A_SUB); +#endif + if (sc->has_71) { + /* XXX this will broke headphones on AudigyDrive */ + /* fx6/7 (pcm4) to side */ + EFX_CACHE(C_SIDE_L); + EFX_CACHE(C_SIDE_R); + EFX_ROUTE(NULL, FX(6), M_FX6_SIDE_L, C_SIDE_L, 100); + EFX_ROUTE(NULL, FX(7), M_FX7_SIDE_R, C_SIDE_R, 100); + EFX_OUTPUT(NULL, C_SIDE_L, M_MASTER_SIDE_L, A_OUT_A_SIDE_L, 100); + EFX_OUTPUT(NULL, C_SIDE_R, M_MASTER_SIDE_R, A_OUT_A_SIDE_R, 100); + EFX_OUTPUTD(C_SIDE_L, M_MASTER_SIDE_L, A_OUT_D_SIDE_L); + EFX_OUTPUTD(C_SIDE_R, M_MASTER_SIDE_R, A_OUT_D_SIDE_R); + } +#ifdef SND_EMU10KX_MCH_RECORDING + /* MCH RECORDING, high 32 slots */ + for(i = 0; i < 32; i++) { + /* XXX fill with dummy data */ + EFX_DUMMY(i,i*0x10000); + emu_addefxop(sc, ACC3, + FX2(i), + DSP_CONST(0), + DSP_CONST(0), + GPR(sc->dummy_gpr[i]), + &pc); + } +#endif +#else /* !SND_EMU10KX_MULTICHANNEL */ + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_A_REAR_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_A_REAR_R); + + EFX_OUTPUTD(C_FRONT_L, M_MASTER_FRONT_L, A_OUT_D_REAR_L); + EFX_OUTPUTD(C_FRONT_R, M_MASTER_FRONT_R, A_OUT_D_REAR_R); +#endif + } + + sc->routing_code_end = pc; + + /* start DSP */ + if (sc->is_emu10k1) { + emu_wrptr(sc, 0, DBG, 0); + } else { + emu_wrptr(sc, 0, A_DBG, 0); + } +} + +/* /dev/em10kx */ +static d_open_t emu10kx_open; +static d_close_t emu10kx_close; +static d_read_t emu10kx_read; + +static struct cdevsw emu10kx_cdevsw = { + .d_open = emu10kx_open, + .d_close = emu10kx_close, + .d_read = emu10kx_read, + .d_name = "emu10kx", + .d_version = D_VERSION, +}; + + +static int +emu10kx_open(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) +{ + int error; + struct emu_sc_info *sc; + + sc = i_dev->si_drv1; + mtx_lock(&sc->emu10kx_lock); + if (sc->emu10kx_isopen) { + mtx_unlock(&sc->emu10kx_lock); + return (EBUSY); + } + sc->emu10kx_isopen = 1; + mtx_unlock(&sc->emu10kx_lock); + if (sbuf_new(&sc->emu10kx_sbuf, NULL, 4096, 0) == NULL) { + error = ENXIO; + goto out; + } + sc->emu10kx_bufptr = 0; + error = (emu10kx_prepare(sc, &sc->emu10kx_sbuf) > 0) ? 0 : ENOMEM; +out: + if (error) { + mtx_lock(&sc->emu10kx_lock); + sc->emu10kx_isopen = 0; + mtx_unlock(&sc->emu10kx_lock); + } + return (error); +} + +static int +emu10kx_close(struct cdev *i_dev, int flags __unused, int mode __unused, struct thread *td __unused) +{ + struct emu_sc_info *sc; + + sc = i_dev->si_drv1; + + mtx_lock(&sc->emu10kx_lock); + if (!(sc->emu10kx_isopen)) { + mtx_unlock(&sc->emu10kx_lock); + return (EBADF); + } + sbuf_delete(&sc->emu10kx_sbuf); + sc->emu10kx_isopen = 0; + mtx_unlock(&sc->emu10kx_lock); + + return (0); +} + +static int +emu10kx_read(struct cdev *i_dev, struct uio *buf, int flag __unused) +{ + int l, err; + struct emu_sc_info *sc; + + sc = i_dev->si_drv1; + mtx_lock(&sc->emu10kx_lock); + if (!(sc->emu10kx_isopen)) { + mtx_unlock(&sc->emu10kx_lock); + return (EBADF); + } + mtx_unlock(&sc->emu10kx_lock); + + l = min(buf->uio_resid, sbuf_len(&sc->emu10kx_sbuf) - sc->emu10kx_bufptr); + err = (l > 0) ? uiomove(sbuf_data(&sc->emu10kx_sbuf) + sc->emu10kx_bufptr, l, buf) : 0; + sc->emu10kx_bufptr += l; + + return (err); +} + +static int +emu10kx_prepare(struct emu_sc_info *sc, struct sbuf *s) +{ + int i; + + sbuf_printf(s, "FreeBSD EMU10Kx Audio Driver\n"); + sbuf_printf(s, "\nHardware resource usage:\n"); + sbuf_printf(s, "DSP General Purpose Registers: %d used, %d total\n", sc->rm->num_used, sc->rm->num_gprs); + sbuf_printf(s, "DSP Instruction Registers: %d used, %d total\n", sc->routing_code_end, sc->code_size); + sbuf_printf(s, "Card supports"); + if (sc->has_ac97) { + sbuf_printf(s, " AC97 codec"); + } else { + sbuf_printf(s, " NO AC97 codec"); + } + if (sc->has_51) { + if (sc->has_71) + sbuf_printf(s, " and 7.1 output"); + else + sbuf_printf(s, " and 5.1 output"); + } + if (sc->is_emu10k1) + sbuf_printf(s, ", SBLive! DSP code"); + if (sc->is_emu10k2) + sbuf_printf(s, ", Audigy DSP code"); + if (sc->is_ca0102) + sbuf_printf(s, ", Audigy DSP code with Audigy2 hacks"); + if (sc->is_ca0108) + sbuf_printf(s, ", Audigy DSP code with Audigy2Value hacks"); + sbuf_printf(s, "\n"); + if (sc->broken_digital) + sbuf_printf(s, "Digital mode unsupported\n"); + sbuf_printf(s, "\nInstalled devices:\n"); + for (i = 0; i < RT_COUNT; i++) + if (sc->pcm[i] != NULL) + if (device_is_attached(sc->pcm[i])) { + sbuf_printf(s, "%s on %s\n", device_get_desc(sc->pcm[i]), device_get_nameunit(sc->pcm[i])); + } + if (sc->midi[0] != NULL) + if (device_is_attached(sc->midi[0])) { + sbuf_printf(s, "EMU10Kx MIDI Interface\n"); + sbuf_printf(s, "\tOn-card connector on %s\n", device_get_nameunit(sc->midi[0])); + } + if (sc->midi[1] != NULL) + if (device_is_attached(sc->midi[1])) { + sbuf_printf(s, "\tOn-Drive connector on %s\n", device_get_nameunit(sc->midi[1])); + } + if (sc->midi[0] != NULL) + if (device_is_attached(sc->midi[0])) { + sbuf_printf(s, "\tIR reciever MIDI events %s\n", sc->enable_ir ? "enabled" : "disabled"); + } + sbuf_finish(s); + return (sbuf_len(s)); +} + +/* INIT & UNINIT */ +static int +emu10kx_dev_init(struct emu_sc_info *sc) +{ + int unit; + + mtx_init(&sc->emu10kx_lock, "kxdevlock", NULL, 0); + unit = device_get_unit(sc->dev); + + sc->cdev = make_dev(&emu10kx_cdevsw, unit2minor(unit), UID_ROOT, GID_WHEEL, 0640, "emu10kx%d", unit); + if (sc->cdev != NULL) { + sc->cdev->si_drv1 = sc; + return (0); + } + return (ENXIO); +} + +static int +emu10kx_dev_uninit(struct emu_sc_info *sc) +{ + intrmask_t s; + + s = spltty(); + mtx_lock(&sc->emu10kx_lock); + if (sc->emu10kx_isopen) { + mtx_unlock(&sc->emu10kx_lock); + splx(s); + return (EBUSY); + } + if (sc->cdev) + destroy_dev(sc->cdev); + sc->cdev = 0; + + splx(s); + mtx_destroy(&sc->emu10kx_lock); + return (0); +} + +/* resource manager */ +int +emu_rm_init(struct emu_sc_info *sc) +{ + int i; + int maxcount; + struct emu_rm *rm; + + rm = malloc(sizeof(struct emu_rm), M_DEVBUF, M_NOWAIT | M_ZERO); + if (rm == NULL) { + return (ENOMEM); + } + sc->rm = rm; + rm->card = sc; + maxcount = sc->num_gprs; + rm->num_used = 0; + mtx_init(&(rm->gpr_lock), "emu10k", "gpr alloc", MTX_DEF); + rm->num_gprs = (maxcount < EMU_MAX_GPR ? maxcount : EMU_MAX_GPR); + for (i = 0; i < rm->num_gprs; i++) + rm->allocmap[i] = 0; + rm->last_free_gpr = 0; + + return (0); +} + +int +emu_rm_uninit(struct emu_sc_info *sc) +{ +#ifdef SND_EMU10KX_DEBUG + int i; + + mtx_lock(&(sc->rm->gpr_lock)); + for (i = 0; i < sc->rm->last_free_gpr; i++) + if (sc->rm->allocmap[i] > 0) + device_printf(sc->dev, "rm: gpr %d not free before uninit\n", i); + mtx_unlock(&(sc->rm->gpr_lock)); +#endif + mtx_destroy(&(sc->rm->gpr_lock)); + free(sc->rm, M_DEVBUF); + return (0); +} + +static int +emu_rm_gpr_alloc(struct emu_rm *rm, int count) +{ + int i, j; + int allocated_gpr; + + allocated_gpr = rm->num_gprs; + /* try fast way first */ + mtx_lock(&(rm->gpr_lock)); + if (rm->last_free_gpr + count <= rm->num_gprs) { + allocated_gpr = rm->last_free_gpr; + rm->last_free_gpr += count; + rm->allocmap[allocated_gpr] = count; + for (i = 1; i < count; i++) + rm->allocmap[allocated_gpr + i] = -(count - i); + } else { + /* longer */ + i = 0; + allocated_gpr = rm->num_gprs; + while (i < rm->last_free_gpr - count) { + if (rm->allocmap[i] > 0) { + i += rm->allocmap[i]; + } else { + allocated_gpr = i; + for (j = 1; j < count; j++) { + if (rm->allocmap[i + j] != 0) + allocated_gpr = rm->num_gprs; + } + if (allocated_gpr == i) + break; + } + } + if (allocated_gpr + count < rm->last_free_gpr) { + rm->allocmap[allocated_gpr] = count; + for (i = 1; i < count; i++) + rm->allocmap[allocated_gpr + i] = -(count - i); + + } + } + if (allocated_gpr == rm->num_gprs) + allocated_gpr = (-1); + if (allocated_gpr >= 0) + rm->num_used += count; + mtx_unlock(&(rm->gpr_lock)); + return (allocated_gpr); +} + +/* mixer */ +void +emumix_set_mode(struct emu_sc_info *sc, int mode) +{ + uint32_t a_iocfg; + uint32_t hcfg; + uint32_t tmp; + + switch (mode) { + case MODE_DIGITAL: + /* FALLTHROUGH */ + case MODE_ANALOG: + break; + default: + return; + } + + hcfg = HCFG_AUDIOENABLE | HCFG_AUTOMUTE; + a_iocfg = 0; + + if (sc->rev >= 6) + hcfg |= HCFG_JOYENABLE; + + if (sc->is_emu10k1) + hcfg |= HCFG_LOCKTANKCACHE_MASK; + else + hcfg |= HCFG_CODECFORMAT_I2S | HCFG_JOYENABLE; + + + if (mode == MODE_DIGITAL) { + if (sc->broken_digital) { + device_printf(sc->dev, "Digital mode is reported as broken on this card,\n"); + } + a_iocfg |= A_IOCFG_ENABLE_DIGITAL; + hcfg |= HCFG_GPOUT0; + } + + if (mode == MODE_ANALOG) + emumix_set_spdif_mode(sc, SPDIF_MODE_PCM); + + if (sc->is_emu10k2) + a_iocfg |= 0x80; /* XXX */ + + if ((sc->is_ca0102) || (sc->is_ca0108)) + a_iocfg |= A_IOCFG_DISABLE_ANALOG; /* means "don't disable" + on this two cards. Means "disable" on emu10k2. */ + + if (sc->is_ca0108) + a_iocfg |= 0x20; /* XXX */ + + emu_wr(sc, HCFG, hcfg, 4); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + tmp = emu_rd(sc, A_IOCFG, 2); + tmp = a_iocfg; + emu_wr(sc, A_IOCFG, tmp, 2); + } + + /* + * XXX Mute center/sub if we go digital on Audigy or later card. + * Route to analog center / sub in emu_initef should be disabled + * until this problem is fixed. + */ +} + +void +emumix_set_spdif_mode(struct emu_sc_info *sc, int mode) +{ + uint32_t spcs; + + switch (mode) { + case SPDIF_MODE_PCM: + break; + case SPDIF_MODE_AC3: + device_printf(sc->dev, "AC3 mode does not work and disabled\n"); + return; + default: + return; + } + + spcs = SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 | + SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC | + SPCS_GENERATIONSTATUS | 0x00001200 | 0x00000000 | + SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT; + + mode = SPDIF_MODE_PCM; + + emu_wrptr(sc, 0, SPCS0, spcs); + emu_wrptr(sc, 0, SPCS1, spcs); + emu_wrptr(sc, 0, SPCS2, spcs); +} + +#define L2L_POINTS 10 + +static int l2l_df[L2L_POINTS] = { + 0x572C5CA, /* 100..90 */ + 0x3211625, /* 90..80 */ + 0x1CC1A76, /* 80..70 */ + 0x108428F, /* 70..60 */ + 0x097C70A, /* 60..50 */ + 0x0572C5C, /* 50..40 */ + 0x0321162, /* 40..30 */ + 0x01CC1A7, /* 30..20 */ + 0x0108428, /* 20..10 */ + 0x016493D /* 10..0 */ +}; + +static int l2l_f[L2L_POINTS] = { + 0x4984461A, /* 90 */ + 0x2A3968A7, /* 80 */ + 0x18406003, /* 70 */ + 0x0DEDC66D, /* 60 */ + 0x07FFFFFF, /* 50 */ + 0x04984461, /* 40 */ + 0x02A3968A, /* 30 */ + 0x01840600, /* 20 */ + 0x00DEDC66, /* 10 */ + 0x00000000 /* 0 */ +}; + + +static int +log2lin(int log_t) +{ + int lin_t; + int idx, lin; + + if (log_t <= 0) { + lin_t = 0x00000000; + return (lin_t); + } + + if (log_t >= 100) { + lin_t = 0x7fffffff; + return (lin_t); + } + + idx = (L2L_POINTS - 1) - log_t / (L2L_POINTS); + lin = log_t % (L2L_POINTS); + lin_t = l2l_df[idx] * lin + l2l_f[idx]; + return (lin_t); +} + + +void +emumix_set_fxvol(struct emu_sc_info *sc, unsigned gpr, int32_t vol) +{ + + vol = log2lin(vol); + emumix_set_gpr(sc, gpr, vol); +} + +void +emumix_set_gpr(struct emu_sc_info *sc, unsigned gpr, int32_t val) +{ + + emu_wrptr(sc, 0, GPR(gpr), val); +} + +void +emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume) +{ + + RANGE(volume, 0, 100); + if (mixer_idx < NUM_MIXERS) { + sc->mixer_volcache[mixer_idx] = volume; + emumix_set_fxvol(sc, sc->mixer_gpr[mixer_idx], volume); + } +} + +int +emumix_get_volume(struct emu_sc_info *sc, int mixer_idx) +{ + if ((mixer_idx < NUM_MIXERS) && (mixer_idx >= 0)) + return (sc->mixer_volcache[mixer_idx]); + return (-1); +} + +/* Init CardBus part */ +static int +emu_cardbus_init(struct emu_sc_info *sc) +{ + + /* + * XXX May not need this if we have IPR3 handler. + * Is it a real init calls, or IPR3 interrupt acknowledgments? + * Looks much like "(data << 16) | register". + */ + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0000); + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x0001); + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x005f); + emu_wr_cbptr(sc, (0x00d0 << 16) | 0x007f); + + emu_wr_cbptr(sc, (0x0090 << 16) | 0x007f); + + return (0); +} + +/* Probe and attach the card */ +static int +emu_init(struct emu_sc_info *sc) +{ + uint32_t ch, tmp; + uint32_t spdif_sr; + uint32_t ac97slot; + int def_mode; + int i; + + /* disable audio and lock cache */ + emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4); + + /* reset recording buffers */ + emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, MICBA, 0); + emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, FXBA, 0); + emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, ADCBA, 0); + + /* disable channel interrupt */ + emu_wr(sc, INTE, INTE_INTERVALTIMERENB | INTE_SAMPLERATETRACKER | INTE_PCIERRORENABLE, 4); + emu_wrptr(sc, 0, CLIEL, 0); + emu_wrptr(sc, 0, CLIEH, 0); + emu_wrptr(sc, 0, SOLEL, 0); + emu_wrptr(sc, 0, SOLEH, 0); + + /* disable P16V and S/PDIF interrupts */ + if ((sc->is_ca0102) || (sc->is_ca0108)) + emu_wr(sc, INTE2, 0, 4); + + if (sc->is_ca0102) + emu_wr(sc, INTE3, 0, 4); + + /* init phys inputs and outputs */ + ac97slot = 0; + if (sc->has_51) + ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE; + if (sc->has_71) + ac97slot = AC97SLOT_CNTR | AC97SLOT_LFE | AC97SLOT_REAR_LEFT | AC97SLOT_REAR_RIGHT; + if (sc->is_emu10k2) + ac97slot |= 0x40; + emu_wrptr(sc, 0, AC97SLOT, ac97slot); + + if (sc->is_emu10k2) /* XXX for later cards? */ + emu_wrptr(sc, 0, SPBYPASS, 0xf00); /* What will happen if + * we write 1 here? */ + + if (bus_dma_tag_create( /* parent */ bus_get_dma_tag(sc->dev), + /* alignment */ 2, /* boundary */ 0, + /* lowaddr */ 1 << 31, /* can only access 0-2gb */ + /* highaddr */ BUS_SPACE_MAXADDR, + /* filter */ NULL, /* filterarg */ NULL, + /* maxsize */ EMU_MAX_BUFSZ, /* nsegments */ 1, /* maxsegz */ 0x3ffff, + /* flags */ 0, /* lockfunc */ busdma_lock_mutex, + /* lockarg */ &Giant, &(sc->mem.dmat)) != 0) { + device_printf(sc->dev, "unable to create dma tag\n"); + bus_dma_tag_destroy(sc->mem.dmat); + return (ENOMEM); + } + + SLIST_INIT(&sc->mem.blocks); + sc->mem.ptb_pages = emu_malloc(&sc->mem, EMU_MAXPAGES * sizeof(uint32_t), &sc->mem.ptb_pages_addr); + if (sc->mem.ptb_pages == NULL) + return (ENOMEM); + + sc->mem.silent_page = emu_malloc(&sc->mem, EMUPAGESIZE, &sc->mem.silent_page_addr); + if (sc->mem.silent_page == NULL) { + emu_free(&sc->mem, sc->mem.ptb_pages); + return (ENOMEM); + } + /* Clear page with silence & setup all pointers to this page */ + bzero(sc->mem.silent_page, EMUPAGESIZE); + tmp = (uint32_t) (sc->mem.silent_page_addr) << 1; + for (i = 0; i < EMU_MAXPAGES; i++) + sc->mem.ptb_pages[i] = tmp | i; + + for (ch = 0; ch < NUM_G; ch++) { + emu_wrptr(sc, ch, MAPA, tmp | MAP_PTI_MASK); + emu_wrptr(sc, ch, MAPB, tmp | MAP_PTI_MASK); + } + emu_wrptr(sc, 0, PTB, (sc->mem.ptb_pages_addr)); + emu_wrptr(sc, 0, TCB, 0); /* taken from original driver */ + emu_wrptr(sc, 0, TCBS, 0); /* taken from original driver */ + + /* init envelope engine */ + for (ch = 0; ch < NUM_G; ch++) { + emu_wrptr(sc, ch, DCYSUSV, 0); + emu_wrptr(sc, ch, IP, 0); + emu_wrptr(sc, ch, VTFT, 0xffff); + emu_wrptr(sc, ch, CVCF, 0xffff); + emu_wrptr(sc, ch, PTRX, 0); + emu_wrptr(sc, ch, CPF, 0); + emu_wrptr(sc, ch, CCR, 0); + + emu_wrptr(sc, ch, PSST, 0); + emu_wrptr(sc, ch, DSL, 0x10); + emu_wrptr(sc, ch, CCCA, 0); + emu_wrptr(sc, ch, Z1, 0); + emu_wrptr(sc, ch, Z2, 0); + emu_wrptr(sc, ch, FXRT, 0xd01c0000); + + emu_wrptr(sc, ch, ATKHLDM, 0); + emu_wrptr(sc, ch, DCYSUSM, 0); + emu_wrptr(sc, ch, IFATN, 0xffff); + emu_wrptr(sc, ch, PEFE, 0); + emu_wrptr(sc, ch, FMMOD, 0); + emu_wrptr(sc, ch, TREMFRQ, 24); /* 1 Hz */ + emu_wrptr(sc, ch, FM2FRQ2, 24); /* 1 Hz */ + emu_wrptr(sc, ch, TEMPENV, 0); + + /*** these are last so OFF prevents writing ***/ + emu_wrptr(sc, ch, LFOVAL2, 0); + emu_wrptr(sc, ch, LFOVAL1, 0); + emu_wrptr(sc, ch, ATKHLDV, 0); + emu_wrptr(sc, ch, ENVVOL, 0); + emu_wrptr(sc, ch, ENVVAL, 0); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + emu_wrptr(sc, ch, 0x4c, 0x0); + emu_wrptr(sc, ch, 0x4d, 0x0); + emu_wrptr(sc, ch, 0x4e, 0x0); + emu_wrptr(sc, ch, 0x4f, 0x0); + emu_wrptr(sc, ch, A_FXRT1, 0x3f3f3f3f); + emu_wrptr(sc, ch, A_FXRT2, 0x3f3f3f3f); + emu_wrptr(sc, ch, A_SENDAMOUNTS, 0x0); + } + } + + emumix_set_spdif_mode(sc, SPDIF_MODE_PCM); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) + emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, A_SPDIF_48000); + + /* + * CAxxxx cards needs additional setup: + * 1. Set I2S capture sample rate to 96000 + * 2. Disable P16v / P17v proceesing + * 3. Allow EMU10K DSP inputs + */ + if ((sc->is_ca0102) || (sc->is_ca0108)) { + + spdif_sr = emu_rdptr(sc, 0, A_SPDIF_SAMPLERATE); + spdif_sr &= 0xfffff1ff; + spdif_sr |= A_I2S_CAPTURE_96000; + emu_wrptr(sc, 0, A_SPDIF_SAMPLERATE, spdif_sr); + + /* Disable P16v processing */ + emu_wr_p16vptr(sc, 0, SRCSel, 0x14); + + /* Setup P16v/P17v sound routing */ + if (sc->is_ca0102) + emu_wr_p16vptr(sc, 0, SRCMULTI_ENABLE, 0xFF00FF00); + else { + emu_wr_p16vptr(sc, 0, P17V_MIXER_I2S_ENABLE, 0xFF000000); + emu_wr_p16vptr(sc, 0, P17V_MIXER_SPDIF_ENABLE, 0xFF000000); + + tmp = emu_rd(sc, A_IOCFG, 2); + emu_wr(sc, A_IOCFG, tmp & ~0x8, 2); + } + } + emu_initefx(sc); + + def_mode = MODE_ANALOG; + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) + def_mode = MODE_DIGITAL; + if (((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) && (sc->broken_digital)) { + device_printf(sc->dev, "Audigy card initialized in analog mode.\n"); + def_mode = MODE_ANALOG; + } + emumix_set_mode(sc, def_mode); + + if (bootverbose) { + tmp = emu_rd(sc, HCFG, 4); + device_printf(sc->dev, "Card Configuration ( 0x%08x )\n", tmp); + device_printf(sc->dev, "Card Configuration ( & 0xff000000 ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x80000000 ? "[Legacy MPIC] " : ""), + (tmp & 0x40000000 ? "[0x40] " : ""), + (tmp & 0x20000000 ? "[0x20] " : ""), + (tmp & 0x10000000 ? "[0x10] " : ""), + (tmp & 0x08000000 ? "[0x08] " : ""), + (tmp & 0x04000000 ? "[0x04] " : ""), + (tmp & 0x02000000 ? "[0x02] " : ""), + (tmp & 0x01000000 ? "[0x01]" : " ")); + device_printf(sc->dev, "Card Configuration ( & 0x00ff0000 ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x00800000 ? "[0x80] " : ""), + (tmp & 0x00400000 ? "[0x40] " : ""), + (tmp & 0x00200000 ? "[Legacy INT] " : ""), + (tmp & 0x00100000 ? "[0x10] " : ""), + (tmp & 0x00080000 ? "[0x08] " : ""), + (tmp & 0x00040000 ? "[Codec4] " : ""), + (tmp & 0x00020000 ? "[Codec2] " : ""), + (tmp & 0x00010000 ? "[I2S Codec]" : " ")); + device_printf(sc->dev, "Card Configuration ( & 0x0000ff00 ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x00008000 ? "[0x80] " : ""), + (tmp & 0x00004000 ? "[GPINPUT0] " : ""), + (tmp & 0x00002000 ? "[GPINPUT1] " : ""), + (tmp & 0x00001000 ? "[GPOUT0] " : ""), + (tmp & 0x00000800 ? "[GPOUT1] " : ""), + (tmp & 0x00000400 ? "[GPOUT2] " : ""), + (tmp & 0x00000200 ? "[Joystick] " : ""), + (tmp & 0x00000100 ? "[0x01]" : " ")); + device_printf(sc->dev, "Card Configuration ( & 0x000000ff ) : %s%s%s%s%s%s%s%s\n", + (tmp & 0x00000080 ? "[0x80] " : ""), + (tmp & 0x00000040 ? "[0x40] " : ""), + (tmp & 0x00000020 ? "[0x20] " : ""), + (tmp & 0x00000010 ? "[AUTOMUTE] " : ""), + (tmp & 0x00000008 ? "[LOCKSOUNDCACHE] " : ""), + (tmp & 0x00000004 ? "[LOCKTANKCACHE] " : ""), + (tmp & 0x00000002 ? "[MUTEBUTTONENABLE] " : ""), + (tmp & 0x00000001 ? "[AUDIOENABLE]" : " ")); + + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + tmp = emu_rd(sc, A_IOCFG, 2); + device_printf(sc->dev, "Audigy Card Configuration ( 0x%04x )\n", tmp); + device_printf(sc->dev, "Audigy Card Configuration ( & 0xff00 )"); + printf(" : %s%s%s%s%s%s%s%s\n", + (tmp & 0x8000 ? "[Rear Speakers] " : ""), + (tmp & 0x4000 ? "[Front Speakers] " : ""), + (tmp & 0x2000 ? "[0x20] " : ""), + (tmp & 0x1000 ? "[0x10] " : ""), + (tmp & 0x0800 ? "[0x08] " : ""), + (tmp & 0x0400 ? "[0x04] " : ""), + (tmp & 0x0200 ? "[0x02] " : ""), + (tmp & 0x0100 ? "[AudigyDrive Phones]" : " ")); + device_printf(sc->dev, "Audigy Card Configuration ( & 0x00ff )"); + printf(" : %s%s%s%s%s%s%s%s\n", + (tmp & 0x0080 ? "[0x80] " : ""), + (tmp & 0x0040 ? "[Mute AnalogOut] " : ""), + (tmp & 0x0020 ? "[0x20] " : ""), + (tmp & 0x0010 ? "[0x10] " : ""), + (tmp & 0x0008 ? "[0x08] " : ""), + (tmp & 0x0004 ? "[GPOUT0] " : ""), + (tmp & 0x0002 ? "[GPOUT1] " : ""), + (tmp & 0x0001 ? "[GPOUT2]" : " ")); + } /* is_emu10k2 or ca* */ + } /* bootverbose */ + return (0); +} + +static int +emu_uninit(struct emu_sc_info *sc) +{ + uint32_t ch; + struct emu_memblk *blk; + + emu_wr(sc, INTE, 0, 4); + for (ch = 0; ch < NUM_G; ch++) + emu_wrptr(sc, ch, DCYSUSV, 0); + for (ch = 0; ch < NUM_G; ch++) { + emu_wrptr(sc, ch, VTFT, 0); + emu_wrptr(sc, ch, CVCF, 0); + emu_wrptr(sc, ch, PTRX, 0); + emu_wrptr(sc, ch, CPF, 0); + } + + /* disable audio and lock cache */ + emu_wr(sc, HCFG, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, 4); + + emu_wrptr(sc, 0, PTB, 0); + /* reset recording buffers */ + emu_wrptr(sc, 0, MICBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, MICBA, 0); + emu_wrptr(sc, 0, FXBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, FXBA, 0); + emu_wrptr(sc, 0, FXWC, 0); + emu_wrptr(sc, 0, ADCBS, ADCBS_BUFSIZE_NONE); + emu_wrptr(sc, 0, ADCBA, 0); + emu_wrptr(sc, 0, TCB, 0); + emu_wrptr(sc, 0, TCBS, 0); + + /* disable channel interrupt */ + emu_wrptr(sc, 0, CLIEL, 0); + emu_wrptr(sc, 0, CLIEH, 0); + emu_wrptr(sc, 0, SOLEL, 0); + emu_wrptr(sc, 0, SOLEH, 0); + + if (!SLIST_EMPTY(&sc->mem.blocks)) + device_printf(sc->dev, "warning: memblock list not empty\n"); + + SLIST_FOREACH(blk, &sc->mem.blocks, link) + if (blk != NULL) + device_printf(sc->dev, "lost %d for %s\n", blk->pte_size, blk->owner); + + emu_free(&sc->mem, sc->mem.ptb_pages); + emu_free(&sc->mem, sc->mem.silent_page); + + return (0); +} + +static int +emu_read_ivar(device_t bus, device_t dev, int ivar_index, uintptr_t * result) +{ + struct sndcard_func *func = device_get_ivars(dev); + struct emu_sc_info *sc = device_get_softc(bus); + + switch (ivar_index) { + case EMU_VAR_FUNC: + *result = func->func; + break; + case EMU_VAR_ROUTE: + *result = ((struct emu_pcminfo *)func->varinfo)->route; + break; + case EMU_VAR_ISEMU10K1: + *result = sc->is_emu10k1; + break; + default: + return (ENOENT); + } + + return (0); +} + +static int +emu_write_ivar(device_t bus __unused, device_t dev __unused, + int ivar_index, uintptr_t value __unused) +{ + + switch (ivar_index) { + case 0: + return (EINVAL); + + default: + return (ENOENT); + } +} + +static int +emu_pci_probe(device_t dev) +{ + struct sbuf *s; + unsigned int thiscard = 0; + uint16_t vendor; + + vendor = pci_read_config(dev, PCIR_DEVVENDOR, /* bytes */ 2); + if (vendor != 0x1102) + return (ENXIO); /* Not Creative */ + + thiscard = emu_getcard(dev); + if (thiscard == 0) + return (ENXIO); + + s = sbuf_new(NULL, NULL, 4096, 0); + if (s == NULL) + return (ENOMEM); + sbuf_printf(s, "Creative %s [%s]", emu_cards[thiscard].desc, emu_cards[thiscard].SBcode); + sbuf_finish(s); + + device_set_desc_copy(dev, sbuf_data(s)); + return (BUS_PROBE_DEFAULT); +} + + +static int +emu_pci_attach(device_t dev) +{ + struct sndcard_func *func; + struct emu_sc_info *sc; + struct emu_pcminfo *pcminfo; + struct emu_midiinfo *midiinfo[3]; + uint32_t data; + int i; + int device_flags; + char status[255]; + int error = ENXIO; + + sc = device_get_softc(dev); + + /* Fill in the softc. */ + mtx_init(&sc->lock, "emu10kx", "bridge conf", MTX_DEF); + mtx_init(&sc->rw, "emu10kx", "atomic op", MTX_DEF); + sc->dev = dev; + sc->type = pci_get_devid(dev); + sc->rev = pci_get_revid(dev); + sc->enable_ir = 0; + sc->enable_debug = 0; + sc->has_ac97 = 0; + sc->has_51 = 0; + sc->has_71 = 0; + sc->broken_digital = 0; + sc->is_emu10k1 = 0; + sc->is_emu10k2 = 0; + sc->is_ca0102 = 0; + sc->is_ca0108 = 0; + sc->is_cardbus = 0; + + device_flags = emu_cards[emu_getcard(dev)].flags; + if (device_flags & HAS_51) + sc->has_51 = 1; + if (device_flags & HAS_71) { + sc->has_51 = 1; + sc->has_71 = 1; + } + if (device_flags & IS_EMU10K1) + sc->is_emu10k1 = 1; + if (device_flags & IS_EMU10K2) + sc->is_emu10k2 = 1; + if (device_flags & IS_CA0102) + sc->is_ca0102 = 1; + if (device_flags & IS_CA0108) + sc->is_ca0108 = 1; + if ((sc->is_emu10k2) && (sc->rev == 4)) { + sc->is_emu10k2 = 0; + sc->is_ca0102 = 1; /* for unknown Audigy 2 cards */ + } + if ((sc->is_ca0102 == 1) || (sc->is_ca0108 == 1)) + if (device_flags & IS_CARDBUS) + sc->is_cardbus = 1; + + if ((sc->is_emu10k1 + sc->is_emu10k2 + sc->is_ca0102 + sc->is_ca0108) != 1) { + device_printf(sc->dev, "Unable to detect HW chipset\n"); + goto bad; + } + if (device_flags & BROKEN_DIGITAL) + sc->broken_digital = 1; + if (device_flags & HAS_AC97) + sc->has_ac97 = 1; + + sc->opcode_shift = 0; + if ((sc->is_emu10k2) || (sc->is_ca0102) || (sc->is_ca0108)) { + sc->opcode_shift = 24; + sc->high_operand_shift = 12; + + /* DSP map */ + /* sc->fx_base = 0x0 */ + sc->input_base = 0x40; + /* sc->p16vinput_base = 0x50; */ + sc->output_base = 0x60; + sc->efxc_base = 0x80; + /* sc->output32h_base = 0xa0; */ + /* sc->output32l_base = 0xb0; */ + sc->dsp_zero = 0xc0; + /* 0xe0...0x100 are unknown */ + /* sc->tram_base = 0x200 */ + /* sc->tram_addr_base = 0x300 */ + sc->gpr_base = A_FXGPREGBASE; + sc->num_gprs = 0x200; + sc->code_base = A_MICROCODEBASE; + sc->code_size = 0x800 / 2; /* 0x600-0xdff, 2048 words, + * 1024 instructions */ + + sc->mchannel_fx = 8; + sc->num_fxbuses = 16; + sc->num_inputs = 8; + sc->num_outputs = 16; + sc->address_mask = A_PTR_ADDRESS_MASK; + } + if (sc->is_emu10k1) { + sc->has_51 = 0; /* We don't support 5.1 sound Live! 5.1 */ + sc->opcode_shift = 20; + sc->high_operand_shift = 10; + sc->code_base = MICROCODEBASE; + sc->code_size = 0x400 / 2; /* 0x400-0x7ff, 1024 words, + * 512 instructions */ + sc->gpr_base = FXGPREGBASE; + sc->num_gprs = 0x100; + sc->input_base = 0x10; + sc->output_base = 0x20; + /* + * XXX 5.1 Analog outputs are inside efxc address space! + * They use ouput+0x11/+0x12 (=efxc+1/+2). + * Don't use this efx registers for recording on SB Live! 5.1! + */ + sc->efxc_base = 0x30; + sc->dsp_zero = 0x40; + sc->mchannel_fx = 0; + sc->num_fxbuses = 8; + sc->num_inputs = 8; + sc->num_outputs = 16; + sc->address_mask = PTR_ADDRESS_MASK; + } + if (sc->opcode_shift == 0) + goto bad; + + data = pci_read_config(dev, PCIR_COMMAND, 2); + data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN); + pci_write_config(dev, PCIR_COMMAND, data, 2); + data = pci_read_config(dev, PCIR_COMMAND, 2); + + pci_enable_busmaster(dev); + + i = PCIR_BAR(0); + sc->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &i, RF_ACTIVE); + if (sc->reg == NULL) { + device_printf(dev, "unable to map register space\n"); + goto bad; + } + sc->st = rman_get_bustag(sc->reg); + sc->sh = rman_get_bushandle(sc->reg); + + for (i = 0; i < EMU_MAX_IRQ_CONSUMERS; i++) + sc->timer[i] = 0; /* disable it */ + + i = 0; + sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &i, RF_ACTIVE | RF_SHAREABLE); + if ((sc->irq == NULL) || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, emu_intr, sc, &sc->ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto bad; + } + if (emu_rm_init(sc) != 0) { + device_printf(dev, "unable to create resource manager\n"); + goto bad; + } + if (sc->is_cardbus) + if (emu_cardbus_init(sc) != 0) { + device_printf(dev, "unable to initialize CardBus interface\n"); + goto bad; + } + sc->ctx = device_get_sysctl_ctx(dev); + if (sc->ctx == NULL) + goto bad; + sc->root = device_get_sysctl_tree(dev); + if (sc->root == NULL) + goto bad; + if (emu_init(sc) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + if (emu10kx_dev_init(sc) == ENXIO) { + device_printf(dev, "unable to create control device\n"); + goto bad; + } + snprintf(status, 255, "rev %d at io 0x%lx irq %ld", sc->rev, rman_get_start(sc->reg), rman_get_start(sc->irq)); + + /* Voices */ + for (i = 0; i < NUM_G; i++) { + sc->voice[i].vnum = i; + sc->voice[i].slave = NULL; + sc->voice[i].busy = 0; + sc->voice[i].ismaster = 0; + sc->voice[i].running = 0; + sc->voice[i].b16 = 0; + sc->voice[i].stereo = 0; + sc->voice[i].speed = 0; + sc->voice[i].start = 0; + sc->voice[i].end = 0; + } + + /* PCM Audio */ + /* FRONT */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_FRONT; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_FRONT] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_FRONT], func); + +#ifdef SND_EMU10KX_MULTICHANNEL + /* REAR */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_REAR; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_REAR] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_REAR], func); + if (sc->has_51) { + /* CENTER */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_CENTER; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_CENTER] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_CENTER], func); + /* SUB */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_SUB; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_SUB] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_SUB], func); + } + if (sc->has_71) { + /* SIDE */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_SIDE; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_SIDE] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_SIDE], func); + }; +#ifdef SND_EMU10KX_MCH_RECORDING + /* MULTICHANNEL RECORDING */ + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo = malloc(sizeof(struct emu_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (pcminfo == NULL) { + error = ENOMEM; + goto bad; + } + pcminfo->card = sc; + pcminfo->route = RT_MCHRECORD; + + func->func = SCF_PCM; + func->varinfo = pcminfo; + sc->pcm[RT_MCHRECORD] = device_add_child(dev, "pcm", -1); + device_set_ivars(sc->pcm[RT_MCHRECORD], func); + +#endif /* SMD_EMU10KX_MCH_RECORDING */ +#endif /* SND_EMU10KX_MULTICHANNEL */ + + /* Midi Interface 1: Live!, Audigy, Audigy 2 */ + if ((sc->is_emu10k1) || (sc->is_emu10k2) || (sc->is_ca0102)) { + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[0] = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (midiinfo[0] == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[0]->card = sc; + if (sc->is_emu10k2 || (sc->is_ca0102)) { + midiinfo[0]->port = A_MUDATA1; + midiinfo[0]->portnr = 1; + } + if (sc->is_emu10k1) { + midiinfo[0]->port = MUDATA; + midiinfo[0]->portnr = 1; + } + func->func = SCF_MIDI; + func->varinfo = midiinfo[0]; + sc->midi[0] = device_add_child(dev, "midi", -1); + device_set_ivars(sc->midi[0], func); + } + /* Midi Interface 2: Audigy, Audigy 2 (on AudigyDrive) */ + if (sc->is_emu10k2 || (sc->is_ca0102)) { + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); + if (func == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[1] = malloc(sizeof(struct emu_midiinfo), M_DEVBUF, M_NOWAIT | M_ZERO); + if (midiinfo[1] == NULL) { + error = ENOMEM; + goto bad; + } + midiinfo[1]->card = sc; + + midiinfo[1]->port = A_MUDATA2; + midiinfo[1]->portnr = 2; + + func->func = SCF_MIDI; + func->varinfo = midiinfo[1]; + sc->midi[1] = device_add_child(dev, "midi", -1); + device_set_ivars(sc->midi[1], func); + } + + return (bus_generic_attach(dev)); + +bad: + /* XXX can we just call emu_pci_detach here? */ + if (sc->cdev) + emu10kx_dev_uninit(sc); + if (sc->rm != NULL) + emu_rm_uninit(sc); + if (sc->reg) + bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); + if (sc->ih) + bus_teardown_intr(dev, sc->irq, sc->ih); + if (sc->irq) + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); + mtx_destroy(&sc->lock); + mtx_destroy(&sc->rw); + return (error); +} + +static int +emu_pci_detach(device_t dev) +{ + struct emu_sc_info *sc; + int devcount, i; + device_t *childlist; + int r = 0; + + sc = device_get_softc(dev); + + for (i = 0; i < RT_COUNT; i++) { + if (sc->pcm[i] != NULL) + r = device_delete_child(dev, sc->pcm[i]); + if (r) + return (r); + } + if (sc->midi[0] != NULL) + r = device_delete_child(dev, sc->midi[0]); + if (r) + return (r); + if (sc->midi[1] != NULL) + r = device_delete_child(dev, sc->midi[1]); + if (r) + return (r); + (void)device_get_children(dev, &childlist, &devcount); + for (i = 0; i < devcount - 1; i++) { + device_printf(dev, "removing stale child %d (unit %d)\n", i, device_get_unit(childlist[i])); + device_delete_child(dev, childlist[i]); + } + free(childlist, M_TEMP); + + /* shutdown chip */ + emu_uninit(sc); + r = emu10kx_dev_uninit(sc); + if (r) + return (r); + emu_rm_uninit(sc); + + if (sc->mem.dmat) + bus_dma_tag_destroy(sc->mem.dmat); + + if (sc->reg) + bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0), sc->reg); + bus_teardown_intr(dev, sc->irq, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq); + mtx_destroy(&sc->lock); + mtx_destroy(&sc->rw); + return (bus_generic_detach(dev)); +} +/* add suspend, resume */ +static device_method_t emu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, emu_pci_probe), + DEVMETHOD(device_attach, emu_pci_attach), + DEVMETHOD(device_detach, emu_pci_detach), + /* Bus methods */ + DEVMETHOD(bus_read_ivar, emu_read_ivar), + DEVMETHOD(bus_write_ivar, emu_write_ivar), + + {0, 0} +}; + + +static driver_t emu_driver = { + "emu10kx", + emu_methods, + sizeof(struct emu_sc_info), + NULL, + 0, + NULL +}; + +static int +emu_modevent(module_t mod __unused, int cmd, void *data __unused) +{ + int err = 0; + + switch (cmd) { + case MOD_LOAD: + break; /* Success */ + + case MOD_UNLOAD: + case MOD_SHUTDOWN: + + /* XXX Should we check state of pcm & midi subdevices here? */ + + break; /* Success */ + + default: + err = EINVAL; + break; + } + + return (err); + +} + +static devclass_t emu_devclass; + +DRIVER_MODULE(snd_emu10kx, pci, emu_driver, emu_devclass, emu_modevent, NULL); +DRIVER_MODULE(snd_emu10kx, cardbus, emu_driver, emu_devclass, emu_modevent, NULL); +MODULE_VERSION(snd_emu10kx, SND_EMU10KX_PREFVER); --- sys/dev/sound/pci/emu10kx.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/emu10kx.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003-2006 Yuriy Tsibizov + * 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 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 AUTHOR 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, WHETHERIN 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. + * + * $FreeBSD: src/sys/dev/sound/pci/emu10kx.h,v 1.3 2007/01/06 18:59:35 netchild Exp $ + */ + +#ifndef EMU10KX_H +#define EMU10KX_H + +#define SND_EMU10KX_MINVER 1 +#define SND_EMU10KX_PREFVER 1 +#define SND_EMU10KX_MAXVER 1 + +#ifdef _KERNEL + +#define EMUPAGESIZE 4096 +#define NUM_G 64 +#define EMU_PLAY_BUFSZ EMUPAGESIZE*16 +/* Recording is limited by EMUPAGESIZE*16=64K buffer */ +#define EMU_REC_BUFSZ EMUPAGESIZE*16 +#define EMU_MAX_BUFSZ EMUPAGESIZE*16 +#define EMU_MAXPAGES 8192 + + +#define EMU_VAR_FUNC 0 +#define EMU_VAR_ROUTE 1 +#define EMU_VAR_ISEMU10K1 2 + +#define RT_FRONT 0 +#define RT_REAR 1 +#define RT_CENTER 2 +#define RT_SUB 3 +#define RT_SIDE 4 +#define RT_MCHRECORD 5 +#define RT_COUNT 6 + +/* mixer controls */ +/* fx play */ +#define M_FX0_FRONT_L 0 +#define M_FX1_FRONT_R 1 +#define M_FX2_REAR_L 2 +#define M_FX3_REAR_R 3 +#define M_FX4_CENTER 4 +#define M_FX5_SUBWOOFER 5 +#define M_FX6_SIDE_L 6 +#define M_FX7_SIDE_R 7 +/* fx rec */ +#define M_FX0_REC_L 8 +#define M_FX1_REC_R 9 +/* inputs play */ +#define M_IN0_FRONT_L 10 +#define M_IN0_FRONT_R 11 +#define M_IN1_FRONT_L 12 +#define M_IN1_FRONT_R 13 +#define M_IN2_FRONT_L 14 +#define M_IN2_FRONT_R 15 +#define M_IN3_FRONT_L 16 +#define M_IN3_FRONT_R 17 +#define M_IN4_FRONT_L 18 +#define M_IN4_FRONT_R 19 +#define M_IN5_FRONT_L 20 +#define M_IN5_FRONT_R 21 +#define M_IN6_FRONT_L 22 +#define M_IN6_FRONT_R 23 +#define M_IN7_FRONT_L 24 +#define M_IN7_FRONT_R 25 +/* inputs rec */ +#define M_IN0_REC_L 26 +#define M_IN0_REC_R 27 +#define M_IN1_REC_L 28 +#define M_IN1_REC_R 29 +#define M_IN2_REC_L 30 +#define M_IN2_REC_R 31 +#define M_IN3_REC_L 32 +#define M_IN3_REC_R 33 +#define M_IN4_REC_L 34 +#define M_IN4_REC_R 35 +#define M_IN5_REC_L 36 +#define M_IN5_REC_R 37 +#define M_IN6_REC_L 38 +#define M_IN6_REC_R 39 +#define M_IN7_REC_L 40 +#define M_IN7_REC_R 41 +/* master volume */ +#define M_MASTER_FRONT_L 42 +#define M_MASTER_FRONT_R 43 +#define M_MASTER_REAR_L 44 +#define M_MASTER_REAR_R 45 +#define M_MASTER_CENTER 46 +#define M_MASTER_SUBWOOFER 47 +#define M_MASTER_SIDE_L 48 +#define M_MASTER_SIDE_R 49 +/* master rec volume */ +#define M_MASTER_REC_L 50 +#define M_MASTER_REC_R 51 + +#define NUM_MIXERS 52 + +struct emu_sc_info; + +/* MIDI device parameters */ +struct emu_midiinfo { + struct emu_sc_info *card; + int port; + int portnr; +}; + +/* PCM device parameters */ +struct emu_pcminfo { + struct emu_sc_info *card; + int route; +}; + +int emu_intr_register(struct emu_sc_info *sc, uint32_t inte_mask, uint32_t intr_mask, uint32_t(*func) (void *softc, uint32_t irq), void *isc); +int emu_intr_unregister(struct emu_sc_info *sc, int ihandle); + +uint32_t emu_rd(struct emu_sc_info *sc, unsigned int regno, unsigned int size); +void emu_wr(struct emu_sc_info *sc, unsigned int regno, uint32_t data, unsigned int size); + +uint32_t emu_rdptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg); +void emu_wrptr(struct emu_sc_info *sc, unsigned int chn, unsigned int reg, uint32_t data); + +uint32_t emu_rd_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg); +void emu_wr_p16vptr(struct emu_sc_info *sc, uint16_t chn, uint16_t reg, uint32_t data); + +int emu_timer_create(struct emu_sc_info *sc); +int emu_timer_set(struct emu_sc_info *sc, int timer, int delay); +int emu_timer_enable(struct emu_sc_info *sc, int timer, int go); +int emu_timer_clear(struct emu_sc_info *sc, int timer); + +struct emu_voice; + +struct emu_route { + int routing_left[8]; + int amounts_left[8]; + int routing_right[8]; + int amounts_right[8]; +}; + +struct emu_voice* emu_valloc(struct emu_sc_info *sc); +void emu_vfree(struct emu_sc_info *sc, struct emu_voice *v); +int emu_vinit(struct emu_sc_info *sc, struct emu_voice *m, struct emu_voice *s, + uint32_t sz, struct snd_dbuf *b); +void emu_vroute(struct emu_sc_info *sc, struct emu_route *rt, struct emu_voice *v); +void emu_vsetup(struct emu_voice *v, int fmt, int spd); +void emu_vwrite(struct emu_sc_info *sc, struct emu_voice *v); +void emu_vtrigger(struct emu_sc_info *sc, struct emu_voice *v, int go); +int emu_vpos(struct emu_sc_info *sc, struct emu_voice *v); + +bus_dma_tag_t emu_gettag(struct emu_sc_info *sc); + +void emumix_set_volume(struct emu_sc_info *sc, int mixer_idx, int volume); +int emumix_get_volume(struct emu_sc_info *sc, int mixer_idx); + +void emu_enable_ir(struct emu_sc_info *sc); +#endif /* _KERNEL */ +#endif /* EMU10K1_H */ --- sys/dev/sound/pci/envy24.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/envy24.c Thu Jul 12 12:04:19 2007 @@ -35,7 +35,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/envy24.c,v 1.11.2.2 2007/06/11 19:33:27 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/envy24.c,v 1.13 2007/05/27 19:58:39 joel Exp $"); MALLOC_DEFINE(M_ENVY24, "envy24", "envy24 audio"); --- sys/dev/sound/pci/envy24.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/envy24.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/envy24.h,v 1.1.2.2 2007/06/11 19:33:27 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/envy24.h,v 1.2 2007/05/27 19:58:39 joel Exp $ */ --- sys/dev/sound/pci/envy24ht.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/envy24ht.c Thu Jul 12 12:04:19 2007 @@ -47,7 +47,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/envy24ht.c,v 1.11.2.2 2007/06/11 19:33:27 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/envy24ht.c,v 1.15 2007/06/02 17:28:26 ariff Exp $"); MALLOC_DEFINE(M_ENVY24HT, "envy24ht", "envy24ht audio"); --- sys/dev/sound/pci/envy24ht.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/envy24ht.h Thu Jul 12 12:04:19 2007 @@ -24,7 +24,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/envy24ht.h,v 1.4.2.2 2007/06/11 19:33:27 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/envy24ht.h,v 1.5 2007/05/27 19:58:39 joel Exp $ */ --- sys/dev/sound/pci/es137x.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/es137x.c Thu Jul 12 12:04:19 2007 @@ -59,7 +59,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/es137x.c,v 1.55.2.2 2006/01/16 02:08:56 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/es137x.c,v 1.71 2007/07/05 10:22:37 ariff Exp $"); #define MEM_MAP_REG 0x14 @@ -93,6 +93,11 @@ #define ES_ADC 2 #define ES_NCHANS 3 +#define ES_DMA_SEGS_MIN 2 +#define ES_DMA_SEGS_MAX 256 +#define ES_BLK_MIN 64 +#define ES_BLK_ALIGN (~(ES_BLK_MIN - 1)) + #define ES1370_DAC1_MINSPEED 5512 #define ES1370_DAC1_MAXSPEED 44100 @@ -105,7 +110,9 @@ struct snd_dbuf *buffer; struct pcmchan_caps caps; int dir, num, index; - u_int32_t fmt, blksz, bufsz; + uint32_t fmt, blksz, blkcnt, bufsz; + uint32_t ptr, prevptr; + int active; }; /* @@ -168,7 +175,7 @@ * 2 = Enable single DAC (DAC2) * 3 = Enable both DACs, swap position (DAC2 comes first instead of DAC1) */ -#define ES_DEFAULT_DAC_CFG 2 +#define ES_DEFAULT_DAC_CFG 0 struct es_info { bus_space_tag_t st; @@ -181,7 +188,7 @@ device_t dev; int num; - unsigned int bufsz; + unsigned int bufsz, blkcnt; /* Contents of board's registers */ uint32_t ctrl; @@ -189,6 +196,8 @@ uint32_t escfg; struct es_chinfo ch[ES_NCHANS]; struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; #define ES_LOCK(sc) snd_mtxlock((sc)->lock) @@ -198,14 +207,15 @@ /* prototypes */ static void es_intr(void *); static uint32_t es1371_wait_src_ready(struct es_info *); -static void es1371_src_write(struct es_info *, u_short, unsigned short); -static u_int es1371_adc_rate(struct es_info *, u_int, int); -static u_int es1371_dac_rate(struct es_info *, u_int, int); +static void es1371_src_write(struct es_info *, + unsigned short, unsigned short); +static unsigned int es1371_adc_rate(struct es_info *, unsigned int, int); +static unsigned int es1371_dac_rate(struct es_info *, unsigned int, int); static int es1371_init(struct es_info *); static int es1370_init(struct es_info *); -static int es1370_wrcodec(struct es_info *, u_char, u_char); +static int es1370_wrcodec(struct es_info *, unsigned char, unsigned char); -static u_int32_t es_fmt[] = { +static uint32_t es_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, @@ -234,23 +244,23 @@ [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; -static __inline u_int32_t +static __inline uint32_t es_rd(struct es_info *es, int regno, int size) { switch (size) { case 1: - return bus_space_read_1(es->st, es->sh, regno); + return (bus_space_read_1(es->st, es->sh, regno)); case 2: - return bus_space_read_2(es->st, es->sh, regno); + return (bus_space_read_2(es->st, es->sh, regno)); case 4: - return bus_space_read_4(es->st, es->sh, regno); + return (bus_space_read_4(es->st, es->sh, regno)); default: - return 0xFFFFFFFF; + return (0xFFFFFFFF); } } static __inline void -es_wr(struct es_info *es, int regno, u_int32_t data, int size) +es_wr(struct es_info *es, int regno, uint32_t data, int size) { switch (size) { @@ -274,12 +284,14 @@ { struct es_info *es; int i; - u_int32_t v; + uint32_t v; es = mix_getdevinfo(m); v = 0; - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) - if (mixtable[i].avail) v |= (1 << i); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (mixtable[i].avail) + v |= (1 << i); + } /* * Each DAC1/2 for ES1370 can be controlled independently * DAC1 = controlled by synth @@ -291,12 +303,14 @@ v &= ~(1 << SOUND_MIXER_SYNTH); mix_setdevs(m, v); v = 0; - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) - if (mixtable[i].recmask) v |= (1 << i); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (mixtable[i].recmask) + v |= (1 << i); + } if (ES_SINGLE_PCM_MIX(es->escfg)) /* ditto */ v &= ~(1 << SOUND_MIXER_SYNTH); mix_setrecdevs(m, v); - return 0; + return (0); } static int @@ -305,38 +319,38 @@ struct es_info *es; int l, r, rl, rr, set_dac1; - if (!mixtable[dev].avail) return -1; + if (!mixtable[dev].avail) + return (-1); l = left; - r = mixtable[dev].stereo? right : l; - if (mixtable[dev].left == 0xf) { - rl = (l < 2)? 0x80 : 7 - (l - 2) / 14; - } else { - rl = (l < 10)? 0x80 : 15 - (l - 10) / 6; - } + r = (mixtable[dev].stereo) ? right : l; + if (mixtable[dev].left == 0xf) + rl = (l < 2) ? 0x80 : 7 - (l - 2) / 14; + else + rl = (l < 10) ? 0x80 : 15 - (l - 10) / 6; es = mix_getdevinfo(m); ES_LOCK(es); if (dev == SOUND_MIXER_PCM && (ES_SINGLE_PCM_MIX(es->escfg)) && - ES_DAC1_ENABLED(es->escfg)) { + ES_DAC1_ENABLED(es->escfg)) set_dac1 = 1; - } else { + else set_dac1 = 0; - } if (mixtable[dev].stereo) { - rr = (r < 10)? 0x80 : 15 - (r - 10) / 6; + rr = (r < 10) ? 0x80 : 15 - (r - 10) / 6; es1370_wrcodec(es, mixtable[dev].right, rr); if (set_dac1 && mixtable[SOUND_MIXER_SYNTH].stereo) - es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].right, rr); + es1370_wrcodec(es, + mixtable[SOUND_MIXER_SYNTH].right, rr); } es1370_wrcodec(es, mixtable[dev].left, rl); if (set_dac1) es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].left, rl); ES_UNLOCK(es); - return l | (r << 8); + return (l | (r << 8)); } static int -es1370_mixsetrecsrc(struct snd_mixer *m, u_int32_t src) +es1370_mixsetrecsrc(struct snd_mixer *m, uint32_t src) { struct es_info *es; int i, j = 0; @@ -349,9 +363,8 @@ ES_LOCK(es); if ((src & (1 << SOUND_MIXER_PCM)) && ES_SINGLE_PCM_MIX(es->escfg) && - ES_DAC1_ENABLED(es->escfg)) { + ES_DAC1_ENABLED(es->escfg)) j |= mixtable[SOUND_MIXER_SYNTH].recmask; - } es1370_wrcodec(es, CODEC_LIMIX1, j & 0x55); es1370_wrcodec(es, CODEC_RIMIX1, j & 0xaa); es1370_wrcodec(es, CODEC_LIMIX2, (j >> 8) & 0x17); @@ -360,13 +373,13 @@ es1370_wrcodec(es, CODEC_OMIX2, 0x3f); ES_UNLOCK(es); - return src; + return (src); } static kobj_method_t es1370_mixer_methods[] = { - KOBJMETHOD(mixer_init, es1370_mixinit), - KOBJMETHOD(mixer_set, es1370_mixset), - KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), + KOBJMETHOD(mixer_init, es1370_mixinit), + KOBJMETHOD(mixer_set, es1370_mixset), + KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), { 0, 0 } }; MIXER_DECLARE(es1370_mixer); @@ -374,30 +387,31 @@ /* -------------------------------------------------------------------- */ static int -es1370_wrcodec(struct es_info *es, u_char i, u_char data) +es1370_wrcodec(struct es_info *es, unsigned char i, unsigned char data) { - u_int t; + unsigned int t; ES_LOCK_ASSERT(es); for (t = 0; t < 0x1000; t++) { if ((es_rd(es, ES1370_REG_STATUS, 4) & - STAT_CSTAT) == 0) { + STAT_CSTAT) == 0) { es_wr(es, ES1370_REG_CODEC, - ((u_short)i << CODEC_INDEX_SHIFT) | data, 2); - return 0; + ((unsigned short)i << CODEC_INDEX_SHIFT) | data, 2); + return (0); } DELAY(1); } device_printf(es->dev, "%s: timed out\n", __func__); - return -1; + return (-1); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * -eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { struct es_info *es = devinfo; struct es_chinfo *ch; @@ -408,26 +422,26 @@ if (dir == PCMDIR_PLAY) { index = ES_GP(es->escfg); es->escfg = ES_SET_GP(es->escfg, index + 1); - if (index == 0) { + if (index == 0) index = ES_DAC_FIRST(es->escfg); - } else if (index == 1) { + else if (index == 1) index = ES_DAC_SECOND(es->escfg); - } else { - device_printf(es->dev, "Invalid ES_GP index: %d\n", index); + else { + device_printf(es->dev, + "Invalid ES_GP index: %d\n", index); ES_UNLOCK(es); - return NULL; + return (NULL); } if (!(index == ES_DAC1 || index == ES_DAC2)) { - device_printf(es->dev, "Unknown DAC: %d\n", - index + 1); + device_printf(es->dev, "Unknown DAC: %d\n", index + 1); ES_UNLOCK(es); - return NULL; + return (NULL); } if (es->ch[index].channel != NULL) { device_printf(es->dev, "DAC%d already initialized!\n", - index + 1); + index + 1); ES_UNLOCK(es); - return NULL; + return (NULL); } } else index = ES_ADC; @@ -443,7 +457,7 @@ } else { uint32_t fixed_rate = ES_FIXED_RATE(es->escfg); if (!(fixed_rate < es_caps.minspeed || - fixed_rate > es_caps.maxspeed)) { + fixed_rate > es_caps.maxspeed)) { ch->caps.maxspeed = fixed_rate; ch->caps.minspeed = fixed_rate; } @@ -453,33 +467,42 @@ ch->channel = c; ch->buffer = b; ch->bufsz = es->bufsz; - ch->blksz = ch->bufsz / 2; + ch->blkcnt = es->blkcnt; + ch->blksz = ch->bufsz / ch->blkcnt; ch->dir = dir; ES_UNLOCK(es); - if (sndbuf_alloc(ch->buffer, es->parent_dmat, ch->bufsz) != 0) - return NULL; + if (sndbuf_alloc(ch->buffer, es->parent_dmat, 0, ch->bufsz) != 0) + return (NULL); ES_LOCK(es); if (dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMEADR >> 8, 1); - es_wr(es, ES1370_REG_DAC1_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); - es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC1_FRAMEADR >> 8, 1); + es_wr(es, ES1370_REG_DAC1_FRAMEADR & 0xff, + sndbuf_getbufaddr(ch->buffer), 4); + es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); } else { - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMEADR >> 8, 1); - es_wr(es, ES1370_REG_DAC2_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); - es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMEADR >> 8, 1); + es_wr(es, ES1370_REG_DAC2_FRAMEADR & 0xff, + sndbuf_getbufaddr(ch->buffer), 4); + es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); } } else { es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8, 1); - es_wr(es, ES1370_REG_ADC_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer), 4); - es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + es_wr(es, ES1370_REG_ADC_FRAMEADR & 0xff, + sndbuf_getbufaddr(ch->buffer), 4); + es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); } ES_UNLOCK(es); - return ch; + return (ch); } static int -eschan_setformat(kobj_t obj, void *data, u_int32_t format) +eschan_setformat(kobj_t obj, void *data, uint32_t format) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; @@ -488,38 +511,46 @@ if (ch->dir == PCMDIR_PLAY) { if (ch->index == ES_DAC1) { es->sctrl &= ~SCTRL_P1FMT; - if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P1SEB; - if (format & AFMT_STEREO) es->sctrl |= SCTRL_P1SMB; + if (format & AFMT_S16_LE) + es->sctrl |= SCTRL_P1SEB; + if (format & AFMT_STEREO) + es->sctrl |= SCTRL_P1SMB; } else { es->sctrl &= ~SCTRL_P2FMT; - if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; - if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; + if (format & AFMT_S16_LE) + es->sctrl |= SCTRL_P2SEB; + if (format & AFMT_STEREO) + es->sctrl |= SCTRL_P2SMB; } } else { es->sctrl &= ~SCTRL_R1FMT; - if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; - if (format & AFMT_STEREO) es->sctrl |= SCTRL_R1SMB; + if (format & AFMT_S16_LE) + es->sctrl |= SCTRL_R1SEB; + if (format & AFMT_STEREO) + es->sctrl |= SCTRL_R1SMB; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); ch->fmt = format; - return 0; + return (0); } static int -eschan1370_setspeed(kobj_t obj, void *data, u_int32_t speed) +eschan1370_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; + ES_LOCK(es); /* Fixed rate , do nothing. */ - if (ch->caps.minspeed == ch->caps.maxspeed) - return ch->caps.maxspeed; + if (ch->caps.minspeed == ch->caps.maxspeed) { + ES_UNLOCK(es); + return (ch->caps.maxspeed); + } if (speed < ch->caps.minspeed) speed = ch->caps.minspeed; if (speed > ch->caps.maxspeed) speed = ch->caps.maxspeed; - ES_LOCK(es); if (ch->index == ES_DAC1) { /* * DAC1 does not support continuous rate settings. @@ -546,11 +577,11 @@ } es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); ES_UNLOCK(es); - return speed; + return (speed); } static int -eschan1371_setspeed(kobj_t obj, void *data, u_int32_t speed) +eschan1371_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; @@ -563,33 +594,132 @@ else i = es1371_adc_rate(es, speed, ch->index); /* record */ ES_UNLOCK(es); - delta = (speed > i) ? speed - i : i - speed; + delta = (speed > i) ? (speed - i) : (i - speed); if (delta < 2) - return speed; - return i; + return (speed); + return (i); } static int -eschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +eschan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { - struct es_info *es; struct es_chinfo *ch = data; - uint32_t oblksz, obufsz; - int error; + struct es_info *es = ch->parent; + + blksz &= ES_BLK_ALIGN; + + if (blksz > (sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN)) + blksz = sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN; + if (blksz < ES_BLK_MIN) + blksz = ES_BLK_MIN; + if (blkcnt > ES_DMA_SEGS_MAX) + blkcnt = ES_DMA_SEGS_MAX; + if (blkcnt < ES_DMA_SEGS_MIN) + blkcnt = ES_DMA_SEGS_MIN; + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= ES_DMA_SEGS_MIN) + blkcnt >>= 1; + else if ((blksz >> 1) >= ES_BLK_MIN) + blksz >>= 1; + else + break; + } + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(es->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + ch->bufsz = sndbuf_getsize(ch->buffer); + ch->blksz = sndbuf_getblksz(ch->buffer); + ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + + return (1); +} + +static int +eschan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + eschan_setfragments(obj, data, blksz, es->blkcnt); + + return (ch->blksz); +} + +#define es_chan_active(es) ((es)->ch[ES_DAC1].active + \ + (es)->ch[ES_DAC2].active + \ + (es)->ch[ES_ADC].active) + +static __inline int +es_poll_channel(struct es_chinfo *ch) +{ + struct es_info *es; + uint32_t sz, delta; + uint32_t reg, ptr; + + if (ch == NULL || ch->channel == NULL || ch->active == 0) + return (0); + + es = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) + reg = ES1370_REG_DAC1_FRAMECNT; + else + reg = ES1370_REG_DAC2_FRAMECNT; + } else + reg = ES1370_REG_ADC_FRAMECNT; + sz = ch->blksz * ch->blkcnt; + es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); + ptr = es_rd(es, reg & 0x000000ff, 4) >> 16; + ptr <<= 2; + ch->ptr = ptr; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + delta = (sz + ptr - ch->prevptr) % sz; - oblksz = ch->blksz; - obufsz = ch->bufsz; - ch->blksz = blocksize; - ch->bufsz = ch->blksz * 2; - error = sndbuf_resize(ch->buffer, 2, ch->blksz); - if (error != 0) { - ch->blksz = oblksz; - ch->bufsz = obufsz; - es = ch->parent; - device_printf(es->dev, "unable to set block size, blksz = %d, " - "error = %d", blocksize, error); + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +static void +es_poll_callback(void *arg) +{ + struct es_info *es = arg; + uint32_t trigger = 0; + int i; + + if (es == NULL) + return; + + ES_LOCK(es); + if (es->polling == 0 || es_chan_active(es) == 0) { + ES_UNLOCK(es); + return; + } + + for (i = 0; i < ES_NCHANS; i++) { + if (es_poll_channel(&es->ch[i]) != 0) + trigger |= 1 << i; + } + + /* XXX */ + callout_reset(&es->poll_timer, 1/*es->poll_ticks*/, + es_poll_callback, es); + + ES_UNLOCK(es); + + for (i = 0; i < ES_NCHANS; i++) { + if (trigger & (1 << i)) + chn_intr(es->ch[i].channel); } - return ch->blksz; } static int @@ -599,51 +729,95 @@ struct es_info *es = ch->parent; uint32_t cnt, b = 0; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; + ES_LOCK(es); cnt = (ch->blksz / sndbuf_getbps(ch->buffer)) - 1; if (ch->fmt & AFMT_16BIT) b |= 0x02; if (ch->fmt & AFMT_STEREO) b |= 0x01; - ES_LOCK(es); if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { if (ch->index == ES_DAC1) { es->ctrl |= CTRL_DAC1_EN; - es->sctrl &= ~(SCTRL_P1LOOPSEL | SCTRL_P1PAUSE | SCTRL_P1SCTRLD); - es->sctrl |= SCTRL_P1INTEN | b; + es->sctrl &= ~(SCTRL_P1LOOPSEL | + SCTRL_P1PAUSE | SCTRL_P1SCTRLD); + if (es->polling == 0) + es->sctrl |= SCTRL_P1INTEN; + else + es->sctrl &= ~SCTRL_P1INTEN; + es->sctrl |= b; es_wr(es, ES1370_REG_DAC1_SCOUNT, cnt, 4); /* start at beginning of buffer */ - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC1_FRAMECNT >> 8, 4); - es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC1_FRAMECNT >> 8, 4); + es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); } else { es->ctrl |= CTRL_DAC2_EN; - es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN); - es->sctrl |= SCTRL_P2INTEN | (b << 2) | - (((b & 2) ? : 1) << SCTRL_SH_P2ENDINC); + es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | + SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | + SCTRL_P2DACSEN); + if (es->polling == 0) + es->sctrl |= SCTRL_P2INTEN; + else + es->sctrl &= ~SCTRL_P2INTEN; + es->sctrl |= (b << 2) | + ((((b >> 1) & 1) + 1) << SCTRL_SH_P2ENDINC); es_wr(es, ES1370_REG_DAC2_SCOUNT, cnt, 4); /* start at beginning of buffer */ - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMECNT >> 8, 4); - es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMECNT >> 8, 4); + es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); } - } else es->ctrl &= ~(ch->index == ES_DAC1 ? CTRL_DAC1_EN : CTRL_DAC2_EN); + } else + es->ctrl &= ~((ch->index == ES_DAC1) ? + CTRL_DAC1_EN : CTRL_DAC2_EN); } else { if (go == PCMTRIG_START) { es->ctrl |= CTRL_ADC_EN; es->sctrl &= ~SCTRL_R1LOOPSEL; - es->sctrl |= SCTRL_R1INTEN | (b << 4); + if (es->polling == 0) + es->sctrl |= SCTRL_R1INTEN; + else + es->sctrl &= ~SCTRL_R1INTEN; + es->sctrl |= b << 4; es_wr(es, ES1370_REG_ADC_SCOUNT, cnt, 4); /* start at beginning of buffer */ - es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMECNT >> 8, 4); - es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1, 4); - } else es->ctrl &= ~CTRL_ADC_EN; + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMECNT >> 8, 4); + es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } else + es->ctrl &= ~CTRL_ADC_EN; } es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + if (go == PCMTRIG_START) { + if (es->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + if (es_chan_active(es) == 0) { + es->poll_ticks = 1; + callout_reset(&es->poll_timer, 1, + es_poll_callback, es); + } + } + ch->active = 1; + } else { + ch->active = 0; + if (es->polling != 0) { + if (es_chan_active(es) == 0) { + callout_stop(&es->poll_timer); + es->poll_ticks = 1; + } + } + } ES_UNLOCK(es); - return 0; + return (0); } static int @@ -651,21 +825,29 @@ { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - u_int32_t reg, cnt; + uint32_t reg, cnt; - if (ch->dir == PCMDIR_PLAY) { - if (ch->index == ES_DAC1) - reg = ES1370_REG_DAC1_FRAMECNT; - else - reg = ES1370_REG_DAC2_FRAMECNT; - } else - reg = ES1370_REG_ADC_FRAMECNT; ES_LOCK(es); - es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); - cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; + if (es->polling != 0) + cnt = ch->ptr; + else { + if (ch->dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) + reg = ES1370_REG_DAC1_FRAMECNT; + else + reg = ES1370_REG_DAC2_FRAMECNT; + } else + reg = ES1370_REG_ADC_FRAMECNT; + es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); + cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; + /* cnt is longwords */ + cnt <<= 2; + } ES_UNLOCK(es); - /* cnt is longwords */ - return cnt << 2; + + cnt &= ES_BLK_ALIGN; + + return (cnt); } static struct pcmchan_caps * @@ -673,29 +855,31 @@ { struct es_chinfo *ch = data; - return &ch->caps; + return (&ch->caps); } static kobj_method_t eschan1370_methods[] = { - KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setformat, eschan_setformat), - KOBJMETHOD(channel_setspeed, eschan1370_setspeed), - KOBJMETHOD(channel_setblocksize, eschan_setblocksize), - KOBJMETHOD(channel_trigger, eschan_trigger), - KOBJMETHOD(channel_getptr, eschan_getptr), - KOBJMETHOD(channel_getcaps, eschan_getcaps), + KOBJMETHOD(channel_init, eschan_init), + KOBJMETHOD(channel_setformat, eschan_setformat), + KOBJMETHOD(channel_setspeed, eschan1370_setspeed), + KOBJMETHOD(channel_setblocksize, eschan_setblocksize), + KOBJMETHOD(channel_setfragments, eschan_setfragments), + KOBJMETHOD(channel_trigger, eschan_trigger), + KOBJMETHOD(channel_getptr, eschan_getptr), + KOBJMETHOD(channel_getcaps, eschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(eschan1370); static kobj_method_t eschan1371_methods[] = { - KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setformat, eschan_setformat), - KOBJMETHOD(channel_setspeed, eschan1371_setspeed), - KOBJMETHOD(channel_setblocksize, eschan_setblocksize), - KOBJMETHOD(channel_trigger, eschan_trigger), - KOBJMETHOD(channel_getptr, eschan_getptr), - KOBJMETHOD(channel_getcaps, eschan_getcaps), + KOBJMETHOD(channel_init, eschan_init), + KOBJMETHOD(channel_setformat, eschan_setformat), + KOBJMETHOD(channel_setspeed, eschan1371_setspeed), + KOBJMETHOD(channel_setblocksize, eschan_setblocksize), + KOBJMETHOD(channel_setfragments, eschan_setfragments), + KOBJMETHOD(channel_trigger, eschan_trigger), + KOBJMETHOD(channel_getptr, eschan_getptr), + KOBJMETHOD(channel_getcaps, eschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(eschan1371); @@ -709,6 +893,10 @@ uint32_t intsrc, sctrl; ES_LOCK(es); + if (es->polling != 0) { + ES_UNLOCK(es); + return; + } intsrc = es_rd(es, ES1370_REG_STATUS, 4); if ((intsrc & STAT_INTR) == 0) { ES_UNLOCK(es); @@ -716,17 +904,23 @@ } sctrl = es->sctrl; - if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; - if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; - if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; + if (intsrc & STAT_ADC) + sctrl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) + sctrl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) + sctrl &= ~SCTRL_P2INTEN; es_wr(es, ES1370_REG_SERIAL_CONTROL, sctrl, 4); es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); ES_UNLOCK(es); - if (intsrc & STAT_ADC) chn_intr(es->ch[ES_ADC].channel); - if (intsrc & STAT_DAC1) chn_intr(es->ch[ES_DAC1].channel); - if (intsrc & STAT_DAC2) chn_intr(es->ch[ES_DAC2].channel); + if (intsrc & STAT_ADC) + chn_intr(es->ch[ES_ADC].channel); + if (intsrc & STAT_DAC1) + chn_intr(es->ch[ES_DAC1].channel); + if (intsrc & STAT_DAC2) + chn_intr(es->ch[ES_DAC2].channel); } /* ES1370 specific */ @@ -738,7 +932,7 @@ /* ES1370 default to fixed rate operation */ if (resource_int_value(device_get_name(es->dev), - device_get_unit(es->dev), "fixed_rate", &r) == 0) { + device_get_unit(es->dev), "fixed_rate", &r) == 0) { fixed_rate = r; if (fixed_rate) { if (fixed_rate < es_caps.minspeed) @@ -750,8 +944,8 @@ fixed_rate = es_caps.maxspeed; if (resource_int_value(device_get_name(es->dev), - device_get_unit(es->dev), "single_pcm_mixer", &r) == 0) - single_pcm = (r) ? 1 : 0; + device_get_unit(es->dev), "single_pcm_mixer", &r) == 0) + single_pcm = (r != 0) ? 1 : 0; else single_pcm = 1; @@ -760,34 +954,38 @@ single_pcm = 1; /* This is ES1370 */ es->escfg = ES_SET_IS_ES1370(es->escfg, 1); - if (fixed_rate) { + if (fixed_rate) es->escfg = ES_SET_FIXED_RATE(es->escfg, fixed_rate); - } else { + else { es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); fixed_rate = DSP_DEFAULT_SPEED; } - if (single_pcm) { + if (single_pcm) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); - } else { + else es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); - } es->ctrl = CTRL_CDC_EN | CTRL_JYSTK_EN | CTRL_SERR_DIS | - (DAC2_SRTODIV(fixed_rate) << CTRL_SH_PCLKDIV); + (DAC2_SRTODIV(fixed_rate) << CTRL_SH_PCLKDIV); es->ctrl |= 3 << CTRL_SH_WTSRSEL; es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es->sctrl = 0; es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); - es1370_wrcodec(es, CODEC_RES_PD, 3);/* No RST, PD */ - es1370_wrcodec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use - * {LR,B}CLK2 and run off the LRCLK2 - * PLL; program DAC_SYNC=0! */ - es1370_wrcodec(es, CODEC_ADSEL, 0);/* Recording source is mixer */ - es1370_wrcodec(es, CODEC_MGAIN, 0);/* MIC amp is 0db */ + /* No RST, PD */ + es1370_wrcodec(es, CODEC_RES_PD, 3); + /* + * CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off the LRCLK2 PLL; + * program DAC_SYNC=0! + */ + es1370_wrcodec(es, CODEC_CSEL, 0); + /* Recording source is mixer */ + es1370_wrcodec(es, CODEC_ADSEL, 0); + /* MIC amp is 0db */ + es1370_wrcodec(es, CODEC_MGAIN, 0); ES_UNLOCK(es); - return 0; + return (0); } /* ES1371 specific */ @@ -805,7 +1003,8 @@ cssr = 0; devid = pci_get_devid(es->dev); revid = pci_get_revid(es->dev); - subdev = (pci_get_subdevice(es->dev) << 16) | pci_get_subvendor(es->dev); + subdev = (pci_get_subdevice(es->dev) << 16) | + pci_get_subvendor(es->dev); /* * Joyport blacklist. Either we're facing with broken hardware * or because this hardware need special (unknown) initialization @@ -846,20 +1045,21 @@ es_wr(es, ES1371_REG_SMPRATE, ES1371_DIS_SRC, 4); for (idx = 0; idx < 0x80; idx++) es1371_src_write(es, idx, 0); - es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); + es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10); - es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); + es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10); - es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); - es1371_adc_rate(es, 22050, ES_ADC); - es1371_dac_rate(es, 22050, ES_DAC1); - es1371_dac_rate(es, 22050, ES_DAC2); - /* WARNING: + es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); + es1371_adc_rate(es, 22050, ES_ADC); + es1371_dac_rate(es, 22050, ES_DAC1); + es1371_dac_rate(es, 22050, ES_DAC2); + /* + * WARNING: * enabling the sample rate converter without properly programming * its parameters causes the chip to lock up (the SRC busy bit will * be stuck high, and I've found no way to rectify this other than @@ -878,38 +1078,41 @@ /* -------------------------------------------------------------------- */ static int -es1371_wrcd(kobj_t obj, void *s, int addr, u_int32_t data) +es1371_wrcd(kobj_t obj, void *s, int addr, uint32_t data) { uint32_t t, x, orig; struct es_info *es = (struct es_info*)s; - for (t = 0; t < 0x1000; t++) + for (t = 0; t < 0x1000; t++) { if (!es_rd(es, ES1371_REG_CODEC & CODEC_WIP, 4)) break; + } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - es_wr(es, ES1371_REG_SMPRATE, - (x & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | - 0x00010000, 4); + es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ - for (t = 0; t < 0x1000; t++) - if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00000000) break; + } /* wait for a SAFE time to write addr/data and then do it, dammit */ - for (t = 0; t < 0x1000; t++) - if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00010000) break; + } - es_wr(es, ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK), 4); + es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & + CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & + CODEC_PODAT_MASK), 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); - return 0; + return (0); } static int @@ -918,83 +1121,88 @@ uint32_t t, x, orig; struct es_info *es = (struct es_info *)s; - for (t = 0; t < 0x1000; t++) + for (t = 0; t < 0x1000; t++) { if (!(x = es_rd(es, ES1371_REG_CODEC, 4) & CODEC_WIP)) break; + } /* save the current state for later */ x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - es_wr(es, ES1371_REG_SMPRATE, - (x & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)) | - 0x00010000, 4); + es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); /* busy wait */ - for (t = 0; t < 0x1000; t++) - if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00000000) + for (t = 0; t < 0x1000; t++) { + if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00000000) break; + } /* wait for a SAFE time to write addr/data and then do it, dammit */ - for (t = 0; t < 0x1000; t++) - if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == 0x00010000) + for (t = 0; t < 0x1000; t++) { + if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00010000) break; + } - es_wr(es, ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - CODEC_PORD, 4); + es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & + CODEC_POADD_MASK) | CODEC_PORD, 4); /* restore SRC reg */ es1371_wait_src_ready(s); es_wr(es, ES1371_REG_SMPRATE, orig, 4); /* now wait for the stinkin' data (RDY) */ - for (t = 0; t < 0x1000; t++) + for (t = 0; t < 0x1000; t++) { if ((x = es_rd(es, ES1371_REG_CODEC, 4)) & CODEC_RDY) break; + } return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } static kobj_method_t es1371_ac97_methods[] = { - KOBJMETHOD(ac97_read, es1371_rdcd), - KOBJMETHOD(ac97_write, es1371_wrcd), + KOBJMETHOD(ac97_read, es1371_rdcd), + KOBJMETHOD(ac97_write, es1371_wrcd), { 0, 0 } }; AC97_DECLARE(es1371_ac97); /* -------------------------------------------------------------------- */ -static u_int -es1371_src_read(struct es_info *es, u_short reg) +static unsigned int +es1371_src_read(struct es_info *es, unsigned short reg) { uint32_t r; - r = es1371_wait_src_ready(es) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); + r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg); es_wr(es, ES1371_REG_SMPRATE, r, 4); - return ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es)); + return (ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es))); } static void -es1371_src_write(struct es_info *es, u_short reg, u_short data) +es1371_src_write(struct es_info *es, unsigned short reg, unsigned short data) { uint32_t r; - r = es1371_wait_src_ready(es) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); + r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg) | ES1371_SRC_RAM_DATAO(data); es_wr(es, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE, 4); } -static u_int -es1371_adc_rate(struct es_info *es, u_int rate, int set) +static unsigned int +es1371_adc_rate(struct es_info *es, unsigned int rate, int set) { - u_int n, truncm, freq, result; + unsigned int n, truncm, freq, result; ES_LOCK_ASSERT(es); - if (rate > 48000) rate = 48000; - if (rate < 4000) rate = 4000; + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; n = rate / 3000; if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9))) n--; @@ -1003,46 +1211,54 @@ result = (48000UL << 15) / (freq / n); if (set) { if (rate >= 24000) { - if (truncm > 239) truncm = 239; + if (truncm > 239) + truncm = 239; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, - (((239 - truncm) >> 1) << 9) | (n << 4)); + (((239 - truncm) >> 1) << 9) | (n << 4)); } else { - if (truncm > 119) truncm = 119; + if (truncm > 119) + truncm = 119; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, - 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); + 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); } es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS, - (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & - 0x00ff) | ((freq >> 5) & 0xfc00)); - es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); + (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & + 0x00ff) | ((freq >> 5) & 0xfc00)); + es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, + freq & 0x7fff); es1371_src_write(es, ES_SMPREG_VOL_ADC, n << 8); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, n << 8); } - return result; + return (result); } -static u_int -es1371_dac_rate(struct es_info *es, u_int rate, int set) +static unsigned int +es1371_dac_rate(struct es_info *es, unsigned int rate, int set) { - u_int freq, r, result, dac, dis; + unsigned int freq, r, result, dac, dis; ES_LOCK_ASSERT(es); - if (rate > 48000) rate = 48000; - if (rate < 4000) rate = 4000; + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; freq = ((rate << 15) + 1500) / 3000; result = (freq * 3000) >> 15; - + dac = (set == ES_DAC1) ? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; dis = (set == ES_DAC1) ? ES1371_DIS_P2 : ES1371_DIS_P1; - r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)); + r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1)); es_wr(es, ES1371_REG_SMPRATE, r, 4); es1371_src_write(es, dac + ES_SMPREG_INT_REGS, - (es1371_src_read(es, dac + ES_SMPREG_INT_REGS) & 0x00ff) | ((freq >> 5) & 0xfc00)); + (es1371_src_read(es, dac + ES_SMPREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); es1371_src_write(es, dac + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); - r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | dis | ES1371_DIS_R1)); + r = (es1371_wait_src_ready(es) & + (ES1371_DIS_SRC | dis | ES1371_DIS_R1)); es_wr(es, ES1371_REG_SMPRATE, r, 4); - return result; + return (result); } static uint32_t @@ -1051,13 +1267,14 @@ uint32_t t, r; for (t = 0; t < 0x1000; t++) { - if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & ES1371_SRC_RAM_BUSY)) - return r; + if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & + ES1371_SRC_RAM_BUSY)) + return (r); DELAY(1); } device_printf(es->dev, "%s: timed out 0x%x [0x%x]\n", __func__, ES1371_REG_SMPRATE, r); - return 0; + return (0); } /* -------------------------------------------------------------------- */ @@ -1072,78 +1289,76 @@ switch(pci_get_devid(dev)) { case ES1370_PCI_ID: device_set_desc(dev, "AudioPCI ES1370"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371_PCI_ID: switch(pci_get_revid(dev)) { case ES1371REV_ES1371_A: device_set_desc(dev, "AudioPCI ES1371-A"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1371_B: device_set_desc(dev, "AudioPCI ES1371-B"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_A: device_set_desc(dev, "AudioPCI ES1373-A"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_B: device_set_desc(dev, "AudioPCI ES1373-B"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_8: device_set_desc(dev, "AudioPCI ES1373-8"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case ES1371REV_CT5880_A: device_set_desc(dev, "Creative CT5880-A"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "AudioPCI ES1371-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - case ES1371_PCI_ID2: device_set_desc(dev, "Strange AudioPCI ES1371-? (vid=3274)"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; - + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); case CT4730_PCI_ID: switch(pci_get_revid(dev)) { case CT4730REV_CT4730_A: - device_set_desc(dev, "Creative SB AudioPCI CT4730/EV1938"); - return BUS_PROBE_DEFAULT; + device_set_desc(dev, + "Creative SB AudioPCI CT4730/EV1938"); + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative SB AudioPCI CT4730-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - case CT5880_PCI_ID: switch(pci_get_revid(dev)) { case CT5880REV_CT5880_C: device_set_desc(dev, "Creative CT5880-C"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_D: device_set_desc(dev, "Creative CT5880-D"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_E: device_set_desc(dev, "Creative CT5880-E"); - return BUS_PROBE_DEFAULT; - + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative CT5880-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return BUS_PROBE_DEFAULT; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - default: - return ENXIO; + return (ENXIO); } } @@ -1162,7 +1377,7 @@ r = es_rd(es, ES1370_REG_STATUS, 4); ES_UNLOCK(es); new_en = (r & ENABLE_SPDIF) ? 1 : 0; - err = sysctl_handle_int(oidp, &new_en, sizeof(new_en), req); + err = sysctl_handle_int(oidp, &new_en, 0, req); if (err || req->newptr == NULL) return (err); @@ -1186,7 +1401,6 @@ return (0); } -#if 0 static int sysctl_es137x_latency_timer(SYSCTL_HANDLER_ARGS) { @@ -1200,8 +1414,8 @@ ES_LOCK(es); val = pci_read_config(dev, PCIR_LATTIMER, 1); ES_UNLOCK(es); - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || req->newptr == NULL) return (err); if (val > 255) @@ -1229,8 +1443,8 @@ if (val < es_caps.minspeed) val = 0; ES_UNLOCK(es); - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || req->newptr == NULL) return (err); if (val != 0 && (val < es_caps.minspeed || val > es_caps.maxspeed)) @@ -1268,14 +1482,14 @@ struct es_info *es; struct snddev_info *d; struct snd_mixer *m; - struct cdev *i_dev; device_t dev; uint32_t val, set; int recsrc, level, err; dev = oidp->oid_arg1; d = device_get_softc(dev); - if (d == NULL || d->mixer_dev == NULL || d->mixer_dev->si_drv1 == NULL) + if (!PCM_REGISTERED(d) || d->mixer_dev == NULL || + d->mixer_dev->si_drv1 == NULL) return (EINVAL); es = d->devinfo; if (es == NULL) @@ -1284,53 +1498,53 @@ set = ES_SINGLE_PCM_MIX(es->escfg); val = set; ES_UNLOCK(es); - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || req->newptr == NULL) return (err); if (!(val == 0 || val == 1)) return (EINVAL); if (val == set) return (0); - i_dev = d->mixer_dev; - if (mixer_ioctl(i_dev, 0, (caddr_t)&recsrc, 0, NULL) != EBADF) + PCM_ACQUIRE_QUICK(d); + m = (d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 : NULL; + if (m == NULL) { + PCM_RELEASE_QUICK(d); + return (ENODEV); + } + if (mixer_busy(m) != 0) { + PCM_RELEASE_QUICK(d); return (EBUSY); - err = mixer_ioctl(i_dev, MIXER_READ(SOUND_MIXER_PCM), - (caddr_t)&level, -1, NULL); - if (!err) - err = mixer_ioctl(i_dev, MIXER_READ(SOUND_MIXER_RECSRC), - (caddr_t)&recsrc, -1, NULL); - if (err) - return (err); - if (level < 0) - return (EINVAL); + } + level = mix_get(m, SOUND_MIXER_PCM); + recsrc = mix_getrecsrc(m); + if (level < 0 || recsrc < 0) { + PCM_RELEASE_QUICK(d); + return (ENXIO); + } ES_LOCK(es); if (es->ctrl & (CTRL_ADC_EN | CTRL_DAC1_EN | CTRL_DAC2_EN)) { ES_UNLOCK(es); + PCM_RELEASE_QUICK(d); return (EBUSY); } - if (val) { + if (val) es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); - } else { + else es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); - } ES_UNLOCK(es); - m = i_dev->si_drv1; if (!val) { - mix_setdevs(m, mix_getdevs(d->mixer_dev->si_drv1) | - (1 << SOUND_MIXER_SYNTH)); - mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) | - (1 << SOUND_MIXER_SYNTH)); - err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), - (caddr_t)&level, -1, NULL); + mix_setdevs(m, mix_getdevs(m) | (1 << SOUND_MIXER_SYNTH)); + mix_setrecdevs(m, mix_getrecdevs(m) | (1 << SOUND_MIXER_SYNTH)); + err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, + (level >> 8) & 0x7f); } else { - err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_SYNTH), - (caddr_t)&level, -1, NULL); - mix_setdevs(m, mix_getdevs(d->mixer_dev->si_drv1) & - ~(1 << SOUND_MIXER_SYNTH)); - mix_setrecdevs(m, mix_getrecdevs(d->mixer_dev->si_drv1) & - ~(1 << SOUND_MIXER_SYNTH)); + err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, + (level >> 8) & 0x7f); + mix_setdevs(m, mix_getdevs(m) & ~(1 << SOUND_MIXER_SYNTH)); + mix_setrecdevs(m, mix_getrecdevs(m) & + ~(1 << SOUND_MIXER_SYNTH)); } if (!err) { level = recsrc; @@ -1339,12 +1553,48 @@ else if (recsrc & (1 << SOUND_MIXER_SYNTH)) recsrc |= 1 << SOUND_MIXER_PCM; if (level != recsrc) - err = mixer_ioctl(i_dev, MIXER_WRITE(SOUND_MIXER_RECSRC), - (caddr_t)&recsrc, -1, NULL); + err = mix_setrecsrc(m, recsrc); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_es_polling(SYSCTL_HANDLER_ARGS) +{ + struct es_info *es; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + es = pcm_getdevinfo(dev); + if (es == NULL) + return (EINVAL); + ES_LOCK(es); + val = es->polling; + ES_UNLOCK(es); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + ES_LOCK(es); + if (val != es->polling) { + if (es_chan_active(es) != 0) + err = EBUSY; + else if (val == 0) + es->polling = 0; + else + es->polling = 1; } + ES_UNLOCK(es); + return (err); } -#endif #endif /* SND_DYNSYSCTL */ static void @@ -1358,61 +1608,78 @@ revid = pci_get_revid(dev); es = pcm_getdevinfo(dev); if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || - (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || - (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || - (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || - (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "spdif_enabled", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_es137x_spdif_enable, "I", - "Enable S/PDIF output on primary playback channel"); -#if 0 + (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { + /* XXX: an user should be able to set this with a control tool, + if not done before 7.0-RELEASE, this needs to be converted + to a device specific sysctl "dev.pcm.X.yyy" via + device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_spdif_enable, "I", + "Enable S/PDIF output on primary playback channel"); } else if (devid == ES1370_PCI_ID) { /* * Enable fixed rate sysctl if both DAC2 / ADC enabled. */ - if (es->ch[ES_DAC2].channel != NULL && es->ch[ES_ADC].channel != NULL) { - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "fixed_rate", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_es137x_fixed_rate, "I", - "Enable fixed rate playback/recording"); + if (es->ch[ES_DAC2].channel != NULL && + es->ch[ES_ADC].channel != NULL) { + /* XXX: an user should be able to set this with a control tool, + if not done before 7.0-RELEASE, this needs to be converted + to a device specific sysctl "dev.pcm.X.yyy" via + device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "fixed_rate", CTLTYPE_INT | CTLFLAG_RW, + dev, sizeof(dev), sysctl_es137x_fixed_rate, "I", + "Enable fixed rate playback/recording"); } /* * Enable single pcm mixer sysctl if both DAC1/2 enabled. */ - if (es->ch[ES_DAC1].channel != NULL && es->ch[ES_DAC2].channel != NULL) { - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "single_pcm_mixer", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_es137x_single_pcm_mixer, "I", - "Single PCM mixer controller for both DAC1/DAC2"); + if (es->ch[ES_DAC1].channel != NULL && + es->ch[ES_DAC2].channel != NULL) { + /* XXX: an user should be able to set this with a control tool, + if not done before 7.0-RELEASE, this needs to be converted + to a device specific sysctl "dev.pcm.X.yyy" via + device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "single_pcm_mixer", + CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_single_pcm_mixer, "I", + "Single PCM mixer controller for both DAC1/DAC2"); } -#endif } - if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "latency_timer", &r) == 0 && - !(r < 0 || r > 255)) + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "latency_timer", &r) == 0 && !(r < 0 || r > 255)) pci_write_config(dev, PCIR_LATTIMER, r, 1); -#if 0 - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "latency_timer", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_es137x_latency_timer, "I", - "PCI Latency Timer configuration"); -#endif + /* XXX: this needs to be converted to a device specific sysctl + "dev.pcm.X.yyy" via device_get_sysctl_*() as discussed on + multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "latency_timer", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_latency_timer, "I", + "PCI Latency Timer configuration"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es_polling, "I", + "Enable polling mode"); #endif /* SND_DYNSYSCTL */ } static int es_pci_attach(device_t dev) { - u_int32_t data; + uint32_t data; struct es_info *es = NULL; int mapped, i, numplay, dac_cfg; char status[SND_STATUSLEN]; @@ -1420,11 +1687,8 @@ kobj_class_t ct = NULL; uint32_t devid; - if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - es->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + es = malloc(sizeof *es, M_DEVBUF, M_WAITOK | M_ZERO); + es->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_es137x softc"); es->dev = dev; es->escfg = 0; mapped = 0; @@ -1438,7 +1702,7 @@ es->regid = MEM_MAP_REG; es->regtype = SYS_RES_MEMORY; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, - RF_ACTIVE); + RF_ACTIVE); if (es->reg) mapped++; } @@ -1446,7 +1710,7 @@ es->regid = PCIR_BAR(0); es->regtype = SYS_RES_IOPORT; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, - RF_ACTIVE); + RF_ACTIVE); if (es->reg) mapped++; } @@ -1457,35 +1721,61 @@ es->st = rman_get_bustag(es->reg); es->sh = rman_get_bushandle(es->reg); - es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); + callout_init(&es->poll_timer, CALLOUT_MPSAFE); + es->poll_ticks = 1; if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "dac", &dac_cfg) == 0) { + device_get_unit(dev), "polling", &i) == 0 && i != 0) + es->polling = 1; + else + es->polling = 0; + + es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= ES_BLK_ALIGN; + if (i < ES_BLK_MIN) + i = ES_BLK_MIN; + es->blkcnt = es->bufsz / i; + i = 0; + while (es->blkcnt >> i) + i++; + es->blkcnt = 1 << (i - 1); + if (es->blkcnt < ES_DMA_SEGS_MIN) + es->blkcnt = ES_DMA_SEGS_MIN; + else if (es->blkcnt > ES_DMA_SEGS_MAX) + es->blkcnt = ES_DMA_SEGS_MAX; + + } else + es->blkcnt = 2; + + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "dac", &dac_cfg) == 0) { if (dac_cfg < 0 || dac_cfg > 3) dac_cfg = ES_DEFAULT_DAC_CFG; } else dac_cfg = ES_DEFAULT_DAC_CFG; switch (dac_cfg) { - case 0: /* Enable all DAC: DAC1, DAC2 */ - numplay = 2; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); - es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC2); - break; - case 1: /* Only DAC1 */ - numplay = 1; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); - break; - case 3: /* Enable all DAC / swap position: DAC2, DAC1 */ - numplay = 2; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); - es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC1); - break; - case 2: /* Only DAC2 */ - default: - numplay = 1; - es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); - break; + case 0: /* Enable all DAC: DAC1, DAC2 */ + numplay = 2; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); + es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC2); + break; + case 1: /* Only DAC1 */ + numplay = 1; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); + break; + case 3: /* Enable all DAC / swap position: DAC2, DAC1 */ + numplay = 2; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); + es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC1); + break; + case 2: /* Only DAC2 */ + default: + numplay = 1; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); + break; } es->escfg = ES_SET_NUMPLAY(es->escfg, numplay); es->escfg = ES_SET_NUMREC(es->escfg, 1); @@ -1514,9 +1804,8 @@ * This is a special case for es1370 only, where the * speed of both ADC and DAC2 locked together. */ - if (!ES_DAC2_ENABLED(es->escfg)) { + if (!ES_DAC2_ENABLED(es->escfg)) es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); - } if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; ct = &eschan1370_class; @@ -1528,13 +1817,15 @@ es->irqid = 0; es->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &es->irqid, - RF_ACTIVE | RF_SHAREABLE); - if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, es, &es->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, + es, &es->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), + /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -1546,8 +1837,9 @@ } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", - (es->regtype == SYS_RES_IOPORT)? "io" : "memory", - rman_get_start(es->reg), rman_get_start(es->irq),PCM_KLDSTRING(snd_es137x)); + (es->regtype == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(es->reg), rman_get_start(es->irq), + PCM_KLDSTRING(snd_es137x)); if (pcm_register(dev, es, numplay, 1)) goto bad; @@ -1557,25 +1849,30 @@ es_init_sysctls(dev); pcm_setstatus(dev, status); es->escfg = ES_SET_GP(es->escfg, 0); - if (numplay == 1) { + if (numplay == 1) device_printf(dev, "\n", - ES_DAC_FIRST(es->escfg) + 1); - } else if (numplay == 2) { + ES_DAC_FIRST(es->escfg) + 1); + else if (numplay == 2) device_printf(dev, "\n", - ES_DAC_FIRST(es->escfg) + 1, - ES_DAC_SECOND(es->escfg) + 1); - } - return 0; + ES_DAC_FIRST(es->escfg) + 1, ES_DAC_SECOND(es->escfg) + 1); + return (0); - bad: - if (es->parent_dmat) bus_dma_tag_destroy(es->parent_dmat); - if (es->ih) bus_teardown_intr(dev, es->irq, es->ih); - if (es->irq) bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); - if (codec) ac97_destroy(codec); - if (es->reg) bus_release_resource(dev, es->regtype, es->regid, es->reg); - if (es->lock) snd_mtxfree(es->lock); - if (es) free(es, M_DEVBUF); - return ENXIO; +bad: + if (es->parent_dmat) + bus_dma_tag_destroy(es->parent_dmat); + if (es->ih) + bus_teardown_intr(dev, es->irq, es->ih); + if (es->irq) + bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); + if (codec) + ac97_destroy(codec); + if (es->reg) + bus_release_resource(dev, es->regtype, es->regid, es->reg); + if (es->lock) + snd_mtxfree(es->lock); + if (es) + free(es, M_DEVBUF); + return (ENXIO); } static int @@ -1585,9 +1882,19 @@ struct es_info *es; r = pcm_unregister(dev); - if (r) return r; + if (r) + return (r); es = pcm_getdevinfo(dev); + + if (es != NULL && es->num != 0) { + ES_LOCK(es); + es->polling = 0; + callout_stop(&es->poll_timer); + ES_UNLOCK(es); + callout_drain(&es->poll_timer); + } + bus_teardown_intr(dev, es->irq, es->ih); bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); bus_release_resource(dev, es->regtype, es->regid, es->reg); @@ -1595,7 +1902,7 @@ snd_mtxfree(es->lock); free(es, M_DEVBUF); - return 0; + return (0); } static device_method_t es_methods[] = { --- sys/dev/sound/pci/es137x.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/es137x.h Thu Jul 12 12:04:19 2007 @@ -18,7 +18,7 @@ * 4. Modifications may be freely made to this file if the above conditions * are met. * - * $FreeBSD: src/sys/dev/sound/pci/es137x.h,v 1.5.2.1 2005/12/30 19:55:53 netchild Exp $ + * $FreeBSD: src/sys/dev/sound/pci/es137x.h,v 1.6 2005/07/31 13:19:38 netchild Exp $ */ #ifndef _ES1370_REG_H --- sys/dev/sound/pci/fm801.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/fm801.c Thu Jul 12 12:04:19 2007 @@ -29,11 +29,11 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/fm801.c,v 1.27.2.1 2006/01/10 01:01:24 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/fm801.c,v 1.33 2007/06/17 06:10:42 ariff Exp $"); #define PCI_VENDOR_FORTEMEDIA 0x1319 -#define PCI_DEVICE_FORTEMEDIA1 0x08011319 -#define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* ??? have no idea what's this... */ +#define PCI_DEVICE_FORTEMEDIA1 0x08011319 /* Audio controller */ +#define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* Joystick controller */ #define FM_PCM_VOLUME 0x00 #define FM_FM_VOLUME 0x02 @@ -334,7 +334,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, fm801->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, 0, fm801->bufsz) != 0) return NULL; return (void *)ch; } @@ -417,15 +417,16 @@ struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; - if(ch->dir == PCMDIR_PLAY) { - if(fm801->play_flip) return fm801->play_blksize; + /* + * Don't mind for play_flip, set the blocksize to the + * desired values in any case - otherwise sound playback + * breaks here. + */ + if(ch->dir == PCMDIR_PLAY) fm801->play_blksize = blocksize; - } - if(ch->dir == PCMDIR_REC) { - if(fm801->rec_flip) return fm801->rec_blksize; + if(ch->dir == PCMDIR_REC) fm801->rec_blksize = blocksize; - } DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir); @@ -442,9 +443,8 @@ DPRINT("fm801ch_trigger go %d , ", go); - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) { + if (!PCMTRIG_COMMON(go)) return 0; - } if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { @@ -575,11 +575,7 @@ int mapped = 0; char status[SND_STATUSLEN]; - if ((fm801 = (struct fm801_info *)malloc(sizeof(*fm801), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + fm801 = malloc(sizeof(*fm801), M_DEVBUF, M_WAITOK | M_ZERO); fm801->type = pci_get_devid(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -630,7 +626,8 @@ goto oops; } - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/hda/hda_reg.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/hda/hda_reg.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/hda/hda_reg.h,v 1.2.2.1 2007/05/13 21:09:24 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/hda/hda_reg.h,v 1.2 2006/11/26 12:24:05 ariff Exp $ */ #ifndef _HDA_REG_H_ --- sys/dev/sound/pci/hda/hdac.c.orig Thu Jul 12 14:39:08 2007 +++ sys/dev/sound/pci/hda/hdac.c Thu Jul 12 12:04:19 2007 @@ -84,10 +84,10 @@ #define HDA_DRV_TEST_REV "20070710_0047" #define HDA_WIDGET_PARSER_REV 1 -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/hda/hdac.c,v 1.36.2.5 2007/07/12 06:39:08 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/hda/hdac.c,v 1.44 2007/07/09 20:42:11 ariff Exp $"); #define HDA_BOOTVERBOSE(stmt) do { \ - if (bootverbose != 0) { \ + if (bootverbose != 0 || snd_verbose > 3) { \ stmt \ } \ } while(0) @@ -103,15 +103,22 @@ #define hdac_lockowned(sc) mtx_owned((sc)->lock) #if defined(__i386__) || defined(__amd64__) -#include +#if __FreeBSD_version < 602110 || defined(SND_USE_LPMAP) +#include +#endif +#endif + +#ifdef _LPMAP_C_ +#define HDAC_BUS_DMA_NOCACHE 0 #define HDAC_DMA_ATTR(sc, v, s, attr) do { \ vm_offset_t va = (vm_offset_t)(v); \ vm_size_t sz = (vm_size_t)(s); \ if ((sc) != NULL && ((sc)->flags & HDAC_F_DMA_NOCACHE) && \ va != 0 && sz != 0) \ - (void)pmap_change_attr(va, sz, (attr)); \ + (void)lpmap_change_attr(va, sz, (attr)); \ } while(0) #else +#define HDAC_BUS_DMA_NOCACHE BUS_DMA_NOCACHE #define HDAC_DMA_ATTR(...) #endif @@ -1404,7 +1411,7 @@ */ result = bus_dmamem_alloc(dma->dma_tag, (void **)&dma->dma_vaddr, BUS_DMA_NOWAIT | BUS_DMA_ZERO | - ((sc->flags & HDAC_F_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), + ((sc->flags & HDAC_F_DMA_NOCACHE) ? HDAC_BUS_DMA_NOCACHE : 0), &dma->dma_map); if (result != 0) { device_printf(sc->dev, "%s: bus_dmamem_alloc failed (%x)\n", @@ -1412,6 +1419,7 @@ goto hdac_dma_alloc_fail; } + HDAC_DMA_ATTR(sc, dma->dma_vaddr, roundsz, PAT_UNCACHEABLE); dma->dma_size = roundsz; /* @@ -1459,6 +1467,8 @@ bus_dmamap_unload(dma->dma_tag, dma->dma_map); } if (dma->dma_vaddr != NULL) { + HDAC_DMA_ATTR(sc, dma->dma_vaddr, dma->dma_size, + PAT_WRITE_BACK); bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); dma->dma_vaddr = NULL; } @@ -1527,7 +1537,7 @@ irq = &sc->irq; irq->irq_rid = 0x0; -#if __FreeBSD_version >= 602106 +#if !defined(_LPMAP_C_) && __FreeBSD_version >= 602106 if ((sc->flags & HDAC_F_MSI) && (result = pci_msi_count(sc->dev)) == 1 && pci_alloc_msi(sc->dev, &result) == 0) @@ -1576,7 +1586,7 @@ if (irq->irq_res != NULL) bus_release_resource(sc->dev, SYS_RES_IRQ, irq->irq_rid, irq->irq_res); -#if __FreeBSD_version >= 602106 +#if !defined(_LPMAP_C_) && __FreeBSD_version >= 602106 if ((sc->flags & HDAC_F_MSI) && irq->irq_rid == 0x1) pci_release_msi(sc->dev); #endif @@ -3025,7 +3035,9 @@ return (NULL); } - if (sndbuf_alloc(ch->b, sc->chan_dmat, sc->chan_size) != 0) + if (sndbuf_alloc(ch->b, sc->chan_dmat, + (sc->flags & HDAC_F_DMA_NOCACHE) ? HDAC_BUS_DMA_NOCACHE : 0, + sc->chan_size) != 0) return (NULL); HDAC_DMA_ATTR(sc, sndbuf_getbuf(ch->b), sndbuf_getmaxsize(ch->b), @@ -3034,6 +3046,7 @@ return (ch); } +#ifdef _LPMAP_C_ static int hdac_channel_free(kobj_t obj, void *data) { @@ -3050,6 +3063,7 @@ return (1); } +#endif static int hdac_channel_setformat(kobj_t obj, void *data, uint32_t format) @@ -3221,7 +3235,7 @@ struct hdac_chan *ch = data; struct hdac_softc *sc = ch->devinfo->codec->sc; - if (!(go == PCMTRIG_START || go == PCMTRIG_STOP || go == PCMTRIG_ABORT)) + if (!PCMTRIG_COMMON(go)) return (0); hdac_lock(sc); @@ -3274,10 +3288,13 @@ static kobj_method_t hdac_channel_methods[] = { KOBJMETHOD(channel_init, hdac_channel_init), +#ifdef _LPMAP_C_ KOBJMETHOD(channel_free, hdac_channel_free), +#endif KOBJMETHOD(channel_setformat, hdac_channel_setformat), KOBJMETHOD(channel_setspeed, hdac_channel_setspeed), KOBJMETHOD(channel_setblocksize, hdac_channel_setblocksize), + KOBJMETHOD(channel_setfragments, hdac_channel_setfragments), KOBJMETHOD(channel_trigger, hdac_channel_trigger), KOBJMETHOD(channel_getptr, hdac_channel_getptr), KOBJMETHOD(channel_getcaps, hdac_channel_getcaps), @@ -3761,7 +3778,7 @@ ); } -#if __FreeBSD_version >= 602106 +#if !defined(_LPMAP_C_) && __FreeBSD_version >= 602106 if (resource_int_value(device_get_name(dev), device_get_unit(dev), "msi", &i) == 0 && i != 0 && pci_msi_count(dev) == 1) --- sys/dev/sound/pci/hda/hdac.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/hda/hdac.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/hda/hdac.h,v 1.1.2.1 2007/05/13 21:09:24 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/hda/hdac.h,v 1.1 2006/10/01 11:12:59 ariff Exp $ */ #ifndef _HDAC_H_ --- sys/dev/sound/pci/hda/hdac_private.h.orig Thu Jul 12 14:39:08 2007 +++ sys/dev/sound/pci/hda/hdac_private.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/hda/hdac_private.h,v 1.6.2.3 2007/07/12 06:39:08 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/hda/hdac_private.h,v 1.8 2007/07/09 20:42:11 ariff Exp $ */ #ifndef _HDAC_PRIVATE_H_ --- sys/dev/sound/pci/hda/hdac_reg.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/hda/hdac_reg.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/hda/hdac_reg.h,v 1.1.2.1 2007/05/13 21:09:24 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/hda/hdac_reg.h,v 1.1 2006/10/01 11:12:59 ariff Exp $ */ #ifndef _HDAC_REG_H_ --- sys/dev/sound/pci/ich.c.orig Thu Jul 12 14:39:38 2007 +++ sys/dev/sound/pci/ich.c Thu Jul 12 12:04:19 2007 @@ -32,7 +32,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ich.c,v 1.53.2.12 2007/07/12 06:39:38 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ich.c,v 1.78 2007/07/11 14:27:45 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -93,15 +93,22 @@ #define ICH_HIGH_LATENCY (1 << 5) #if defined(__i386__) || defined(__amd64__) -#include +#if __FreeBSD_version < 602110 || defined(SND_USE_LPMAP) +#include +#endif +#endif + +#ifdef _LPMAP_C_ +#define ICH_BUS_DMA_NOCACHE 0 #define ICH_DMA_ATTR(sc, v, s, attr) do { \ vm_offset_t va = (vm_offset_t)(v); \ vm_size_t sz = (vm_size_t)(s); \ if ((sc) != NULL && ((sc)->flags & ICH_DMA_NOCACHE) && \ va != 0 && sz != 0) \ - (void)pmap_change_attr(va, sz, (attr)); \ + (void)lpmap_change_attr(va, sz, (attr)); \ } while(0) #else +#define ICH_BUS_DMA_NOCACHE BUS_DMA_NOCACHE #define ICH_DMA_ATTR(...) #endif @@ -424,7 +431,9 @@ ch->spdreg = 0; ICH_UNLOCK(sc); - if (sndbuf_alloc(ch->buffer, sc->chan_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->chan_dmat, + ((sc->flags & ICH_DMA_NOCACHE) ? ICH_BUS_DMA_NOCACHE : 0), + sc->bufsz) != 0) return (NULL); ICH_DMA_ATTR(sc, sndbuf_getbuf(ch->buffer), @@ -437,6 +446,7 @@ return (ch); } +#ifdef _LPMAP_C_ static int ichchan_free(kobj_t obj, void *data) { @@ -452,6 +462,7 @@ return (1); } +#endif static int ichchan_setformat(kobj_t obj, void *data, uint32_t format) @@ -607,7 +618,9 @@ static kobj_method_t ichchan_methods[] = { KOBJMETHOD(channel_init, ichchan_init), +#ifdef _LPMAP_C_ KOBJMETHOD(channel_free, ichchan_free), +#endif KOBJMETHOD(channel_setformat, ichchan_setformat), KOBJMETHOD(channel_setspeed, ichchan_setspeed), KOBJMETHOD(channel_setblocksize, ichchan_setblocksize), @@ -697,8 +710,11 @@ ich_initsys(struct sc_info* sc) { #ifdef SND_DYNSYSCTL - SYSCTL_ADD_INT(snd_sysctl_tree(sc->dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(sc->dev)), + /* XXX: this should move to a device specific sysctl "dev.pcm.X.yyy" + via device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "ac97rate", CTLFLAG_RW, &sc->ac97rate, 48000, "AC97 link rate (default = 48000)"); @@ -1077,10 +1093,12 @@ } if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT | - ((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0), + ((sc->flags & ICH_DMA_NOCACHE) ? ICH_BUS_DMA_NOCACHE : 0), &sc->dtmap)) goto bad; + ICH_DMA_ATTR(sc, sc->dtbl, sc->dtbl_size, PAT_UNCACHEABLE); + if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sc->dtbl_size, ich_setmap, sc, 0)) goto bad; @@ -1125,8 +1143,10 @@ sc->nabmbarid, sc->nabmbar); if (sc->dtmap) bus_dmamap_unload(sc->dmat, sc->dtmap); - if (sc->dtbl) + if (sc->dtbl) { + ICH_DMA_ATTR(sc, sc->dtbl, sc->dtbl_size, PAT_WRITE_BACK); bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); + } if (sc->chan_dmat) bus_dma_tag_destroy(sc->chan_dmat); if (sc->dmat) @@ -1153,6 +1173,7 @@ bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); bus_dmamap_unload(sc->dmat, sc->dtmap); + ICH_DMA_ATTR(sc, sc->dtbl, sc->dtbl_size, PAT_WRITE_BACK); bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); bus_dma_tag_destroy(sc->chan_dmat); bus_dma_tag_destroy(sc->dmat); --- sys/dev/sound/pci/maestro.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/maestro.c Thu Jul 12 12:04:19 2007 @@ -54,10 +54,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro.c,v 1.28.2.3 2006/02/04 11:58:28 netchild Exp $"); - - -#define inline __inline +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro.c,v 1.36 2007/06/17 06:10:42 ariff Exp $"); /* * PCI IDs of supported chips: @@ -189,72 +186,80 @@ #endif static unsigned int powerstate_init = PCI_POWERSTATE_D2; +/* XXX: this should move to a device specific sysctl dev.pcm.X.debug.Y via + device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ SYSCTL_NODE(_debug, OID_AUTO, maestro, CTLFLAG_RD, 0, ""); SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_active, CTLFLAG_RW, &powerstate_active, 0, "The Dx power state when active (0-1)"); SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_idle, CTLFLAG_RW, &powerstate_idle, 0, "The Dx power state when idle (0-2)"); SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_init, CTLFLAG_RW, - &powerstate_init, 0, "The Dx power state prior to the first use (0-2)"); + &powerstate_init, 0, + "The Dx power state prior to the first use (0-2)"); /* ----------------------------- * Prototypes */ -static inline void agg_lock(struct agg_info*); -static inline void agg_unlock(struct agg_info*); -static inline void agg_sleep(struct agg_info*, const char *wmesg, int msec); - -static inline u_int32_t agg_rd(struct agg_info*, int, int size); -static inline void agg_wr(struct agg_info*, int, u_int32_t data, int size); - -static inline int agg_rdcodec(struct agg_info*, int); -static inline int agg_wrcodec(struct agg_info*, int, u_int32_t); - -static inline void ringbus_setdest(struct agg_info*, int, int); - -static inline u_int16_t wp_rdreg(struct agg_info*, u_int16_t); -static inline void wp_wrreg(struct agg_info*, u_int16_t, u_int16_t); -static inline u_int16_t wp_rdapu(struct agg_info*, unsigned, u_int16_t); -static inline void wp_wrapu(struct agg_info*, unsigned, u_int16_t, u_int16_t); -static inline void wp_settimer(struct agg_info*, u_int); -static inline void wp_starttimer(struct agg_info*); -static inline void wp_stoptimer(struct agg_info*); - -static inline u_int16_t wc_rdreg(struct agg_info*, u_int16_t); -static inline void wc_wrreg(struct agg_info*, u_int16_t, u_int16_t); -static inline u_int16_t wc_rdchctl(struct agg_info*, int); -static inline void wc_wrchctl(struct agg_info*, int, u_int16_t); - -static inline void agg_stopclock(struct agg_info*, int part, int st); - -static inline void agg_initcodec(struct agg_info*); -static void agg_init(struct agg_info*); -static void agg_power(struct agg_info*, int); - -static void aggch_start_dac(struct agg_chinfo*); -static void aggch_stop_dac(struct agg_chinfo*); -static void aggch_start_adc(struct agg_rchinfo*); -static void aggch_stop_adc(struct agg_rchinfo*); -static void aggch_feed_adc_stereo(struct agg_rchinfo*); -static void aggch_feed_adc_mono(struct agg_rchinfo*); - -static inline void suppress_jitter(struct agg_chinfo*); -static inline void suppress_rec_jitter(struct agg_rchinfo*); - -static void set_timer(struct agg_info*); - -static void agg_intr(void *); -static int agg_probe(device_t); -static int agg_attach(device_t); -static int agg_detach(device_t); -static int agg_suspend(device_t); -static int agg_resume(device_t); -static int agg_shutdown(device_t); +static void agg_sleep(struct agg_info*, const char *wmesg, int msec); + +static __inline u_int32_t agg_rd(struct agg_info*, int, int size); +static __inline void agg_wr(struct agg_info*, int, u_int32_t data, + int size); +static int agg_rdcodec(struct agg_info*, int); +static int agg_wrcodec(struct agg_info*, int, u_int32_t); + +static void ringbus_setdest(struct agg_info*, int, int); + +static u_int16_t wp_rdreg(struct agg_info*, u_int16_t); +static void wp_wrreg(struct agg_info*, u_int16_t, u_int16_t); +static u_int16_t wp_rdapu(struct agg_info*, unsigned, u_int16_t); +static void wp_wrapu(struct agg_info*, unsigned, u_int16_t, u_int16_t); +static void wp_settimer(struct agg_info*, u_int); +static void wp_starttimer(struct agg_info*); +static void wp_stoptimer(struct agg_info*); + +#if 0 +static u_int16_t wc_rdreg(struct agg_info*, u_int16_t); +#endif +static void wc_wrreg(struct agg_info*, u_int16_t, u_int16_t); +#if 0 +static u_int16_t wc_rdchctl(struct agg_info*, int); +#endif +static void wc_wrchctl(struct agg_info*, int, u_int16_t); + +static void agg_stopclock(struct agg_info*, int part, int st); + +static void agg_initcodec(struct agg_info*); +static void agg_init(struct agg_info*); +static void agg_power(struct agg_info*, int); + +static void aggch_start_dac(struct agg_chinfo*); +static void aggch_stop_dac(struct agg_chinfo*); +static void aggch_start_adc(struct agg_rchinfo*); +static void aggch_stop_adc(struct agg_rchinfo*); +static void aggch_feed_adc_stereo(struct agg_rchinfo*); +static void aggch_feed_adc_mono(struct agg_rchinfo*); + +#ifdef AGG_JITTER_CORRECTION +static void suppress_jitter(struct agg_chinfo*); +static void suppress_rec_jitter(struct agg_rchinfo*); +#endif + +static void set_timer(struct agg_info*); + +static void agg_intr(void *); +static int agg_probe(device_t); +static int agg_attach(device_t); +static int agg_detach(device_t); +static int agg_suspend(device_t); +static int agg_resume(device_t); +static int agg_shutdown(device_t); static void *dma_malloc(bus_dma_tag_t, u_int32_t, bus_addr_t*); -static void dma_free(bus_dma_tag_t, void *); +static void dma_free(bus_dma_tag_t, void *); /* ----------------------------- @@ -262,24 +267,10 @@ */ /* locking */ +#define agg_lock(sc) snd_mtxlock(&((sc)->lock)) +#define agg_unlock(sc) snd_mtxunlock(&((sc)->lock)) -static inline void -agg_lock(struct agg_info *sc) -{ -#ifdef USING_MUTEX - mtx_lock(&sc->lock); -#endif -} - -static inline void -agg_unlock(struct agg_info *sc) -{ -#ifdef USING_MUTEX - mtx_unlock(&sc->lock); -#endif -} - -static inline void +static void agg_sleep(struct agg_info *sc, const char *wmesg, int msec) { int timo; @@ -297,7 +288,7 @@ /* I/O port */ -static inline u_int32_t +static __inline u_int32_t agg_rd(struct agg_info *sc, int regno, int size) { switch (size) { @@ -317,7 +308,7 @@ ((struct agg_info*)(sc))->st, \ ((struct agg_info*)(sc))->sh, (regno)) -static inline void +static __inline void agg_wr(struct agg_info *sc, int regno, u_int32_t data, int size) { switch (size) { @@ -342,7 +333,7 @@ /* Codec/Ringbus */ -static inline int +static int agg_codec_wait4idle(struct agg_info *ess) { unsigned t = 26; @@ -356,7 +347,7 @@ } -static inline int +static int agg_rdcodec(struct agg_info *ess, int regno) { int ret; @@ -383,7 +374,7 @@ return ret; } -static inline int +static int agg_wrcodec(struct agg_info *ess, int regno, u_int32_t data) { /* We have to wait for a SAFE time to write addr/data */ @@ -406,7 +397,7 @@ return 0; } -static inline void +static void ringbus_setdest(struct agg_info *ess, int src, int dest) { u_int32_t data; @@ -421,21 +412,21 @@ /* Wave Processor */ -static inline u_int16_t +static u_int16_t wp_rdreg(struct agg_info *ess, u_int16_t reg) { AGG_WR(ess, PORT_DSP_INDEX, reg, 2); return AGG_RD(ess, PORT_DSP_DATA, 2); } -static inline void +static void wp_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { AGG_WR(ess, PORT_DSP_INDEX, reg, 2); AGG_WR(ess, PORT_DSP_DATA, data, 2); } -static inline int +static int wp_wait_data(struct agg_info *ess, u_int16_t data) { unsigned t = 0; @@ -450,7 +441,7 @@ return 0; } -static inline u_int16_t +static u_int16_t wp_rdapu(struct agg_info *ess, unsigned ch, u_int16_t reg) { wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); @@ -459,14 +450,15 @@ return wp_rdreg(ess, WPREG_DATA_PORT); } -static inline void +static void wp_wrapu(struct agg_info *ess, unsigned ch, u_int16_t reg, u_int16_t data) { wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); if (wp_wait_data(ess, reg | (ch << 4)) == 0) { wp_wrreg(ess, WPREG_DATA_PORT, data); if (wp_wait_data(ess, data) != 0) - device_printf(ess->dev, "wp_wrapu() write timed out.\n"); + device_printf(ess->dev, + "wp_wrapu() write timed out.\n"); } else { device_printf(ess->dev, "wp_wrapu() indexing timed out.\n"); } @@ -490,7 +482,7 @@ wp_wrapu(ess, apuch, APUREG_FREQ_HIWORD, dv >> 8); } -static inline void +static void wp_settimer(struct agg_info *ess, u_int divide) { u_int prescale = 0; @@ -511,7 +503,7 @@ wp_wrreg(ess, WPREG_TIMER_ENABLE, 1); } -static inline void +static void wp_starttimer(struct agg_info *ess) { AGG_WR(ess, PORT_INT_STAT, 1, 2); @@ -520,7 +512,7 @@ wp_wrreg(ess, WPREG_TIMER_START, 1); } -static inline void +static void wp_stoptimer(struct agg_info *ess) { AGG_WR(ess, PORT_HOSTINT_CTRL, ~HOSTINT_CTRL_DSOUND_INT_ENABLED @@ -533,27 +525,31 @@ /* WaveCache */ -static inline u_int16_t +#if 0 +static u_int16_t wc_rdreg(struct agg_info *ess, u_int16_t reg) { AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); return AGG_RD(ess, PORT_WAVCACHE_DATA, 2); } +#endif -static inline void +static void wc_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); AGG_WR(ess, PORT_WAVCACHE_DATA, data, 2); } -static inline u_int16_t +#if 0 +static u_int16_t wc_rdchctl(struct agg_info *ess, int ch) { return wc_rdreg(ess, ch << 3); } +#endif -static inline void +static void wc_wrchctl(struct agg_info *ess, int ch, u_int16_t data) { wc_wrreg(ess, ch << 3, data); @@ -562,7 +558,7 @@ /* -------------------------------------------------------------------- */ /* Power management */ -static inline void +static void agg_stopclock(struct agg_info *ess, int part, int st) { u_int32_t data; @@ -586,7 +582,7 @@ * Controller. */ -static inline void +static void agg_initcodec(struct agg_info* ess) { u_int16_t data; @@ -770,7 +766,8 @@ DELAY(100); #if 0 if ((agg_rdcodec(ess, AC97_REG_POWER) & 3) != 3) - device_printf(ess->dev, "warning: codec not ready.\n"); + device_printf(ess->dev, + "warning: codec not ready.\n"); #endif AGG_WR(ess, PORT_RINGBUS_CTRL, (AGG_RD(ess, PORT_RINGBUS_CTRL, 4) @@ -1065,7 +1062,7 @@ * * XXX - this function works in 16bit stereo format only. */ -static inline void +static void interleave(int16_t *l, int16_t *r, int16_t *p, unsigned n) { int16_t *end; @@ -1107,7 +1104,7 @@ * * XXX - this function works in 16bit monoral format only. */ -static inline void +static void mixdown(int16_t *src, int16_t *dest, unsigned n) { int16_t *end; @@ -1135,12 +1132,13 @@ ch->hwptr = cur; } +#ifdef AGG_JITTER_CORRECTION /* * Stereo jitter suppressor. * Sometimes playback pointers differ in stereo-paired channels. * Calling this routine within intr fixes the problem. */ -static inline void +static void suppress_jitter(struct agg_chinfo *ch) { if (ch->stereo) { @@ -1157,7 +1155,7 @@ } } -static inline void +static void suppress_rec_jitter(struct agg_rchinfo *ch) { int cp1, cp2, diff /*, halfsize*/ ; @@ -1172,8 +1170,9 @@ AGG_WR(ch->parent, PORT_DSP_DATA, cp1, 2); } } +#endif -static inline u_int +static u_int calc_timer_div(struct agg_chinfo *ch) { u_int speed; @@ -1190,7 +1189,7 @@ + speed - 1) / speed; } -static inline u_int +static u_int calc_timer_div_rch(struct agg_rchinfo *ch) { u_int speed; @@ -1277,7 +1276,8 @@ /* Playback channel. */ static void * -aggpch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +aggpch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { struct agg_info *ess = devinfo; struct agg_chinfo *ch; @@ -1482,7 +1482,8 @@ /* Recording channel. */ static void * -aggrch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +aggrch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { struct agg_info *ess = devinfo; struct agg_rchinfo *ch; @@ -1772,15 +1773,11 @@ char status[SND_STATUSLEN]; int ret = 0; - if ((ess = malloc(sizeof *ess, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - ret = ENOMEM; - goto bad; - } + ess = malloc(sizeof(*ess), M_DEVBUF, M_WAITOK | M_ZERO); ess->dev = dev; #ifdef USING_MUTEX - mtx_init(&ess->lock, device_get_desc(dev), "hardware status lock", + mtx_init(&ess->lock, device_get_desc(dev), "snd_maestro softc", MTX_DEF | MTX_RECURSE); if (!mtx_initialized(&ess->lock)) { device_printf(dev, "failed to create a mutex.\n"); @@ -1790,7 +1787,7 @@ #endif ess->bufsz = pcm_getbuffersize(dev, 4096, AGG_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/ NULL, + if (bus_dma_tag_create(/*parent*/ bus_get_dma_tag(dev), /*align */ 4, 1 << (16+1), /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, /*filter*/ NULL, NULL, @@ -1805,7 +1802,7 @@ goto bad; } - if (bus_dma_tag_create(/*parent*/NULL, + if (bus_dma_tag_create(/*parent*/ bus_get_dma_tag(dev), /*align */ 1 << WAVCACHE_BASEADDR_SHIFT, 1 << (16+1), /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, --- sys/dev/sound/pci/maestro3.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/maestro3.c Thu Jul 12 12:04:19 2007 @@ -61,7 +61,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro3.c,v 1.28.2.2 2007/03/12 02:03:25 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro3.c,v 1.35 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -407,7 +407,7 @@ ch->fmt = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_pchan_init chn_allocbuf failed\n"); return (NULL); } @@ -573,6 +573,9 @@ struct sc_info *sc = ch->parent; int ret; + if (!PCMTRIG_COMMON(go)) + return (0); + M3_LOCK(sc); ret = m3_pchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); @@ -756,7 +759,7 @@ ch->fmt = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_rchan_init chn_allocbuf failed\n"); return (NULL); } @@ -916,6 +919,9 @@ struct sc_info *sc = ch->parent; int ret; + if (!PCMTRIG_COMMON(go)) + return (0); + M3_LOCK(sc); ret = m3_rchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); @@ -1259,20 +1265,11 @@ M3_DEBUG(CALL, ("m3_pci_attach\n")); - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); sc->sc_lock = snd_mtxcreate(device_get_nameunit(dev), - "sound softc"); - if (sc->sc_lock == NULL) { - device_printf(dev, "cannot create mutex\n"); - free(sc, M_DEVBUF); - return (ENXIO); - } + "snd_maestro3 softc"); for (card = m3_card_types ; card->pci_id ; card++) { if (sc->type == card->pci_id) { sc->which = card->which; @@ -1332,7 +1329,7 @@ M3_BUFSIZE_MAX); if (bus_dma_tag_create( - NULL, /* parent */ + bus_get_dma_tag(dev), /* parent */ 2, 0, /* alignment, boundary */ M3_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ @@ -1401,11 +1398,7 @@ /* Create the buffer for saving the card state during suspend */ len = sizeof(u_int16_t) * (REV_B_CODE_MEMORY_LENGTH + REV_B_DATA_MEMORY_LENGTH); - sc->savemem = (u_int16_t*)malloc(len, M_DEVBUF, M_NOWAIT | M_ZERO); - if (sc->savemem == NULL) { - device_printf(dev, "Failed to create suspend buffer\n"); - goto bad; - } + sc->savemem = (u_int16_t*)malloc(len, M_DEVBUF, M_WAITOK | M_ZERO); return 0; --- sys/dev/sound/pci/neomagic.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/neomagic.c Thu Jul 12 12:04:19 2007 @@ -34,7 +34,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/neomagic.c,v 1.34.2.1 2005/12/30 19:55:53 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/neomagic.c,v 1.37 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -406,7 +406,7 @@ struct sc_info *sc = ch->parent; int ssz; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ssz = (ch->fmt & AFMT_16BIT)? 2 : 1; @@ -671,11 +671,7 @@ struct ac97_info *codec = 0; char status[SND_STATUSLEN]; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->type = pci_get_devid(dev); --- sys/dev/sound/pci/solo.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/solo.c Thu Jul 12 12:04:19 2007 @@ -33,7 +33,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/solo.c,v 1.35.2.4 2006/07/13 01:53:54 yongari Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/solo.c,v 1.45 2007/06/17 06:10:42 ariff Exp $"); #define SOLO_DEFAULT_BUFSZ 16384 #define ABS(x) (((x) < 0)? -(x) : (x)) @@ -92,7 +92,8 @@ void *ih; bus_dma_tag_t parent_dmat; - int simplex_dir, type, duplex:1, newspeed:1, dmasz[2]; + int simplex_dir, type, dmasz[2]; + unsigned int duplex:1, newspeed:1; unsigned int bufsz; struct ess_chinfo pch, rch; @@ -538,7 +539,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) return NULL; ch->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) @@ -584,10 +585,11 @@ struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; - DEB(printf("esschan_trigger: %d\n",go)); - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; + DEB(printf("esschan_trigger: %d\n",go)); + ess_lock(sc); switch (go) { case PCMTRIG_START: @@ -897,7 +899,7 @@ RF_ACTIVE | RF_SHAREABLE); #if ESS18XX_MPSAFE == 1 - sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_solo softc"); return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp && sc->lock)? 0 : ENXIO; @@ -984,10 +986,7 @@ u_int16_t ddma; u_int32_t data; - sc = (struct ess_info *)malloc(sizeof *sc, M_DEVBUF, M_NOWAIT | M_ZERO); - if (!sc) - return ENXIO; - + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN; pci_write_config(dev, PCIR_COMMAND, data, 2); @@ -1030,9 +1029,9 @@ pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); #if 0 - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/65536, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/65536, /*boundary*/0, #endif - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/spicds.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/spicds.c Thu Jul 12 12:04:19 2007 @@ -24,7 +24,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/spicds.c,v 1.5.2.2 2007/06/11 19:33:27 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/spicds.c,v 1.6 2007/05/27 19:58:39 joel Exp $ */ #include --- sys/dev/sound/pci/spicds.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/spicds.h Thu Jul 12 12:04:19 2007 @@ -24,7 +24,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/spicds.h,v 1.3.2.2 2007/06/11 19:33:28 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pci/spicds.h,v 1.4 2007/05/27 19:58:39 joel Exp $ */ /* supported CODECs */ --- sys/dev/sound/pci/t4dwave.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/t4dwave.c Thu Jul 12 12:04:19 2007 @@ -31,7 +31,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/t4dwave.c,v 1.48 2005/03/01 08:58:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/t4dwave.c,v 1.53 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -494,7 +494,7 @@ ch->buffer = b; ch->parent = tr; ch->channel = c; - if (sndbuf_alloc(ch->buffer, tr->parent_dmat, tr->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; @@ -533,7 +533,7 @@ { struct tr_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { @@ -602,7 +602,7 @@ ch->buffer = b; ch->parent = tr; ch->channel = c; - if (sndbuf_alloc(ch->buffer, tr->parent_dmat, tr->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; @@ -658,7 +658,7 @@ struct tr_info *tr = ch->parent; u_int32_t i; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { @@ -814,14 +814,10 @@ int i; char status[SND_STATUSLEN]; - if ((tr = malloc(sizeof(*tr), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + tr = malloc(sizeof(*tr), M_DEVBUF, M_WAITOK | M_ZERO); tr->type = pci_get_devid(dev); tr->rev = pci_get_revid(dev); - tr->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + tr->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_t4dwave softc"); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); @@ -860,7 +856,8 @@ goto bad; } - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/TR_MAXADDR, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/via8233.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/via8233.c Thu Jul 12 12:04:19 2007 @@ -32,7 +32,7 @@ * Grzybowski Rafal, Russell Davies, Mark Handley, Daniel O'Connor for * comments, machine time, testing patches, and patience. VIA for * providing specs. ALSA for helpful comments and some register poke - * ordering. + * ordering. */ #include @@ -44,7 +44,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via8233.c,v 1.20.2.3 2007/04/26 08:21:44 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via8233.c,v 1.37 2007/06/14 11:13:38 ariff Exp $"); #define VIA8233_PCI_ID 0x30591106 @@ -62,13 +62,18 @@ #define NWRCHANS 1 /* No of write channels */ #define NCHANS (NWRCHANS + NDXSCHANS + NMSGDCHANS) #define NSEGS NCHANS * SEGS_PER_CHAN /* Segments in SGD table */ +#define VIA_SEGS_MIN 2 +#define VIA_SEGS_MAX 64 +#define VIA_SEGS_DEFAULT 2 +#define VIA_BLK_MIN 32 +#define VIA_BLK_ALIGN (~(VIA_BLK_MIN - 1)) #define VIA_DEFAULT_BUFSZ 0x1000 /* we rely on this struct being packed to 64 bits */ struct via_dma_op { - volatile u_int32_t ptr; - volatile u_int32_t flags; + volatile uint32_t ptr; + volatile uint32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 @@ -83,11 +88,14 @@ struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; - int dir, blksz; - int rbase; + int dir, rbase, active; + unsigned int blksz, blkcnt; + unsigned int ptr, prevptr; }; struct via_info { + device_t dev; + bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; @@ -100,18 +108,21 @@ void *ih; struct ac97_info *codec; - unsigned int bufsz; + unsigned int bufsz, blkcnt; int dxs_src, dma_eol_wake; struct via_chinfo pch[NDXSCHANS + NMSGDCHANS]; struct via_chinfo rch[NWRCHANS]; struct via_dma_op *sgd_table; - u_int16_t codec_caps; - u_int16_t n_dxs_registered; + uint16_t codec_caps; + uint16_t n_dxs_registered; + int play_num, rec_num; struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; -static u_int32_t via_fmt[] = { +static uint32_t via_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, @@ -122,6 +133,23 @@ static struct pcmchan_caps via_vracaps = { 4000, 48000, via_fmt, 0 }; static struct pcmchan_caps via_caps = { 48000, 48000, via_fmt, 0 }; +static __inline int +via_chan_active(struct via_info *via) +{ + int i, ret = 0; + + if (via == NULL) + return (0); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) + ret += via->pch[i].active; + + for (i = 0; i < NWRCHANS; i++) + ret += via->rch[i].active; + + return (ret); +} + #ifdef SND_DYNSYSCTL static int sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) @@ -137,12 +165,12 @@ r = pci_read_config(dev, VIA_PCI_SPDIF, 1); snd_mtxunlock(via->lock); new_en = (r & VIA_SPDIF_EN) ? 1 : 0; - err = sysctl_handle_int(oidp, &new_en, sizeof(new_en), req); + err = sysctl_handle_int(oidp, &new_en, 0, req); if (err || req->newptr == NULL) - return err; + return (err); if (new_en < 0 || new_en > 1) - return EINVAL; + return (EINVAL); if (new_en) r |= VIA_SPDIF_EN; @@ -152,10 +180,9 @@ pci_write_config(dev, VIA_PCI_SPDIF, r, 1); snd_mtxunlock(via->lock); - return 0; + return (0); } -#if 0 static int sysctl_via8233_dxs_src(SYSCTL_HANDLER_ARGS) { @@ -168,60 +195,99 @@ snd_mtxlock(via->lock); val = via->dxs_src; snd_mtxunlock(via->lock); - err = sysctl_handle_int(oidp, &val, sizeof(val), req); + err = sysctl_handle_int(oidp, &val, 0, req); if (err || req->newptr == NULL) - return err; + return (err); if (val < 0 || val > 1) - return EINVAL; + return (EINVAL); snd_mtxlock(via->lock); via->dxs_src = val; snd_mtxunlock(via->lock); - return 0; + return (0); +} + +static int +sysctl_via_polling(SYSCTL_HANDLER_ARGS) +{ + struct via_info *via; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + via = pcm_getdevinfo(dev); + if (via == NULL) + return (EINVAL); + snd_mtxlock(via->lock); + val = via->polling; + snd_mtxunlock(via->lock); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + snd_mtxlock(via->lock); + if (val != via->polling) { + if (via_chan_active(via) != 0) + err = EBUSY; + else if (val == 0) + via->polling = 0; + else + via->polling = 1; + } + snd_mtxunlock(via->lock); + + return (err); } -#endif #endif /* SND_DYNSYSCTL */ static void via_init_sysctls(device_t dev) { #ifdef SND_DYNSYSCTL - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "spdif_enabled", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_via8233_spdif_enable, "I", - "Enable S/PDIF output on primary playback channel"); -#if 0 - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "via_dxs_src", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_via8233_dxs_src, "I", - "Enable VIA DXS Sample Rate Converter"); -#endif + /* XXX: an user should be able to set this with a control tool, + if not done before 7.0-RELEASE, this needs to be converted to + a device specific sysctl "dev.pcm.X.yyy" via device_get_sysctl_*() + as discussed on multimedia@ in msg-id <861wujij2q.fsf@xps.des.no> */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via8233_spdif_enable, "I", + "Enable S/PDIF output on primary playback channel"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "dxs_src", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via8233_dxs_src, "I", + "Enable VIA DXS Sample Rate Converter"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via_polling, "I", + "Enable polling mode"); #endif } -static __inline u_int32_t +static __inline uint32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: - return bus_space_read_1(via->st, via->sh, regno); + return (bus_space_read_1(via->st, via->sh, regno)); case 2: - return bus_space_read_2(via->st, via->sh, regno); + return (bus_space_read_2(via->st, via->sh, regno)); case 4: - return bus_space_read_4(via->st, via->sh, regno); + return (bus_space_read_4(via->st, via->sh, regno)); default: - return 0xFFFFFFFF; + return (0xFFFFFFFF); } } static __inline void -via_wr(struct via_info *via, int regno, u_int32_t data, int size) +via_wr(struct via_info *via, int regno, uint32_t data, int size) { switch (size) { @@ -248,11 +314,11 @@ /* poll until codec not busy */ for (i = 0; i < 1000; i++) { if ((via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_BUSY) == 0) - return 0; + return (0); DELAY(1); } - printf("via: codec busy\n"); - return 1; + device_printf(via->dev, "%s: codec busy\n", __func__); + return (1); } static int @@ -263,25 +329,26 @@ /* poll until codec valid */ for (i = 0; i < 1000; i++) { if (via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_CODEC00_VALID) - return 0; + return (0); DELAY(1); } - printf("via: codec invalid\n"); - return 1; + device_printf(via->dev, "%s: codec invalid\n", __func__); + return (1); } static int -via_write_codec(kobj_t obj, void *addr, int reg, u_int32_t val) +via_write_codec(kobj_t obj, void *addr, int reg, uint32_t val) { struct via_info *via = addr; - if (via_waitready_codec(via)) return -1; + if (via_waitready_codec(via)) + return (-1); - via_wr(via, VIA_AC97_CONTROL, + via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_INDEX(reg) | VIA_AC97_DATA(val), 4); - return 0; + return (0); } static int @@ -290,23 +357,23 @@ struct via_info *via = addr; if (via_waitready_codec(via)) - return -1; + return (-1); - via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | - VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); + via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | + VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); if (via_waitready_codec(via)) - return -1; + return (-1); if (via_waitvalid_codec(via)) - return -1; + return (-1); - return via_rd(via, VIA_AC97_CONTROL, 2); + return (via_rd(via, VIA_AC97_CONTROL, 2)); } static kobj_method_t via_ac97_methods[] = { - KOBJMETHOD(ac97_read, via_read_codec), - KOBJMETHOD(ac97_write, via_write_codec), + KOBJMETHOD(ac97_read, via_read_codec), + KOBJMETHOD(ac97_write, via_write_codec), { 0, 0 } }; AC97_DECLARE(via_ac97); @@ -316,31 +383,30 @@ static int via_buildsgdt(struct via_chinfo *ch) { - u_int32_t phys_addr, flag; - int i, seg_size; + uint32_t phys_addr, flag; + int i; - seg_size = sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN; phys_addr = sndbuf_getbufaddr(ch->buffer); - for (i = 0; i < SEGS_PER_CHAN; i++) { - flag = (i == SEGS_PER_CHAN - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; - ch->sgd_table[i].ptr = phys_addr + (i * seg_size); - ch->sgd_table[i].flags = flag | seg_size; + for (i = 0; i < ch->blkcnt; i++) { + flag = (i == ch->blkcnt - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; + ch->sgd_table[i].ptr = phys_addr + (i * ch->blksz); + ch->sgd_table[i].flags = flag | ch->blksz; } - return 0; + return (0); } /* -------------------------------------------------------------------- */ /* Format setting functions */ static int -via8233wr_setformat(kobj_t obj, void *data, u_int32_t format) +via8233wr_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t f = WR_FORMAT_STOP_INDEX; + uint32_t f = WR_FORMAT_STOP_INDEX; if (format & AFMT_STEREO) f |= WR_FORMAT_STEREO; @@ -350,15 +416,15 @@ via_wr(via, VIA_WR0_FORMAT, f, 4); snd_mtxunlock(via->lock); - return 0; + return (0); } static int -via8233dxs_setformat(kobj_t obj, void *data, u_int32_t format) +via8233dxs_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t r, v; + uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); @@ -367,22 +433,22 @@ v &= ~(VIA8233_DXS_RATEFMT_STEREO | VIA8233_DXS_RATEFMT_16BIT); if (format & AFMT_STEREO) v |= VIA8233_DXS_RATEFMT_STEREO; - if (format & AFMT_16BIT) + if (format & AFMT_16BIT) v |= VIA8233_DXS_RATEFMT_16BIT; via_wr(via, r, v, 4); snd_mtxunlock(via->lock); - return 0; + return (0); } static int -via8233msgd_setformat(kobj_t obj, void *data, u_int32_t format) +via8233msgd_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t s = 0xff000000; - u_int8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; + uint32_t s = 0xff000000; + uint8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; if (format & AFMT_STEREO) { v |= MC_SGD_CHANNELS(2); @@ -397,30 +463,30 @@ via_wr(via, VIA_MC_SGD_FORMAT, v, 1); snd_mtxunlock(via->lock); - return 0; + return (0); } /* -------------------------------------------------------------------- */ /* Speed setting functions */ static int -via8233wr_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233wr_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) - return ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed); + return (ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed)); - return 48000; + return (48000); } static int -via8233dxs_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233dxs_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t r, v; + uint32_t r, v; r = ch->rbase + VIA8233_RP_DXS_RATEFMT; snd_mtxlock(via->lock); @@ -432,19 +498,19 @@ via_wr(via, r, v, 4); snd_mtxunlock(via->lock); - return speed; + return (speed); } static int -via8233msgd_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233msgd_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) - return ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed); + return (ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed)); - return 48000; + return (48000); } /* -------------------------------------------------------------------- */ @@ -458,8 +524,8 @@ /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) - return &via_vracaps; - return &via_caps; + return (&via_vracaps); + return (&via_caps); } static struct pcmchan_caps * @@ -475,8 +541,8 @@ * conversion. */ if (via->dxs_src) - return &via_vracaps; - return &via_caps; + return (&via_vracaps); + return (&via_caps); } static struct pcmchan_caps * @@ -487,21 +553,61 @@ /* Controlled by ac97 registers */ if (via->codec_caps & AC97_EXTCAP_VRA) - return &via_vracaps; - return &via_caps; + return (&via_vracaps); + return (&via_caps); } /* -------------------------------------------------------------------- */ /* Common functions */ static int -via8233chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +via8233chan_setfragments(kobj_t obj, void *data, + uint32_t blksz, uint32_t blkcnt) { struct via_chinfo *ch = data; + struct via_info *via = ch->parent; + + blksz &= VIA_BLK_ALIGN; + + if (blksz > (sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN)) + blksz = sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN; + if (blksz < VIA_BLK_MIN) + blksz = VIA_BLK_MIN; + if (blkcnt > VIA_SEGS_MAX) + blkcnt = VIA_SEGS_MAX; + if (blkcnt < VIA_SEGS_MIN) + blkcnt = VIA_SEGS_MIN; + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= VIA_SEGS_MIN) + blkcnt >>= 1; + else if ((blksz >> 1) >= VIA_BLK_MIN) + blksz >>= 1; + else + break; + } + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(via->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); - sndbuf_resize(ch->buffer, SEGS_PER_CHAN, blocksize); ch->blksz = sndbuf_getblksz(ch->buffer); - return ch->blksz; + ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + + return (1); +} + +static int +via8233chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct via_chinfo *ch = data; + struct via_info *via = ch->parent; + + via8233chan_setfragments(obj, data, blksz, via->blkcnt); + + return (ch->blksz); } static int @@ -509,18 +615,23 @@ { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t v, index, count; + uint32_t v, index, count; int ptr; snd_mtxlock(via->lock); - v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); - snd_mtxunlock(via->lock); - index = v >> 24; /* Last completed buffer */ - count = v & 0x00ffffff; /* Bytes remaining */ - ptr = (index + 1) * ch->blksz - count; - ptr %= SEGS_PER_CHAN * ch->blksz; /* Wrap to available space */ + if (via->polling != 0) { + ptr = ch->ptr; + snd_mtxunlock(via->lock); + } else { + v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); + snd_mtxunlock(via->lock); + index = v >> 24; /* Last completed buffer */ + count = v & 0x00ffffff; /* Bytes remaining */ + ptr = (index + 1) * ch->blksz - count; + ptr %= ch->blkcnt * ch->blksz; /* Wrap to available space */ + } - return ptr; + return (ptr); } static void @@ -528,8 +639,8 @@ { via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via_wr(via, ch->rbase + VIA_RP_CONTROL, 0x00, 1); - via_wr(via, ch->rbase + VIA_RP_STATUS, - SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); + via_wr(via, ch->rbase + VIA_RP_STATUS, + SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); } /* -------------------------------------------------------------------- */ @@ -538,93 +649,107 @@ static void via8233chan_sgdinit(struct via_info *via, struct via_chinfo *ch, int chnum) { - ch->sgd_table = &via->sgd_table[chnum * SEGS_PER_CHAN]; - ch->sgd_addr = via->sgd_addr + chnum * SEGS_PER_CHAN * sizeof(struct via_dma_op); + ch->sgd_table = &via->sgd_table[chnum * VIA_SEGS_MAX]; + ch->sgd_addr = via->sgd_addr + chnum * VIA_SEGS_MAX * + sizeof(struct via_dma_op); } static void* via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; - struct via_chinfo *ch = &via->rch[c->num]; + struct via_chinfo *ch; + int num; + snd_mtxlock(via->lock); + num = via->rec_num++; + ch = &via->rch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; - - ch->rbase = VIA_WR_BASE(c->num); - snd_mtxlock(via->lock); + ch->blkcnt = via->blkcnt; + ch->rbase = VIA_WR_BASE(num); via_wr(via, ch->rbase + VIA_WR_RP_SGD_FORMAT, WR_FIFO_ENABLE, 1); snd_mtxunlock(via->lock); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) + return (NULL); snd_mtxlock(via->lock); - via8233chan_sgdinit(via, ch, c->num); + via8233chan_sgdinit(via, ch, num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); - return ch; + return (ch); } static void* via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; - struct via_chinfo *ch = &via->pch[c->num]; + struct via_chinfo *ch; + int num; + snd_mtxlock(via->lock); + num = via->play_num++; + ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; + ch->blkcnt = via->blkcnt; /* * All cards apparently support DXS3, but not other DXS * channels. We therefore want to align first DXS channel to * DXS3. */ - snd_mtxlock(via->lock); ch->rbase = VIA_DXS_BASE(NDXSCHANS - 1 - via->n_dxs_registered); via->n_dxs_registered++; snd_mtxunlock(via->lock); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) + return (NULL); snd_mtxlock(via->lock); - via8233chan_sgdinit(via, ch, NWRCHANS + c->num); + via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); - return ch; + return (ch); } static void* via8233msgd_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; - struct via_chinfo *ch = &via->pch[c->num]; + struct via_chinfo *ch; + int num; + snd_mtxlock(via->lock); + num = via->play_num++; + ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->rbase = VIA_MC_SGD_STATUS; + ch->blkcnt = via->blkcnt; + snd_mtxunlock(via->lock); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) + return (NULL); snd_mtxlock(via->lock); - via8233chan_sgdinit(via, ch, NWRCHANS + c->num); + via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); snd_mtxunlock(via->lock); - return ch; + return (ch); } static void @@ -635,19 +760,137 @@ muted = (muted) ? VIA8233_DXS_MUTE : 0; via_wr(via, ch->rbase + VIA8233_RP_DXS_LVOL, muted, 1); via_wr(via, ch->rbase + VIA8233_RP_DXS_RVOL, muted, 1); - r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & VIA8233_DXS_MUTE; - if (r != muted) { - printf("via: failed to set dxs volume " - "(dxs base 0x%02x).\n", ch->rbase); - } + r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & + VIA8233_DXS_MUTE; + if (r != muted) + device_printf(via->dev, + "%s: failed to set dxs volume " + "(dxs base 0x%02x).\n", __func__, ch->rbase); + } +} + +static __inline int +via_poll_channel(struct via_chinfo *ch) +{ + struct via_info *via; + uint32_t sz, delta; + uint32_t v, index, count; + int ptr; + + if (ch == NULL || ch->channel == NULL || ch->active == 0) + return (0); + + via = ch->parent; + sz = ch->blksz * ch->blkcnt; + v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); + index = v >> 24; + count = v & 0x00ffffff; + ptr = ((index + 1) * ch->blksz) - count; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + ch->ptr = ptr; + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +static void +via_poll_callback(void *arg) +{ + struct via_info *via = arg; + uint32_t ptrigger = 0, rtrigger = 0; + int i; + + if (via == NULL) + return; + + snd_mtxlock(via->lock); + if (via->polling == 0 || via_chan_active(via) == 0) { + snd_mtxunlock(via->lock); + return; + } + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) + ptrigger |= (via_poll_channel(&via->pch[i]) != 0) ? + (1 << i) : 0; + + for (i = 0; i < NWRCHANS; i++) + rtrigger |= (via_poll_channel(&via->rch[i]) != 0) ? + (1 << i) : 0; + + /* XXX */ + callout_reset(&via->poll_timer, 1/*via->poll_ticks*/, + via_poll_callback, via); + + snd_mtxunlock(via->lock); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + if (ptrigger & (1 << i)) + chn_intr(via->pch[i].channel); + } + for (i = 0; i < NWRCHANS; i++) { + if (rtrigger & (1 << i)) + chn_intr(via->rch[i].channel); } } static int +via_poll_ticks(struct via_info *via) +{ + struct via_chinfo *ch; + int i; + int ret = hz; + int pollticks; + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + ch = &via->pch[i]; + if (ch->channel == NULL || ch->active == 0) + continue; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (pollticks < ret) + ret = pollticks; + } + + for (i = 0; i < NWRCHANS; i++) { + ch = &via->rch[i]; + if (ch->channel == NULL || ch->active == 0) + continue; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (pollticks < ret) + ret = pollticks; + } + + return (ret); +} + +static int via8233chan_trigger(kobj_t obj, void* data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + int pollticks; + + if (!PCMTRIG_COMMON(go)) + return (0); snd_mtxlock(via->lock); switch(go) { @@ -655,53 +898,108 @@ via_buildsgdt(ch); via8233chan_mute(via, ch, 0); via_wr(via, ch->rbase + VIA_RP_TABLE_PTR, ch->sgd_addr, 4); + if (via->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->buffer) * + sndbuf_getspd(ch->buffer)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) + pollticks = 1; + if (via_chan_active(via) == 0 || + pollticks < via->poll_ticks) { + if (bootverbose) { + if (via_chan_active(via) == 0) + printf("%s: pollticks=%d\n", + __func__, pollticks); + else + printf("%s: " + "pollticks %d -> %d\n", + __func__, via->poll_ticks, + pollticks); + } + via->poll_ticks = pollticks; + callout_reset(&via->poll_timer, 1, + via_poll_callback, via); + } + } via_wr(via, ch->rbase + VIA_RP_CONTROL, - SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | - SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); + SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | + ((via->polling == 0) ? + (SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG) : 0), 1); + ch->active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via8233chan_mute(via, ch, 1); via8233chan_reset(via, ch); + ch->active = 0; + if (via->polling != 0) { + if (via_chan_active(via) == 0) { + callout_stop(&via->poll_timer); + via->poll_ticks = 1; + } else { + pollticks = via_poll_ticks(via); + if (pollticks > via->poll_ticks) { + if (bootverbose) + printf("%s: pollticks " + "%d -> %d\n", + __func__, via->poll_ticks, + pollticks); + via->poll_ticks = pollticks; + callout_reset(&via->poll_timer, + 1, via_poll_callback, + via); + } + } + } + break; + default: break; } snd_mtxunlock(via->lock); - return 0; + return (0); } static kobj_method_t via8233wr_methods[] = { - KOBJMETHOD(channel_init, via8233wr_init), - KOBJMETHOD(channel_setformat, via8233wr_setformat), - KOBJMETHOD(channel_setspeed, via8233wr_setspeed), - KOBJMETHOD(channel_getcaps, via8233wr_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233wr_init), + KOBJMETHOD(channel_setformat, via8233wr_setformat), + KOBJMETHOD(channel_setspeed, via8233wr_setspeed), + KOBJMETHOD(channel_getcaps, via8233wr_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_setfragments, via8233chan_setfragments), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233wr); static kobj_method_t via8233dxs_methods[] = { - KOBJMETHOD(channel_init, via8233dxs_init), - KOBJMETHOD(channel_setformat, via8233dxs_setformat), - KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), - KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233dxs_init), + KOBJMETHOD(channel_setformat, via8233dxs_setformat), + KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), + KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_setfragments, via8233chan_setfragments), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233dxs); static kobj_method_t via8233msgd_methods[] = { - KOBJMETHOD(channel_init, via8233msgd_init), - KOBJMETHOD(channel_setformat, via8233msgd_setformat), - KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), - KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233msgd_init), + KOBJMETHOD(channel_setformat, via8233msgd_setformat), + KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), + KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_setfragments, via8233chan_setfragments), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233msgd); @@ -712,55 +1010,56 @@ via_intr(void *p) { struct via_info *via = p; + uint32_t ptrigger = 0, rtrigger = 0; int i, reg, stat; - /* Poll playback channels */ snd_mtxlock(via->lock); + if (via->polling != 0) { + snd_mtxunlock(via->lock); + return; + } + /* Poll playback channels */ for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { - if (via->pch[i].channel == NULL) + if (via->pch[i].channel == NULL || via->pch[i].active == 0) continue; reg = via->pch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || - !(stat & SGD_STATUS_ACTIVE))) { - via_wr(via, - via->pch[i].rbase + VIA_RP_CONTROL, - SGD_CONTROL_START | - SGD_CONTROL_AUTOSTART | - SGD_CONTROL_I_EOL | - SGD_CONTROL_I_FLAG, 1); - } + !(stat & SGD_STATUS_ACTIVE))) + via_wr(via, via->pch[i].rbase + VIA_RP_CONTROL, + SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | + SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); - snd_mtxunlock(via->lock); - chn_intr(via->pch[i].channel); - snd_mtxlock(via->lock); + ptrigger |= 1 << i; } } - /* Poll record channels */ for (i = 0; i < NWRCHANS; i++) { - if (via->rch[i].channel == NULL) + if (via->rch[i].channel == NULL || via->rch[i].active == 0) continue; reg = via->rch[i].rbase + VIA_RP_STATUS; stat = via_rd(via, reg, 1); if (stat & SGD_STATUS_INTR) { if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || - !(stat & SGD_STATUS_ACTIVE))) { - via_wr(via, - via->rch[i].rbase + VIA_RP_CONTROL, - SGD_CONTROL_START | - SGD_CONTROL_AUTOSTART | - SGD_CONTROL_I_EOL | - SGD_CONTROL_I_FLAG, 1); - } + !(stat & SGD_STATUS_ACTIVE))) + via_wr(via, via->rch[i].rbase + VIA_RP_CONTROL, + SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | + SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); via_wr(via, reg, stat, 1); - snd_mtxunlock(via->lock); - chn_intr(via->rch[i].channel); - snd_mtxlock(via->lock); + rtrigger |= 1 << i; } } snd_mtxunlock(via->lock); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + if (ptrigger & (1 << i)) + chn_intr(via->pch[i].channel); + } + for (i = 0; i < NWRCHANS; i++) { + if (rtrigger & (1 << i)) + chn_intr(via->rch[i].channel); + } } /* @@ -772,33 +1071,33 @@ switch(pci_get_devid(dev)) { case VIA8233_PCI_ID: switch(pci_get_revid(dev)) { - case VIA8233_REV_ID_8233PRE: + case VIA8233_REV_ID_8233PRE: device_set_desc(dev, "VIA VT8233 (pre)"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233C: device_set_desc(dev, "VIA VT8233C"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233: device_set_desc(dev, "VIA VT8233"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233A: device_set_desc(dev, "VIA VT8233A"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8235: device_set_desc(dev, "VIA VT8235"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8237: device_set_desc(dev, "VIA VT8237"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8251: device_set_desc(dev, "VIA VT8251"); - return BUS_PROBE_DEFAULT; + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "VIA VT8233X"); /* Unknown */ - return BUS_PROBE_DEFAULT; - } + return (BUS_PROBE_DEFAULT); + } } - return ENXIO; + return (ENXIO); } static void @@ -811,7 +1110,7 @@ static int via_chip_init(device_t dev) { - u_int32_t data, cnt; + uint32_t data, cnt; /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); @@ -819,32 +1118,32 @@ if ((data & VIA_PCI_ACLINK_C00_READY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ /* Assert low */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); /* Wait T_rst_low */ - DELAY(100); + DELAY(100); /* Assert high */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); /* Wait T_rst2clk */ DELAY(5); /* Assert low */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); } else { /* Warm reset */ /* Force no sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); DELAY(100); /* Sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); /* Wait T_sync_high */ DELAY(5); /* Force no sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); /* Wait T_sync2clk */ DELAY(5); } @@ -855,13 +1154,12 @@ /* Wait for codec to become ready (largest reported delay 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); - if (data & VIA_PCI_ACLINK_C00_READY) { - return 0; - } + if (data & VIA_PCI_ACLINK_C00_READY) + return (0); DELAY(5000); } device_printf(dev, "primary codec not ready (cnt = 0x%02x)\n", cnt); - return ENXIO; + return (ENXIO); } static int @@ -870,13 +1168,22 @@ struct via_info *via = 0; char status[SND_STATUSLEN]; int i, via_dxs_disabled, via_dxs_src, via_dxs_chnum, via_sgd_chnum; + int nsegs; uint32_t revid; - if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - via->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + via = malloc(sizeof *via, M_DEVBUF, M_WAITOK | M_ZERO); + via->lock = snd_mtxcreate(device_get_nameunit(dev), + "snd_via8233 softc"); + via->dev = dev; + + callout_init(&via->poll_timer, CALLOUT_MPSAFE); + via->poll_ticks = 1; + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "polling", &i) == 0 && i != 0) + via->polling = 1; + else + via->polling = 0; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); @@ -891,76 +1198,34 @@ via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); - via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); - via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, - RF_ACTIVE | RF_SHAREABLE); - if (!via->irq || - snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!via->irq || + snd_setup_intr(dev, via->irq, INTR_MPSAFE, + via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } - /* DMA tag for buffers */ - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, - /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, - /*highaddr*/BUS_SPACE_MAXADDR, - /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/NULL, - /*lockarg*/NULL, &via->parent_dmat) != 0) { - device_printf(dev, "unable to create dma tag\n"); - goto bad; - } - - /* - * DMA tag for SGD table. The 686 uses scatter/gather DMA and - * requires a list in memory of work to do. We need only 16 bytes - * for this list, and it is wasteful to allocate 16K. - */ - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, - /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, - /*highaddr*/BUS_SPACE_MAXADDR, - /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/NSEGS * sizeof(struct via_dma_op), - /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/NULL, - /*lockarg*/NULL, &via->sgd_dmat) != 0) { - device_printf(dev, "unable to create dma tag\n"); - goto bad; - } - - if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, - BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) - goto bad; - if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, - NSEGS * sizeof(struct via_dma_op), dma_cb, via, 0)) - goto bad; - - if (via_chip_init(dev)) - goto bad; - - via->codec = AC97_CREATE(dev, via, via_ac97); - if (!via->codec) - goto bad; - - mixer_init(dev, ac97_getmixerclass(), via->codec); - - via->codec_caps = ac97_getextcaps(via->codec); - - /* Try to set VRA without generating an error, VRM not reqrd yet */ - if (via->codec_caps & - (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { - u_int16_t ext = ac97_getextmode(via->codec); - ext |= (via->codec_caps & - (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); - ext &= ~AC97_EXTCAP_DRA; - ac97_setextmode(via->codec, ext); - } + via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= VIA_BLK_ALIGN; + if (i < VIA_BLK_MIN) + i = VIA_BLK_MIN; + via->blkcnt = via->bufsz / i; + i = 0; + while (via->blkcnt >> i) + i++; + via->blkcnt = 1 << (i - 1); + if (via->blkcnt < VIA_SEGS_MIN) + via->blkcnt = VIA_SEGS_MIN; + else if (via->blkcnt > VIA_SEGS_MAX) + via->blkcnt = VIA_SEGS_MAX; - snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", - rman_get_start(via->reg), rman_get_start(via->irq),PCM_KLDSTRING(snd_via8233)); + } else + via->blkcnt = VIA_SEGS_DEFAULT; revid = pci_get_revid(dev); @@ -985,8 +1250,8 @@ */ via_dxs_disabled = 1; } else if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "via_dxs_disabled", - &via_dxs_disabled) == 0) + device_get_unit(dev), "via_dxs_disabled", + &via_dxs_disabled) == 0) via_dxs_disabled = (via_dxs_disabled > 0) ? 1 : 0; else via_dxs_disabled = 0; @@ -996,12 +1261,12 @@ via_sgd_chnum = 1; } else { if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "via_dxs_channels", - &via_dxs_chnum) != 0) + device_get_unit(dev), "via_dxs_channels", + &via_dxs_chnum) != 0) via_dxs_chnum = NDXSCHANS; if (resource_int_value(device_get_name(dev), - device_get_unit(dev), "via_sgd_channels", - &via_sgd_chnum) != 0) + device_get_unit(dev), "via_sgd_channels", + &via_sgd_chnum) != 0) via_sgd_chnum = NMSGDCHANS; } if (via_dxs_chnum > NDXSCHANS) @@ -1018,11 +1283,76 @@ via_sgd_chnum = 0; } if (via_dxs_chnum > 0 && resource_int_value(device_get_name(dev), - device_get_unit(dev), "via_dxs_src", - &via_dxs_src) == 0) + device_get_unit(dev), "via_dxs_src", &via_dxs_src) == 0) via->dxs_src = (via_dxs_src > 0) ? 1 : 0; else via->dxs_src = 0; + + nsegs = (via_dxs_chnum + via_sgd_chnum + NWRCHANS) * VIA_SEGS_MAX; + + /* DMA tag for buffers */ + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + /* + * DMA tag for SGD table. The 686 uses scatter/gather DMA and + * requires a list in memory of work to do. We need only 16 bytes + * for this list, and it is wasteful to allocate 16K. + */ + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, + /*highaddr*/BUS_SPACE_MAXADDR, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/nsegs * sizeof(struct via_dma_op), + /*nsegments*/1, /*maxsegz*/0x3ffff, + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->sgd_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, + BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) + goto bad; + if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, + nsegs * sizeof(struct via_dma_op), dma_cb, via, 0)) + goto bad; + + if (via_chip_init(dev)) + goto bad; + + via->codec = AC97_CREATE(dev, via, via_ac97); + if (!via->codec) + goto bad; + + mixer_init(dev, ac97_getmixerclass(), via->codec); + + via->codec_caps = ac97_getextcaps(via->codec); + + /* Try to set VRA without generating an error, VRM not reqrd yet */ + if (via->codec_caps & + (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { + uint16_t ext = ac97_getextmode(via->codec); + ext |= (via->codec_caps & + (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); + ext &= ~AC97_EXTCAP_DRA; + ac97_setextmode(via->codec, ext); + } + + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", + rman_get_start(via->reg), rman_get_start(via->irq), + PCM_KLDSTRING(snd_via8233)); + /* Register */ if (pcm_register(dev, via, via_dxs_chnum + via_sgd_chnum, NWRCHANS)) goto bad; @@ -1035,37 +1365,56 @@ if (via_dxs_chnum > 0) via_init_sysctls(dev); device_printf(dev, "\n", - (via_dxs_chnum > 0) ? "En" : "Dis", - (via->dxs_src) ? "(SRC)" : "", - via_dxs_chnum, via_sgd_chnum, NWRCHANS); + (via_dxs_chnum > 0) ? "En" : "Dis", (via->dxs_src) ? "(SRC)" : "", + via_dxs_chnum, via_sgd_chnum, NWRCHANS); pcm_setstatus(dev, status); - return 0; + return (0); bad: - if (via->codec) ac97_destroy(via->codec); - if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); - if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); - if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); - if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); - if (via->sgd_dmamap) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); - if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); - if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); - if (via->lock) snd_mtxfree(via->lock); - if (via) free(via, M_DEVBUF); - return ENXIO; + if (via->codec) + ac97_destroy(via->codec); + if (via->reg) + bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); + if (via->ih) + bus_teardown_intr(dev, via->irq, via->ih); + if (via->irq) + bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); + if (via->parent_dmat) + bus_dma_tag_destroy(via->parent_dmat); + if (via->sgd_dmamap) + bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); + if (via->sgd_table) + bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); + if (via->sgd_dmat) + bus_dma_tag_destroy(via->sgd_dmat); + if (via->lock) + snd_mtxfree(via->lock); + if (via) + free(via, M_DEVBUF); + return (ENXIO); } static int via_detach(device_t dev) { int r; - struct via_info *via = 0; + struct via_info *via; r = pcm_unregister(dev); - if (r) return r; + if (r) + return (r); via = pcm_getdevinfo(dev); + + if (via != NULL && (via->play_num != 0 || via->rec_num != 0)) { + snd_mtxlock(via->lock); + via->polling = 0; + callout_stop(&via->poll_timer); + snd_mtxunlock(via->lock); + callout_drain(&via->poll_timer); + } + bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); @@ -1075,7 +1424,7 @@ bus_dma_tag_destroy(via->sgd_dmat); snd_mtxfree(via->lock); free(via, M_DEVBUF); - return 0; + return (0); } --- sys/dev/sound/pci/via82c686.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/via82c686.c Thu Jul 12 12:04:19 2007 @@ -33,7 +33,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via82c686.c,v 1.34.2.2 2007/04/26 08:21:44 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via82c686.c,v 1.43 2007/06/17 06:10:42 ariff Exp $"); #define VIA_PCI_ID 0x30581106 #define NSEGS 4 /* Number of segments in SGD table */ @@ -270,7 +270,7 @@ ch->dir = dir; snd_mtxunlock(via->lock); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return NULL; return ch; @@ -342,7 +342,7 @@ struct via_dma_op *ado; bus_addr_t sgd_addr = ch->sgd_addr; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ado = ch->sgd_table; @@ -477,11 +477,9 @@ char status[SND_STATUSLEN]; u_int32_t data, cnt; - if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - via->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + via = malloc(sizeof(*via), M_DEVBUF, M_WAITOK | M_ZERO); + via->lock = snd_mtxcreate(device_get_nameunit(dev), + "snd_via82c686 softc"); /* Get resources */ data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -555,7 +553,8 @@ via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); /* DMA tag for buffers */ - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, @@ -571,7 +570,8 @@ * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/vibes.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pci/vibes.c Thu Jul 12 12:04:19 2007 @@ -36,7 +36,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/vibes.c,v 1.19.2.1 2006/01/24 18:54:22 joel Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/vibes.c,v 1.26 2007/06/17 06:10:42 ariff Exp $"); /* ------------------------------------------------------------------------- */ /* Constants */ @@ -192,7 +192,7 @@ ch->channel = c; ch->dir = dir; - if (sndbuf_alloc(b, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(b, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("svchan_init failed\n")); return NULL; } @@ -336,6 +336,7 @@ sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); @@ -412,6 +413,7 @@ sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_PLAY_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); @@ -717,11 +719,7 @@ char status[SND_STATUSLEN]; u_long midi_start, games_start, count, sdmaa, sdmac, ml, mu; - sc = malloc(sizeof(struct sc_info), M_DEVBUF, M_NOWAIT | M_ZERO); - if (sc == NULL) { - device_printf(dev, "cannot allocate softc"); - return ENXIO; - } + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -762,13 +760,14 @@ sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || - bus_setup_intr(dev, sc->irq, INTR_TYPE_AV, sv_intr, sc, &sc->ih)) { + snd_setup_intr(dev, sc->irq, 0, sv_intr, sc, &sc->ih)) { device_printf(dev, "sv_attach: Unable to map interrupt\n"); goto fail; } sc->bufsz = pcm_getbuffersize(dev, 4096, SV_DEFAULT_BUFSZ, 65536); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2, + /*boundary*/0, /*lowaddr*/BUS_SPACE_MAXADDR_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pcm/ac97.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/ac97.c Thu Jul 12 12:04:19 2007 @@ -32,12 +32,12 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97.c,v 1.53.2.5 2007/05/13 20:53:39 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97.c,v 1.73 2007/06/17 06:10:43 ariff Exp $"); MALLOC_DEFINE(M_AC97, "ac97", "ac97 codec"); struct ac97mixtable_entry { - int reg:8; /* register index */ + int reg; /* register index */ /* reg < 0 if inverted polarity */ unsigned bits:4; /* width of control field */ unsigned ofs:4; /* offset (only if stereo=0) */ @@ -48,7 +48,8 @@ unsigned enable:1; /* entry is enabled */ }; -#define AC97_NAMELEN 16 +#define AC97_MIXER_SIZE SOUND_MIXER_NRDEVICES + struct ac97_info { kobj_t methods; device_t dev; @@ -57,8 +58,8 @@ u_int32_t subvendor; unsigned count, caps, se, extcaps, extid, extstat, noext:1; u_int32_t flags; - struct ac97mixtable_entry mix[32]; - char name[AC97_NAMELEN]; + struct ac97mixtable_entry mix[AC97_MIXER_SIZE]; + char name[16]; struct mtx *lock; }; @@ -75,7 +76,7 @@ ac97_patch patch; }; -static const struct ac97mixtable_entry ac97mixtable_default[32] = { +static const struct ac97mixtable_entry ac97mixtable_default[AC97_MIXER_SIZE] = { /* [offset] reg bits of st mu re mk en */ [SOUND_MIXER_VOLUME] = { AC97_MIX_MASTER, 5, 0, 1, 1, 6, 0, 1 }, [SOUND_MIXER_OGAIN] = { AC97_MIX_AUXOUT, 5, 0, 1, 1, 0, 0, 0 }, @@ -571,38 +572,6 @@ } } -static void -ac97_fix_volume(struct ac97_info *codec) -{ - struct snddev_info *d = device_get_softc(codec->dev); - -#if 0 - /* XXX For the sake of debugging purposes */ - ac97_wrcd(codec, AC97_MIX_PCM, 0); - bzero(&codec->mix[SOUND_MIXER_PCM], - sizeof(codec->mix[SOUND_MIXER_PCM])); - if (d) - d->flags |= SD_F_SOFTPCMVOL; - return; -#endif - switch (codec->id) { - case 0x434d4941: /* CMI9738 */ - case 0x434d4961: /* CMI9739 */ - case 0x434d4978: /* CMI9761 */ - case 0x434d4982: /* CMI9761 */ - case 0x434d4983: /* CMI9761 */ - ac97_wrcd(codec, AC97_MIX_PCM, 0); - break; - default: - return; - break; - } - bzero(&codec->mix[SOUND_MIXER_PCM], - sizeof(codec->mix[SOUND_MIXER_PCM])); - if (d) - d->flags |= SD_F_SOFTPCMVOL; -} - static const char* ac97_hw_desc(u_int32_t id, const char* vname, const char* cname, char* buf) { @@ -646,12 +615,13 @@ i = ac97_rdcd(codec, AC97_REG_RESET); j = ac97_rdcd(codec, AC97_REG_RESET); + k = ac97_rdcd(codec, AC97_REG_RESET); /* * Let see if this codec can return consistent value. * If not, turn on aggressive read workaround * (STAC9704 comes in mind). */ - if (i != j) { + if (i != j || j != k) { codec->flags |= AC97_F_RDCD_BUG; i = ac97_rdcd(codec, AC97_REG_RESET); } @@ -706,16 +676,15 @@ } } - for (i = 0; i < 32; i++) { + for (i = 0; i < AC97_MIXER_SIZE; i++) { codec->mix[i] = ac97mixtable_default[i]; } ac97_fix_auxout(codec); ac97_fix_tone(codec); - ac97_fix_volume(codec); if (codec_patch) codec_patch(codec); - for (i = 0; i < 32; i++) { + for (i = 0; i < AC97_MIXER_SIZE; i++) { k = codec->noext? codec->mix[i].enable : 1; reg = codec->mix[i].reg; if (reg < 0) @@ -853,24 +822,21 @@ ac97_create(device_t dev, void *devinfo, kobj_class_t cls) { struct ac97_info *codec; + int eapdinv; - codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT); - if (codec == NULL) - return NULL; - - snprintf(codec->name, AC97_NAMELEN, "%s:ac97", device_get_nameunit(dev)); + codec = malloc(sizeof(*codec), M_AC97, M_WAITOK | M_ZERO); + snprintf(codec->name, sizeof(codec->name), "%s:ac97", + device_get_nameunit(dev)); codec->lock = snd_mtxcreate(codec->name, "ac97 codec"); - codec->methods = kobj_create(cls, M_AC97, M_WAITOK); - if (codec->methods == NULL) { - snd_mtxlock(codec->lock); - snd_mtxfree(codec->lock); - free(codec, M_AC97); - return NULL; - } - + codec->methods = kobj_create(cls, M_AC97, M_WAITOK | M_ZERO); codec->dev = dev; codec->devinfo = devinfo; codec->flags = 0; + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "eapdinv", &eapdinv) == 0) { + if (eapdinv != 0) + codec->flags |= AC97_F_EAPD_INV; + } return codec; } @@ -898,6 +864,60 @@ /* -------------------------------------------------------------------- */ +#ifdef SND_DYNSYSCTL +static int +sysctl_hw_snd_ac97_eapd(SYSCTL_HANDLER_ARGS) +{ + struct ac97_info *codec; + int ea, inv, err = 0; + u_int16_t val; + + codec = oidp->oid_arg1; + if (codec == NULL || codec->id == 0 || codec->lock == NULL) + return EINVAL; + snd_mtxlock(codec->lock); + val = ac97_rdcd(codec, AC97_REG_POWER); + inv = (codec->flags & AC97_F_EAPD_INV) ? 0 : 1; + ea = (val >> 15) ^ inv; + snd_mtxunlock(codec->lock); + err = sysctl_handle_int(oidp, &ea, 0, req); + if (err == 0 && req->newptr != NULL) { + if (ea != 0 && ea != 1) + return EINVAL; + if (ea != ((val >> 15) ^ inv)) { + snd_mtxlock(codec->lock); + ac97_wrcd(codec, AC97_REG_POWER, val ^ 0x8000); + snd_mtxunlock(codec->lock); + } + } + return err; +} +#endif + +static void +ac97_init_sysctl(struct ac97_info *codec) +{ +#ifdef SND_DYNSYSCTL + u_int16_t orig, val; + + if (codec == NULL || codec->dev == NULL) + return; + snd_mtxlock(codec->lock); + orig = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig ^ 0x8000); + val = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig); + snd_mtxunlock(codec->lock); + if ((val & 0x8000) == (orig & 0x8000)) + return; + SYSCTL_ADD_PROC(device_get_sysctl_ctx(codec->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(codec->dev)), + OID_AUTO, "eapd", CTLTYPE_INT | CTLFLAG_RW, + codec, sizeof(codec), sysctl_hw_snd_ac97_eapd, + "I", "AC97 External Amplifier"); +#endif +} + static int ac97mix_init(struct snd_mixer *m) { @@ -910,15 +930,67 @@ if (ac97_initmixer(codec)) return -1; + switch (codec->id) { + case 0x41445374: /* AD1981B */ + if (codec->subvendor == 0x02d91014) { + /* + * IBM Thinkcentre: + * Tie "ogain" and "phone" to "vol" since its + * master volume is basically useless and can't + * control anything. + */ + mask = 0; + if (codec->mix[SOUND_MIXER_OGAIN].enable) + mask |= SOUND_MASK_OGAIN; + if (codec->mix[SOUND_MIXER_PHONEOUT].enable) + mask |= SOUND_MASK_PHONEOUT; + if (codec->mix[SOUND_MIXER_VOLUME].enable) + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + else { + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + mix_setrealdev(m, SOUND_MIXER_VOLUME, + SOUND_MIXER_NONE); + } + } + break; + case 0x434d4941: /* CMI9738 */ + case 0x434d4961: /* CMI9739 */ + case 0x434d4978: /* CMI9761 */ + case 0x434d4982: /* CMI9761 */ + case 0x434d4983: /* CMI9761 */ + ac97_wrcd(codec, AC97_MIX_PCM, 0); + bzero(&codec->mix[SOUND_MIXER_PCM], + sizeof(codec->mix[SOUND_MIXER_PCM])); + pcm_setflags(codec->dev, pcm_getflags(codec->dev) | + SD_F_SOFTPCMVOL); + /* XXX How about master volume ? */ + break; + default: + break; + } + +#if 0 + /* XXX For the sake of debugging purposes */ + mix_setparentchild(m, SOUND_MIXER_VOLUME, + SOUND_MASK_PCM | SOUND_MASK_CD); + mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); + ac97_wrcd(codec, AC97_MIX_MASTER, 0); +#endif + mask = 0; - for (i = 0; i < 32; i++) + for (i = 0; i < AC97_MIXER_SIZE; i++) mask |= codec->mix[i].enable? 1 << i : 0; mix_setdevs(m, mask); mask = 0; - for (i = 0; i < 32; i++) + for (i = 0; i < AC97_MIXER_SIZE; i++) mask |= codec->mix[i].recidx? 1 << i : 0; mix_setrecdevs(m, mask); + + ac97_init_sysctl(codec); + return 0; } @@ -952,7 +1024,7 @@ { struct ac97_info *codec = mix_getdevinfo(m); - if (codec == NULL) + if (codec == NULL || dev >= AC97_MIXER_SIZE) return -1; return ac97_setmixer(codec, dev, left, right); } @@ -965,7 +1037,7 @@ if (codec == NULL) return -1; - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + for (i = 0; i < AC97_MIXER_SIZE; i++) if ((src & (1 << i)) != 0) break; return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1; --- sys/dev/sound/pcm/ac97.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/ac97.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/ac97.h,v 1.16.2.3 2007/05/13 20:53:39 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/ac97.h,v 1.20 2007/04/19 13:54:22 ariff Exp $ */ #define AC97_MUTE 0x8080 --- sys/dev/sound/pcm/ac97_patch.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/ac97_patch.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,6 @@ /*- - * Copyright 2002 FreeBSD, Inc. All rights reserved. + * Copyright (c) 2002 Orion Hodson + * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,7 +28,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97_patch.c,v 1.3.2.4 2007/07/04 04:04:42 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97_patch.c,v 1.10 2007/07/01 17:28:58 ariff Exp $"); void ad1886_patch(struct ac97_info* codec) { --- sys/dev/sound/pcm/ac97_patch.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/ac97_patch.h Thu Jul 12 12:04:19 2007 @@ -1,5 +1,6 @@ /*- - * Copyright 2003 FreeBSD, Inc. All rights reserved. + * Copyright (c) 2003 Orion Hodson + * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -22,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/ac97_patch.h,v 1.3.2.2 2007/04/26 08:30:52 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/ac97_patch.h,v 1.7 2007/04/19 13:54:22 ariff Exp $ */ typedef void (*ac97_patch)(struct ac97_info*); --- sys/dev/sound/pcm/buffer.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/buffer.c Thu Jul 12 12:04:19 2007 @@ -28,7 +28,7 @@ #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/buffer.c,v 1.25.2.3 2007/04/26 08:21:43 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/buffer.c,v 1.37 2007/06/16 03:37:27 ariff Exp $"); struct snd_dbuf * sndbuf_create(device_t dev, char *drv, char *desc, struct pcm_channel *channel) @@ -78,16 +78,18 @@ */ int -sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, unsigned int size) +sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, int dmaflags, + unsigned int size) { int ret; b->dmatag = dmatag; + b->dmaflags = dmaflags | BUS_DMA_NOWAIT; b->maxsize = size; b->bufsize = b->maxsize; b->buf_addr = 0; b->flags |= SNDBUF_F_MANAGED; - if (bus_dmamem_alloc(b->dmatag, (void **)&b->buf, BUS_DMA_NOWAIT, + if (bus_dmamem_alloc(b->dmatag, (void **)&b->buf, b->dmaflags, &b->dmamap)) { sndbuf_free(b); return (ENOMEM); @@ -123,6 +125,9 @@ if (b->tmpbuf) free(b->tmpbuf, M_DEVBUF); + if (b->shadbuf) + free(b->shadbuf, M_DEVBUF); + if (b->buf) { if (b->flags & SNDBUF_F_MANAGED) { if (b->dmamap) @@ -134,15 +139,20 @@ } b->tmpbuf = NULL; + b->shadbuf = NULL; b->buf = NULL; + b->sl = 0; b->dmatag = NULL; b->dmamap = NULL; } +#define SNDBUF_CACHE_SHIFT 5 + int sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) { - u_int8_t *tmpbuf, *f2; + unsigned int bufsize, allocsize; + u_int8_t *tmpbuf; chn_lock(b->channel); if (b->maxsize == 0) @@ -151,27 +161,38 @@ blkcnt = b->blkcnt; if (blksz == 0) blksz = b->blksz; - if (blkcnt < 2 || blksz < 16 || (blkcnt * blksz > b->maxsize)) { + if (blkcnt < 2 || blksz < 16 || (blkcnt * blksz) > b->maxsize) { chn_unlock(b->channel); return EINVAL; } if (blkcnt == b->blkcnt && blksz == b->blksz) goto out; - chn_unlock(b->channel); - tmpbuf = malloc(blkcnt * blksz, M_DEVBUF, M_NOWAIT); - if (tmpbuf == NULL) - return ENOMEM; - chn_lock(b->channel); + bufsize = blkcnt * blksz; + + if (bufsize > b->allocsize || + bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + allocsize = round_page(bufsize); + chn_unlock(b->channel); + tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); + chn_lock(b->channel); + if (snd_verbose > 3) + printf("%s(): b=%p %p -> %p [%d -> %d : %d]\n", + __func__, b, b->tmpbuf, tmpbuf, + b->allocsize, allocsize, bufsize); + if (b->tmpbuf != NULL) + free(b->tmpbuf, M_DEVBUF); + b->tmpbuf = tmpbuf; + b->allocsize = allocsize; + } else if (snd_verbose > 3) + printf("%s(): b=%p %d [%d] NOCHANGE\n", + __func__, b, b->allocsize, b->bufsize); + b->blkcnt = blkcnt; b->blksz = blksz; - b->bufsize = blkcnt * blksz; - f2 = b->tmpbuf; - b->tmpbuf = tmpbuf; + b->bufsize = bufsize; + sndbuf_reset(b); - chn_unlock(b->channel); - free(f2, M_DEVBUF); - return 0; out: chn_unlock(b->channel); return 0; @@ -180,53 +201,59 @@ int sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) { - u_int8_t *buf, *tmpbuf, *f1, *f2; - unsigned int bufsize; - int ret; + unsigned int bufsize, allocsize; + u_int8_t *buf, *tmpbuf, *shadbuf; if (blkcnt < 2 || blksz < 16) return EINVAL; bufsize = blksz * blkcnt; - chn_unlock(b->channel); - buf = malloc(bufsize, M_DEVBUF, M_WAITOK); - if (buf == NULL) { - ret = ENOMEM; - goto out; - } - - tmpbuf = malloc(bufsize, M_DEVBUF, M_WAITOK); - if (tmpbuf == NULL) { - free(buf, M_DEVBUF); - ret = ENOMEM; - goto out; - } - chn_lock(b->channel); + if (bufsize > b->allocsize || + bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + allocsize = round_page(bufsize); + chn_unlock(b->channel); + buf = malloc(allocsize, M_DEVBUF, M_WAITOK); + tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); + shadbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); + chn_lock(b->channel); + if (b->buf != NULL) + free(b->buf, M_DEVBUF); + b->buf = buf; + if (b->tmpbuf != NULL) + free(b->tmpbuf, M_DEVBUF); + b->tmpbuf = tmpbuf; + if (b->shadbuf != NULL) + free(b->shadbuf, M_DEVBUF); + b->shadbuf = shadbuf; + if (snd_verbose > 3) + printf("%s(): b=%p %d -> %d [%d]\n", + __func__, b, b->allocsize, allocsize, bufsize); + b->allocsize = allocsize; + } else if (snd_verbose > 3) + printf("%s(): b=%p %d [%d] NOCHANGE\n", + __func__, b, b->allocsize, b->bufsize); b->blkcnt = blkcnt; b->blksz = blksz; b->bufsize = bufsize; b->maxsize = bufsize; - f1 = b->buf; - f2 = b->tmpbuf; - b->buf = buf; - b->tmpbuf = tmpbuf; + b->sl = bufsize; sndbuf_reset(b); - chn_unlock(b->channel); - if (f1) - free(f1, M_DEVBUF); - if (f2) - free(f2, M_DEVBUF); - - ret = 0; -out: - chn_lock(b->channel); - return ret; + return 0; } +/** + * @brief Zero out space in buffer free area + * + * This function clears a chunk of @c length bytes in the buffer free area + * (i.e., where the next write will be placed). + * + * @param b buffer context + * @param length number of bytes to blank + */ void sndbuf_clear(struct snd_dbuf *b, unsigned int length) { @@ -238,10 +265,7 @@ if (length > b->bufsize) length = b->bufsize; - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; + data = sndbuf_zerodata(b->fmt); i = sndbuf_getfreeptr(b); p = sndbuf_getbuf(b); @@ -254,25 +278,37 @@ } } +/** + * @brief Zap buffer contents, resetting "ready area" fields + * + * @param b buffer context + */ void sndbuf_fillsilence(struct snd_dbuf *b) { - int i; - u_char data, *p; - - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; - - i = 0; - p = sndbuf_getbuf(b); - while (i < b->bufsize) - p[i++] = data; + if (b->bufsize > 0) + memset(sndbuf_getbuf(b), sndbuf_zerodata(b->fmt), b->bufsize); b->rp = 0; b->rl = b->bufsize; } +/** + * @brief Reset buffer w/o flushing statistics + * + * This function just zeroes out buffer contents and sets the "ready length" + * to zero. This was originally to facilitate minimal playback interruption + * (i.e., dropped samples) in SNDCTL_DSP_SILENCE/SKIP ioctls. + * + * @param b buffer context + */ +void +sndbuf_softreset(struct snd_dbuf *b) +{ + b->rl = 0; + if (b->buf && b->bufsize > 0) + sndbuf_clear(b, b->bufsize); +} + void sndbuf_reset(struct snd_dbuf *b) { @@ -285,6 +321,7 @@ b->xrun = 0; if (b->buf && b->bufsize > 0) sndbuf_clear(b, b->bufsize); + sndbuf_clearshadow(b); } u_int32_t @@ -385,6 +422,12 @@ } unsigned int +sndbuf_getallocsize(struct snd_dbuf *b) +{ + return b->allocsize; +} + +unsigned int sndbuf_runsz(struct snd_dbuf *b) { return b->dl; @@ -412,11 +455,11 @@ } void -sndbuf_setxrun(struct snd_dbuf *b, unsigned int cnt) +sndbuf_setxrun(struct snd_dbuf *b, unsigned int xrun) { SNDBUF_LOCKASSERT(b); - b->xrun = cnt; + b->xrun = xrun; } unsigned int @@ -504,8 +547,67 @@ b->prev_total = b->total; } +unsigned int +snd_xbytes(unsigned int v, unsigned int from, unsigned int to) +{ + unsigned int w, x, y; + + if (from == to) + return v; + + if (from == 0 || to == 0 || v == 0) + return 0; + + x = from; + y = to; + while (y != 0) { + w = x % y; + x = y; + y = w; + } + from /= x; + to /= x; + + return (unsigned int)(((u_int64_t)v * to) / from); +} + +unsigned int +sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to) +{ + if (from == NULL || to == NULL || v == 0) + return 0; + + return snd_xbytes(v, sndbuf_getbps(from) * sndbuf_getspd(from), + sndbuf_getbps(to) * sndbuf_getspd(to)); +} + +u_int8_t +sndbuf_zerodata(u_int32_t fmt) +{ + if (fmt & AFMT_SIGNED) + return (0x00); + else if (fmt & AFMT_MU_LAW) + return (0x7f); + else if (fmt & AFMT_A_LAW) + return (0x55); + return (0x80); +} + /************************************************************/ +/** + * @brief Acquire buffer space to extend ready area + * + * This function extends the ready area length by @c count bytes, and may + * optionally copy samples from another location stored in @c from. The + * counter @c snd_dbuf::total is also incremented by @c count bytes. + * + * @param b audio buffer + * @param from sample source (optional) + * @param count number of bytes to acquire + * + * @retval 0 Unconditional + */ int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count) { @@ -516,7 +618,7 @@ b->total += count; if (from != NULL) { while (count > 0) { - l = MIN(count, sndbuf_getsize(b) - sndbuf_getfreeptr(b)); + l = min(count, sndbuf_getsize(b) - sndbuf_getfreeptr(b)); bcopy(from, sndbuf_getbufofs(b, sndbuf_getfreeptr(b)), l); from += l; b->rl += l; @@ -529,6 +631,20 @@ return 0; } +/** + * @brief Dispose samples from channel buffer, increasing size of ready area + * + * This function discards samples from the supplied buffer by advancing the + * ready area start pointer and decrementing the ready area length. If + * @c to is not NULL, then the discard samples will be copied to the location + * it points to. + * + * @param b PCM channel sound buffer + * @param to destination buffer (optional) + * @param count number of bytes to discard + * + * @returns 0 unconditionally + */ int sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count) { @@ -538,7 +654,7 @@ KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); if (to != NULL) { while (count > 0) { - l = MIN(count, sndbuf_getsize(b) - sndbuf_getreadyptr(b)); + l = min(count, sndbuf_getsize(b) - sndbuf_getreadyptr(b)); bcopy(sndbuf_getbufofs(b, sndbuf_getreadyptr(b)), to, l); to += l; b->rl -= l; @@ -558,15 +674,20 @@ int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count) { + unsigned int cnt; + KASSERT(count > 0, ("can't feed 0 bytes")); if (sndbuf_getfree(to) < count) return EINVAL; - count = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); - if (count) - sndbuf_acquire(to, to->tmpbuf, count); - /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ + do { + cnt = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); + if (cnt) { + sndbuf_acquire(to, to->tmpbuf, cnt); + count -= cnt; + } + } while (count && cnt); return 0; } @@ -605,3 +726,45 @@ b->flags |= flags; } +/** + * @brief Clear the shadow buffer by filling with samples equal to zero. + * + * @param b buffer to clear + */ +void +sndbuf_clearshadow(struct snd_dbuf *b) +{ + KASSERT(b != NULL, ("b is a null pointer")); + KASSERT(b->sl >= 0, ("illegal shadow length")); + + if ((b->shadbuf != NULL) && (b->sl > 0)) + memset(b->shadbuf, sndbuf_zerodata(b->fmt), b->sl); +} + +#ifdef OSSV4_EXPERIMENT +/** + * @brief Return peak value from samples in buffer ready area. + * + * Peak ranges from 0-32767. If channel is monaural, most significant 16 + * bits will be zero. For now, only expects to work with 1-2 channel + * buffers. + * + * @note Currently only operates with linear PCM formats. + * + * @param b buffer to analyze + * @param lpeak pointer to store left peak value + * @param rpeak pointer to store right peak value + */ +void +sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp) +{ + u_int32_t lpeak, rpeak; + + lpeak = 0; + rpeak = 0; + + /** + * @todo fill this in later + */ +} +#endif --- sys/dev/sound/pcm/buffer.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/buffer.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/buffer.h,v 1.10.2.2 2007/05/13 20:50:31 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/buffer.h,v 1.17 2007/06/14 11:15:51 ariff Exp $ */ #define SND_DMA(b) (sndbuf_getflags((b)) & SNDBUF_F_DMA) @@ -39,7 +39,9 @@ struct snd_dbuf { device_t dev; u_int8_t *buf, *tmpbuf; - unsigned int bufsize, maxsize; + u_int8_t *shadbuf; /**< shadow buffer used w/ S_D_SILENCE/SKIP */ + volatile int sl; /**< shadbuf ready length in # of bytes */ + unsigned int bufsize, maxsize, allocsize; volatile int dl; /* transfer size */ volatile int rp; /* pointers to the ready area */ volatile int rl; /* length of ready area */ @@ -53,6 +55,7 @@ bus_dmamap_t dmamap; bus_dma_tag_t dmatag; bus_addr_t buf_addr; + int dmaflags; struct selinfo sel; struct pcm_channel *channel; char name[SNDBUF_NAMELEN]; @@ -63,7 +66,7 @@ void sndbuf_dump(struct snd_dbuf *b, char *s, u_int32_t what); -int sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, unsigned int size); +int sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, int dmaflags, unsigned int size); int sndbuf_setup(struct snd_dbuf *b, void *buf, unsigned int size); void sndbuf_free(struct snd_dbuf *b); int sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz); @@ -71,6 +74,8 @@ void sndbuf_reset(struct snd_dbuf *b); void sndbuf_clear(struct snd_dbuf *b, unsigned int length); void sndbuf_fillsilence(struct snd_dbuf *b); +void sndbuf_softreset(struct snd_dbuf *b); +void sndbuf_clearshadow(struct snd_dbuf *b); u_int32_t sndbuf_getfmt(struct snd_dbuf *b); int sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt); @@ -84,6 +89,7 @@ void *sndbuf_getbufofs(struct snd_dbuf *b, unsigned int ofs); unsigned int sndbuf_getsize(struct snd_dbuf *b); unsigned int sndbuf_getmaxsize(struct snd_dbuf *b); +unsigned int sndbuf_getallocsize(struct snd_dbuf *b); unsigned int sndbuf_getalign(struct snd_dbuf *b); unsigned int sndbuf_getblkcnt(struct snd_dbuf *b); void sndbuf_setblkcnt(struct snd_dbuf *b, unsigned int blkcnt); @@ -94,7 +100,7 @@ struct selinfo *sndbuf_getsel(struct snd_dbuf *b); unsigned int sndbuf_getxrun(struct snd_dbuf *b); -void sndbuf_setxrun(struct snd_dbuf *b, unsigned int cnt); +void sndbuf_setxrun(struct snd_dbuf *b, unsigned int xrun); unsigned int sndbuf_gethwptr(struct snd_dbuf *b); void sndbuf_sethwptr(struct snd_dbuf *b, unsigned int ptr); unsigned int sndbuf_getfree(struct snd_dbuf *b); @@ -104,6 +110,9 @@ unsigned int sndbuf_getblocks(struct snd_dbuf *b); unsigned int sndbuf_getprevblocks(struct snd_dbuf *b); unsigned int sndbuf_gettotal(struct snd_dbuf *b); +unsigned int snd_xbytes(unsigned int v, unsigned int from, unsigned int to); +unsigned int sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to); +u_int8_t sndbuf_zerodata(u_int32_t fmt); void sndbuf_updateprevtotal(struct snd_dbuf *b); int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count); @@ -118,3 +127,7 @@ void sndbuf_dma(struct snd_dbuf *b, int go); int sndbuf_dmaptr(struct snd_dbuf *b); void sndbuf_dmabounce(struct snd_dbuf *b); + +#ifdef OSSV4_EXPERIMENT +void sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp); +#endif --- sys/dev/sound/pcm/channel.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/channel.c Thu Jul 12 12:04:19 2007 @@ -31,70 +31,237 @@ #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/channel.c,v 1.99.2.5 2007/05/13 20:53:39 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/channel.c,v 1.121 2007/06/16 03:37:28 ariff Exp $"); -#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ -#if 0 -#define DMA_ALIGN_THRESHOLD 4 -#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) -#endif +int report_soft_formats = 1; +SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, + &report_soft_formats, 1, "report software-emulated formats"); -#define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) +int chn_latency = CHN_LATENCY_DEFAULT; +TUNABLE_INT("hw.snd.latency", &chn_latency); -/* -#define DEB(x) x -*/ +static int +sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) +{ + int err, val; -static int chn_targetirqrate = 32; -TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate); + val = chn_latency; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return err; + if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) + err = EINVAL; + else + chn_latency = val; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_latency, "I", + "buffering latency (0=low ... 10=high)"); + +int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; +TUNABLE_INT("hw.snd.latency_profile", &chn_latency_profile); static int -sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) { int err, val; - val = chn_targetirqrate; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val < 16 || val > 512) + val = chn_latency_profile; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return err; + if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX) err = EINVAL; else - chn_targetirqrate = val; + chn_latency_profile = val; return err; } -SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", ""); -static int report_soft_formats = 1; -SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, - &report_soft_formats, 1, "report software-emulated formats"); +SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", + "buffering latency profile (0=aggresive 1=safe)"); + +static int chn_timeout = CHN_TIMEOUT; +TUNABLE_INT("hw.snd.timeout", &chn_timeout); +#ifdef SND_DEBUG +static int +sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_timeout; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return err; + if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX) + err = EINVAL; + else + chn_timeout = val; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, timeout, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_timeout, "I", + "interrupt timeout (1 - 10) seconds"); +#endif + +static int chn_vpc_autoreset = 0; +TUNABLE_INT("hw.snd.vpc_autoreset", &chn_vpc_autoreset); +SYSCTL_INT(_hw_snd, OID_AUTO, vpc_autoreset, CTLFLAG_RW, + &chn_vpc_autoreset, 0, "automatically reset channels volume to 0db"); + +static int chn_vol_0db_pcm = SND_VOL_0DB_PCM; + +static void +chn_vpc_proc(int reset, int db) +{ + struct snddev_info *d; + struct pcm_channel *c; + int i; + + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + pcm_lock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_VOL_0DB, db); + if (reset != 0) + chn_vpc_reset(c, SND_VOL_C_PCM, 1); + CHN_UNLOCK(c); + } + PCM_RELEASE(d); + pcm_unlock(d); + } +} + +static int +sysctl_hw_snd_vpc_0db(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_vol_0db_pcm; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (val < SND_VOL_0DB_MIN || val > SND_VOL_0DB_MAX) + return (EINVAL); + + chn_vol_0db_pcm = val; + chn_vpc_proc(0, val); + + return (0); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_0db, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_vpc_0db, "I", + "0db relative level"); + +static int +sysctl_hw_snd_vpc_reset(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL || val == 0) + return (err); + + chn_vol_0db_pcm = SND_VOL_0DB_PCM; + chn_vpc_proc(1, SND_VOL_0DB_PCM); + + return (0); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_reset, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_vpc_reset, "I", + "reset volume on all channels"); + +static int chn_usefrags = 0; +TUNABLE_INT("hw.snd.usefrags", &chn_usefrags); +static int chn_syncdelay = -1; +TUNABLE_INT("hw.snd.syncdelay", &chn_syncdelay); +#ifdef SND_DEBUG +SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RW, + &chn_usefrags, 1, "prefer setfragments() over setblocksize()"); +SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RW, + &chn_syncdelay, 1, + "append (0-1000) millisecond trailing buffer delay on each sync"); +#endif + +/** + * @brief Channel sync group lock + * + * Clients should acquire this lock @b without holding any channel locks + * before touching syncgroups or the main syncgroup list. + */ +struct mtx snd_pcm_syncgroups_mtx; +MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF); +/** + * @brief syncgroups' master list + * + * Each time a channel syncgroup is created, it's added to this list. This + * list should only be accessed with @sa snd_pcm_syncgroups_mtx held. + * + * See SNDCTL_DSP_SYNCGROUP for more information. + */ +struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(head); static int chn_buildfeeder(struct pcm_channel *c); static void chn_lockinit(struct pcm_channel *c, int dir) { - switch(dir) { + switch (dir) { case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); + cv_init(&c->intr_cv, "pcmwr"); + break; + case PCMDIR_PLAY_VIRTUAL: + c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); + cv_init(&c->intr_cv, "pcmwrv"); break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); + cv_init(&c->intr_cv, "pcmrd"); break; - case PCMDIR_VIRTUAL: - c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); + case PCMDIR_REC_VIRTUAL: + c->lock = snd_mtxcreate(c->name, "pcm virtual record channel"); + cv_init(&c->intr_cv, "pcmrdv"); break; case 0: c->lock = snd_mtxcreate(c->name, "pcm fake channel"); + cv_init(&c->intr_cv, "pcmfk"); break; } + + cv_init(&c->cv, "pcmchn"); } static void chn_lockdestroy(struct pcm_channel *c) { + CHN_LOCKASSERT(c); + + CHN_BROADCAST(&c->cv); + CHN_BROADCAST(&c->intr_cv); + + cv_destroy(&c->cv); + cv_destroy(&c->intr_cv); + snd_mtxfree(c->lock); } +/** + * @brief Determine channel is ready for I/O + * + * @retval 1 = ready for I/O + * @retval 0 = not ready for I/O + */ static int chn_polltrigger(struct pcm_channel *c) { @@ -112,8 +279,8 @@ #if 0 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; #endif - lim = 1; - return (amt >= lim)? 1 : 0; + lim = c->lw; + return (amt >= lim) ? 1 : 0; } return 0; } @@ -131,38 +298,48 @@ static void chn_wakeup(struct pcm_channel *c) { - struct snd_dbuf *bs = c->bufsoft; - struct pcmchan_children *pce; + struct snd_dbuf *bs; + struct pcm_channel *ch; CHN_LOCKASSERT(c); - if (SLIST_EMPTY(&c->children)) { + + bs = c->bufsoft; + + if (CHN_EMPTY(c, children.busy)) { if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) selwakeuppri(sndbuf_getsel(bs), PRIBIO); + if (c->flags & CHN_F_SLEEPING) { + /* + * Ok, I can just panic it right here since it is + * quite obvious that we never allow multiple waiters + * from userland. I'm too generous... + */ + CHN_BROADCAST(&c->intr_cv); + } } else { - SLIST_FOREACH(pce, &c->children, link) { - CHN_LOCK(pce->channel); - chn_wakeup(pce->channel); - CHN_UNLOCK(pce->channel); + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + chn_wakeup(ch); + CHN_UNLOCK(ch); } } - - wakeup(bs); } static int -chn_sleep(struct pcm_channel *c, char *str, int timeout) +chn_sleep(struct pcm_channel *c, int timeout) { - struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); -#ifdef USING_MUTEX - ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout); -#else - ret = tsleep(bs, PRIBIO | PCATCH, str, timeout); -#endif - return ret; + if (c->flags & CHN_F_DEAD) + return (EINVAL); + + c->flags |= CHN_F_SLEEPING; + ret = cv_timedwait_sig(&c->intr_cv, c->lock, timeout); + c->flags &= ~CHN_F_SLEEPING; + + return ((c->flags & CHN_F_DEAD) ? EINVAL : ret); } /* @@ -184,22 +361,24 @@ delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); sndbuf_sethwptr(b, hwptr); - DEB( - if (delta >= ((sndbuf_getsize(b) * 15) / 16)) { - if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING))) - device_printf(c->dev, "hwptr went backwards %d -> %d\n", old, hwptr); - } - ); - if (c->direction == PCMDIR_PLAY) { - amt = MIN(delta, sndbuf_getready(b)); + amt = min(delta, sndbuf_getready(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { - amt = MIN(delta, sndbuf_getfree(b)); + amt = min(delta, sndbuf_getfree(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_acquire(b, NULL, amt); } + if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) { + device_printf(c->dev, "WARNING: %s DMA completion " + "too fast/slow ! hwptr=%u, old=%u " + "delta=%u amt=%u ready=%u free=%u\n", + CHN_DIRSTR(c), hwptr, old, delta, amt, + sndbuf_getready(b), sndbuf_getfree(b)); + } return delta; } @@ -212,7 +391,7 @@ CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); - if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || !(c->flags & CHN_F_TRIGGERED)) + if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c)) return; chn_dmaupdate(c); ret = chn_wrfeed(c); @@ -231,21 +410,11 @@ unsigned int ret, amt; CHN_LOCKASSERT(c); -#if 0 - DEB( - if (c->flags & CHN_F_CLOSING) { - sndbuf_dump(b, "b", 0x02); - sndbuf_dump(bs, "bs", 0x02); - }) -#endif - if (c->flags & CHN_F_MAPPED) + if ((c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_CLOSING)) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); amt = sndbuf_getfree(b); - KASSERT(amt <= sndbuf_getsize(bs), - ("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, - amt, sndbuf_getsize(bs), c->flags)); ret = (amt > 0) ? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; /* @@ -254,7 +423,7 @@ if (sndbuf_getfree(b) > 0) c->xruns++; - if (ret == 0 && sndbuf_getfree(b) < amt) + if (sndbuf_getfree(b) < amt) chn_wakeup(c); return ret; @@ -286,99 +455,65 @@ int chn_write(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, newsize, count, sz; struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); - /* - * XXX Certain applications attempt to write larger size - * of pcm data than c->blocksize2nd without blocking, - * resulting partial write. Expand the block size so that - * the write operation avoids blocking. - */ - if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) { - DEB(device_printf(c->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n", - buf->uio_resid, sndbuf_getblksz(bs))); - newsize = 16; - while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2)) - newsize <<= 1; - chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize); - DEB(device_printf(c->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs))); - } ret = 0; - count = hz; - while (!ret && (buf->uio_resid > 0) && (count > 0)) { - sz = sndbuf_getfree(bs); - if (sz == 0) { - if (c->flags & CHN_F_NBIO) - ret = EWOULDBLOCK; - else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - timeout = 1; - ret = chn_sleep(c, "pcmwr", timeout); - if (ret == EWOULDBLOCK) { - count -= timeout; - ret = 0; - } else if (ret == 0) - count = hz; - } - } else { - sz = MIN(sz, buf->uio_resid); - KASSERT(sz > 0, ("confusion in chn_write")); - /* printf("sz: %d\n", sz); */ + timeout = chn_timeout * hz; + while (ret == 0 && buf->uio_resid > 0) { + sz = min(buf->uio_resid, sndbuf_getfree(bs)); + if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ - togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && sz > 0) { p = sndbuf_getfreeptr(bs); - t = MIN(togo, sndbuf_getsize(bs) - p); + t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); - togo -= t; - x = sndbuf_acquire(bs, NULL, t); + sz -= t; + sndbuf_acquire(bs, NULL, t); } ret = 0; - if (ret == 0 && !(c->flags & CHN_F_TRIGGERED)) - chn_start(c, 0); + if (CHN_STOPPED(c)) { + ret = chn_start(c, 0); + if (ret != 0) + c->flags |= CHN_F_DEAD; + } + } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) { + /** + * @todo Evaluate whether EAGAIN is truly desirable. + * 4Front drivers behave like this, but I'm + * not sure if it at all violates the "write + * should be allowed to block" model. + * + * The idea is that, while set with CHN_F_NOTRIGGER, + * a channel isn't playing, *but* without this we + * end up with "interrupt timeout / channel dead". + */ + ret = EAGAIN; + } else { + ret = chn_sleep(c, timeout); + if (ret == EAGAIN) { + ret = EINVAL; + c->flags |= CHN_F_DEAD; + printf("%s: play interrupt timeout, " + "channel dead\n", c->name); + } else if (ret == ERESTART || ret == EINTR) + c->flags |= CHN_F_ABORTING; } } - /* printf("ret: %d left: %d\n", ret, buf->uio_resid); */ - - if (count <= 0) { - c->flags |= CHN_F_DEAD; - printf("%s: play interrupt timeout, channel dead\n", c->name); - } - - return ret; -} -#if 0 -static int -chn_rddump(struct pcm_channel *c, unsigned int cnt) -{ - struct snd_dbuf *b = c->bufhard; - - CHN_LOCKASSERT(c); -#if 0 - static uint32_t kk = 0; - printf("%u: dumping %d bytes\n", ++kk, cnt); -#endif - c->xruns++; - sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt); - return sndbuf_dispose(b, NULL, cnt); + return (ret); } -#endif /* * Feed new data from the read buffer. Can be called in the bottom half. @@ -391,21 +526,12 @@ unsigned int ret, amt; CHN_LOCKASSERT(c); - DEB( - if (c->flags & CHN_F_CLOSING) { - sndbuf_dump(b, "b", 0x02); - sndbuf_dump(bs, "bs", 0x02); - }) -#if 0 - amt = sndbuf_getready(b); - if (sndbuf_getfree(bs) < amt) { - c->xruns++; - amt = sndbuf_getfree(bs); - } -#endif + if (c->flags & CHN_F_MAPPED) + sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); + amt = sndbuf_getfree(bs); - ret = (amt > 0)? sndbuf_feed(b, bs, c, c->feeder, amt) : 0; + ret = (amt > 0) ? sndbuf_feed(b, bs, c, c->feeder, amt) : ENOSPC; amt = sndbuf_getready(b); if (amt > 0) { @@ -413,7 +539,8 @@ sndbuf_dispose(b, NULL, amt); } - chn_wakeup(c); + if (sndbuf_getready(bs) > 0) + chn_wakeup(c); return ret; } @@ -426,7 +553,7 @@ CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel")); - if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED)) + if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c)) return; chn_trigger(c, PCMTRIG_EMLDMARD); chn_dmaupdate(c); @@ -461,63 +588,57 @@ int chn_read(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, sz, count; - struct snd_dbuf *bs = c->bufsoft; + struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); - if (!(c->flags & CHN_F_TRIGGERED)) - chn_start(c, 0); + + if (CHN_STOPPED(c)) { + ret = chn_start(c, 0); + if (ret != 0) { + c->flags |= CHN_F_DEAD; + return (ret); + } + } ret = 0; - count = hz; - while (!ret && (buf->uio_resid > 0) && (count > 0)) { - sz = MIN(buf->uio_resid, sndbuf_getready(bs)); + timeout = chn_timeout * hz; + while (ret == 0 && buf->uio_resid > 0) { + sz = min(buf->uio_resid, sndbuf_getready(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ - togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && sz > 0) { p = sndbuf_getreadyptr(bs); - t = MIN(togo, sndbuf_getsize(bs) - p); + t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); - togo -= t; - x = sndbuf_dispose(bs, NULL, t); + sz -= t; + sndbuf_dispose(bs, NULL, t); } ret = 0; - } else { - if (c->flags & CHN_F_NBIO) { - ret = EWOULDBLOCK; - } else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - ret = chn_sleep(c, "pcmrd", timeout); - if (ret == EWOULDBLOCK) { - count -= timeout; - ret = 0; - } else { - count = hz; - } - - } + } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) + ret = EAGAIN; + else { + ret = chn_sleep(c, timeout); + if (ret == EAGAIN) { + ret = EINVAL; + c->flags |= CHN_F_DEAD; + printf("%s: record interrupt timeout, " + "channel dead\n", c->name); + } else if (ret == ERESTART || ret == EINTR) + c->flags |= CHN_F_ABORTING; } } - if (count <= 0) { - c->flags |= CHN_F_DEAD; - printf("%s: record interrupt timeout, channel dead\n", c->name); - } - - return ret; + return (ret); } void @@ -538,38 +659,67 @@ u_int32_t i, j; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; + int err; CHN_LOCKASSERT(c); /* if we're running, or if we're prevented from triggering, bail */ - if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force)) - return EINVAL; + if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force)) + return (EINVAL); - i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs); - j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b); - if (force || (i >= j)) { - c->flags |= CHN_F_TRIGGERED; - /* - * if we're starting because a vchan started, don't feed any data - * or it becomes impossible to start vchans synchronised with the - * first one. the hardbuf should be empty so we top it up with - * silence to give it something to chew. the real data will be - * fed at the first irq. - */ - if (c->direction == PCMDIR_PLAY) { - /* - * Reduce pops during playback startup. - */ - sndbuf_fillsilence(b); - if (SLIST_EMPTY(&c->children)) - chn_wrfeed(c); + err = 0; + + if (force) { + i = 1; + j = 0; + } else { + if (c->direction == PCMDIR_REC) { + i = sndbuf_getfree(bs); + j = (i > 0) ? 1 : sndbuf_getready(b); + } else { + if (sndbuf_getfree(bs) == 0) { + i = 1; + j = 0; + } else { + struct snd_dbuf *pb; + + pb = CHN_BUF_PARENT(c, b); + i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb); + j = sndbuf_getbps(pb); + } } + if (snd_verbose > 3 && CHN_EMPTY(c, children)) + printf("%s: %s (%s) threshold i=%d j=%d\n", + __func__, CHN_DIRSTR(c), + (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + i, j); + } + + if (i >= j) { + c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); + c->feedcount = (c->flags & CHN_F_CLOSING) ? 2 : 0; + c->interrupts = 0; c->xruns = 0; - chn_trigger(c, PCMTRIG_START); - return 0; + if (c->direction == PCMDIR_PLAY && c->parentchannel == NULL) { + sndbuf_fillsilence(b); + if (snd_verbose > 3) + printf("%s: %s starting! (%s) (ready=%d " + "force=%d i=%d j=%d intrtimeout=%u " + "latency=%dms)\n", + __func__, + (c->flags & CHN_F_HAS_VCHAN) ? + "VCHAN" : "HW", + (c->flags & CHN_F_CLOSING) ? "closing" : + "running", + sndbuf_getready(b), + force, i, j, c->timeout, + (sndbuf_getsize(b) * 1000) / + (sndbuf_getbps(b) * sndbuf_getspd(b))); + } + err = chn_trigger(c, PCMTRIG_START); } - return 0; + return (err); } void @@ -591,35 +741,127 @@ int chn_sync(struct pcm_channel *c, int threshold) { - u_long rdy; - int ret; - struct snd_dbuf *bs = c->bufsoft; + struct snd_dbuf *b, *bs; + int ret, count, hcount, minflush, resid, residp, syncdelay, blksz; + u_int32_t cflag; CHN_LOCKASSERT(c); + if (c->direction != PCMDIR_PLAY) + return (EINVAL); + + bs = c->bufsoft; + + if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) || + (threshold < 1 && sndbuf_getready(bs) < 1)) + return (0); + /* if we haven't yet started and nothing is buffered, else start*/ - if (!(c->flags & CHN_F_TRIGGERED)) { - if (sndbuf_getready(bs) > 0) { + if (CHN_STOPPED(c)) { + if (threshold > 0 || sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); - if (ret) - return ret; - } else { - return 0; - } + if (ret != 0) + return (ret); + } else + return (0); + } + + b = CHN_BUF_PARENT(c, c->bufhard); + + minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs); + + syncdelay = chn_syncdelay; + + if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0)) + minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs); + + /* + * Append (0-1000) millisecond trailing buffer (if needed) + * for slower / high latency hardwares (notably USB audio) + * to avoid audible truncation. + */ + if (syncdelay > 0) + minflush += (sndbuf_getbps(bs) * sndbuf_getspd(bs) * + ((syncdelay > 1000) ? 1000 : syncdelay)) / 1000; + + minflush -= minflush % sndbuf_getbps(bs); + + if (minflush > 0) { + threshold = min(minflush, sndbuf_getfree(bs)); + sndbuf_clear(bs, threshold); + sndbuf_acquire(bs, NULL, threshold); + minflush -= threshold; + } + + resid = sndbuf_getready(bs); + residp = resid; + blksz = sndbuf_getblksz(b); + if (blksz < 1) { + printf("%s: WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n", + __func__, sndbuf_getmaxsize(b), sndbuf_getsize(b), + sndbuf_getblksz(b), sndbuf_getblkcnt(b)); + if (sndbuf_getblkcnt(b) > 0) + blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b); + if (blksz < 1) + blksz = 1; } + count = sndbuf_xbytes(minflush + resid, bs, b) / blksz; + hcount = count; + ret = 0; + + if (snd_verbose > 3) + printf("%s: [begin] timeout=%d count=%d " + "minflush=%d resid=%d\n", __func__, c->timeout, count, + minflush, resid); - for (;;) { - rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); - if (rdy <= threshold) { - ret = chn_sleep(c, "pcmsyn", 1); - if (ret == ERESTART || ret == EINTR) { - DEB(printf("chn_sync: tsleep returns %d\n", ret)); - return -1; - } + cflag = c->flags & CHN_F_CLOSING; + c->flags |= CHN_F_CLOSING; + while (count > 0 && (resid > 0 || minflush > 0)) { + ret = chn_sleep(c, c->timeout); + if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; + break; + } else if (ret == 0 || ret == EAGAIN) { + resid = sndbuf_getready(bs); + if (resid == residp) { + --count; + if (snd_verbose > 3) + printf("%s: [stalled] timeout=%d " + "count=%d hcount=%d " + "resid=%d minflush=%d\n", + __func__, c->timeout, count, + hcount, resid, minflush); + } else if (resid < residp && count < hcount) { + ++count; + if (snd_verbose > 3) + printf("%s: [resume] timeout=%d " + "count=%d hcount=%d " + "resid=%d minflush=%d\n", + __func__, c->timeout, count, + hcount, resid, minflush); + } + if (minflush > 0 && sndbuf_getfree(bs) > 0) { + threshold = min(minflush, + sndbuf_getfree(bs)); + sndbuf_clear(bs, threshold); + sndbuf_acquire(bs, NULL, threshold); + resid = sndbuf_getready(bs); + minflush -= threshold; + } + residp = resid; } else break; - } - return 0; + } + c->flags &= ~CHN_F_CLOSING; + c->flags |= cflag; + + if (snd_verbose > 3) + printf("%s: timeout=%d count=%d hcount=%d resid=%d residp=%d " + "minflush=%d ret=%d\n", + __func__, c->timeout, count, hcount, resid, residp, + minflush, ret); + + return (0); } /* called externally, handle locking */ @@ -630,14 +872,17 @@ int ret; CHN_LOCKASSERT(c); - if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED)) - chn_start(c, 1); + if (!(c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED))) { + ret = chn_start(c, 1); + if (ret != 0) + return (0); + } ret = 0; if (chn_polltrigger(c) && chn_pollreset(c)) ret = ev; else selrecord(td, sndbuf_getsel(bs)); - return ret; + return (ret); } /* @@ -654,7 +899,7 @@ struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); - if (!(c->flags & CHN_F_TRIGGERED)) + if (CHN_STOPPED(c)) return 0; c->flags |= CHN_F_ABORTING; @@ -664,7 +909,7 @@ sndbuf_setrun(b, 0); if (!(c->flags & CHN_F_VIRTUAL)) chn_dmaupdate(c); - missing = sndbuf_getready(bs) + sndbuf_getready(b); + missing = sndbuf_getready(bs); c->flags &= ~CHN_F_ABORTING; return missing; @@ -684,48 +929,14 @@ int chn_flush(struct pcm_channel *c) { - int ret, count, resid, resid_p; struct snd_dbuf *b = c->bufhard; - struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); - /* if we haven't yet started and nothing is buffered, else start*/ - if (!(c->flags & CHN_F_TRIGGERED)) { - if (sndbuf_getready(bs) > 0) { - ret = chn_start(c, 1); - if (ret) - return ret; - } else { - return 0; - } - } - c->flags |= CHN_F_CLOSING; - resid = sndbuf_getready(bs) + sndbuf_getready(b); - resid_p = resid; - count = 10; - ret = 0; - while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) { - /* still pending output data. */ - ret = chn_sleep(c, "pcmflu", hz / 10); - if (ret == EWOULDBLOCK) - ret = 0; - if (ret == 0) { - resid = sndbuf_getready(bs) + sndbuf_getready(b); - if (resid == resid_p) - count--; - if (resid > resid_p) - DEB(printf("chn_flush: buffer length increasind %d -> %d\n", resid_p, resid)); - resid_p = resid; - } - } - if (count == 0) - DEB(printf("chn_flush: timeout, hw %d, sw %d\n", - sndbuf_getready(b), sndbuf_getready(bs))); - + chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); @@ -746,14 +957,141 @@ return 0; } +static struct afmtstr_table default_afmtstr_table[] = { + { "alaw", AFMT_A_LAW }, { "mulaw", AFMT_MU_LAW }, + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +int +afmtstr_swap_sign(char *s) +{ + if (s == NULL || strlen(s) < 2) /* full length of "s8" */ + return 0; + if (*s == 's') + *s = 'u'; + else if (*s == 'u') + *s = 's'; + else + return 0; + return 1; +} + +int +afmtstr_swap_endian(char *s) +{ + if (s == NULL || strlen(s) < 5) /* full length of "s16le" */ + return 0; + if (s[3] == 'l') + s[3] = 'b'; + else if (s[3] == 'b') + s[3] = 'l'; + else + return 0; + return 1; +} + +u_int32_t +afmtstr2afmt(struct afmtstr_table *tbl, const char *s, int stereo) +{ + size_t fsz, sz; + + sz = (s == NULL) ? 0 : strlen(s); + + if (sz > 1) { + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->fmtstr != NULL; tbl++) { + fsz = strlen(tbl->fmtstr); + if (sz < fsz) + continue; + if (strncmp(s, tbl->fmtstr, fsz) != 0) + continue; + if (fsz == sz) + return tbl->format | + ((stereo) ? AFMT_STEREO : 0); + if ((sz - fsz) < 2 || s[fsz] != ':') + break; + /* + * For now, just handle mono/stereo. + */ + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 'm' || + s[fsz + 1] == '1')) || + strcmp(s + fsz + 1, "mono") == 0) + return tbl->format; + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 's' || + s[fsz + 1] == '2')) || + strcmp(s + fsz + 1, "stereo") == 0) + return tbl->format | AFMT_STEREO; + break; + } + } + + return 0; +} + +u_int32_t +afmt2afmtstr(struct afmtstr_table *tbl, u_int32_t afmt, char *dst, + size_t len, int type, int stereo) +{ + u_int32_t fmt = 0; + char *fmtstr = NULL, *tag = ""; + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->format != 0; tbl++) { + if (tbl->format == 0) + break; + if ((afmt & ~AFMT_STEREO) != tbl->format) + continue; + fmt = afmt; + fmtstr = tbl->fmtstr; + break; + } + + if (fmt != 0 && fmtstr != NULL && dst != NULL && len > 0) { + strlcpy(dst, fmtstr, len); + switch (type) { + case AFMTSTR_SIMPLE: + tag = (fmt & AFMT_STEREO) ? ":s" : ":m"; + break; + case AFMTSTR_NUM: + tag = (fmt & AFMT_STEREO) ? ":2" : ":1"; + break; + case AFMTSTR_FULL: + tag = (fmt & AFMT_STEREO) ? ":stereo" : ":mono"; + break; + case AFMTSTR_NONE: + default: + break; + } + if (strlen(tag) > 0 && ((stereo && !(fmt & AFMT_STEREO)) || \ + (!stereo && (fmt & AFMT_STEREO)))) + strlcat(dst, tag, len); + } + + return fmt; +} + int chn_reset(struct pcm_channel *c, u_int32_t fmt) { int hwspd, r; CHN_LOCKASSERT(c); + c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; + c->timeout = 1; c->xruns = 0; r = CHANNEL_RESET(c->methods, c->devinfo); @@ -778,7 +1116,7 @@ #endif } if (r == 0) - r = chn_setblocksize(c, 0, 0); + r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); @@ -793,12 +1131,19 @@ struct snd_dbuf *b, *bs; int ret; + if (chn_timeout < CHN_TIMEOUT_MIN || chn_timeout > CHN_TIMEOUT_MAX) + chn_timeout = CHN_TIMEOUT; + chn_lockinit(c, dir); b = NULL; bs = NULL; + CHN_INIT(c, children); + CHN_INIT(c, children.busy); c->devinfo = NULL; c->feeder = NULL; + c->latency = -1; + c->timeout = 1; ret = ENOMEM; b = sndbuf_create(c->dev, c->name, "primary", c); @@ -829,6 +1174,20 @@ c->bufsoft = bs; c->flags = 0; c->feederflags = 0; + c->sm = NULL; + + /* Only Front Left/Right, for now. */ + c->matrix[0] = SND_CHN_T_FL; + c->matrix[1] = SND_CHN_T_FR; + c->matrix[2] = SND_CHN_T_MAX; + + c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER; + c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm; + + c->volume[SND_VOL_C_MASTER][SND_CHN_T_FL] = SND_VOL_0DB_MASTER; + c->volume[SND_VOL_C_MASTER][SND_CHN_T_FR] = SND_VOL_0DB_MASTER; + + chn_vpc_reset(c, SND_VOL_C_PCM, 1); ret = ENODEV; CHN_UNLOCK(c); /* XXX - Unlock for CHANNEL_INIT() malloc() call */ @@ -853,6 +1212,19 @@ if (ret) goto out; + /** + * @todo Should this be moved somewhere else? The primary buffer + * is allocated by the driver or via DMA map setup, and tmpbuf + * seems to only come into existence in sndbuf_resize(). + */ + if (c->direction == PCMDIR_PLAY) { + bs->sl = sndbuf_getmaxsize(bs); + bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_NOWAIT); + if (bs->shadbuf == NULL) { + ret = ENOMEM; + goto out; + } + } out: CHN_UNLOCK(c); @@ -865,6 +1237,7 @@ sndbuf_destroy(bs); if (b) sndbuf_destroy(b); + CHN_LOCK(c); c->flags |= CHN_F_DEAD; chn_lockdestroy(c); @@ -880,16 +1253,22 @@ struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; - if (c->flags & CHN_F_TRIGGERED) + if (CHN_STARTED(c)) { + CHN_LOCK(c); chn_trigger(c, PCMTRIG_ABORT); - while (chn_removefeeder(c) == 0); + CHN_UNLOCK(c); + } + while (chn_removefeeder(c) == 0) + ; if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); - c->flags |= CHN_F_DEAD; sndbuf_destroy(bs); sndbuf_destroy(b); + CHN_LOCK(c); + c->flags |= CHN_F_DEAD; chn_lockdestroy(c); - return 0; + + return (0); } int @@ -910,21 +1289,472 @@ return r; } +/* XXX Obsolete. Use *_matrix() variant instead. */ int chn_setvolume(struct pcm_channel *c, int left, int right) { - CHN_LOCKASSERT(c); - /* should add a feeder for volume changing if channel returns -1 */ - if (left > 100) - left = 100; - if (left < 0) - left = 0; - if (right > 100) - right = 100; - if (right < 0) - right = 0; - c->volume = left | (right << 8); - return 0; + int ret; + + ret = chn_setvolume_matrix(c, SND_VOL_C_MASTER, SND_CHN_T_FL, left); + ret |= chn_setvolume_matrix(c, SND_VOL_C_MASTER, SND_CHN_T_FR, + right) << 8; + + return (ret); +} + +int +chn_setvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt, int val) +{ + int i; + + KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && + (vc == SND_VOL_C_MASTER || (vc & 1)) && + (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && + vt <= SND_CHN_T_END)) && (vt != SND_CHN_T_VOL_0DB || + (val >= SND_VOL_0DB_MIN && val <= SND_VOL_0DB_MAX)), + ("%s(): invalid volume matrix c=%p vc=%d vt=%d val=%d", + __func__, c, vc, vt, val)); + CHN_LOCKASSERT(c); + + if (val < 0) + val = 0; + if (val > 100) + val = 100; + + c->volume[vc][vt] = val; + + /* + * Do relative calculation here and store it into class + 1 + * to ease the job of feeder_volume. + */ + if (vc == SND_VOL_C_MASTER) { + for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; + vc += SND_VOL_C_STEP) + c->volume[SND_VOL_C_VAL(vc)][vt] = + SND_VOL_CALC_VAL(c->volume, vc, vt); + } else if (vc & 1) { + if (vt == SND_CHN_T_VOL_0DB) { + for (i = 0; c->matrix[i] != SND_CHN_T_MAX; i++) { + vt = c->matrix[i]; + c->volume[SND_VOL_C_VAL(vc)][vt] = + SND_VOL_CALC_VAL(c->volume, vc, vt); + } + } else + c->volume[SND_VOL_C_VAL(vc)][vt] = + SND_VOL_CALC_VAL(c->volume, vc, vt); + } + + return (val); +} + +int +chn_getvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt) +{ + KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && + (vt == SND_CHN_T_VOL_0DB || + (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), + ("%s(): invalid volume matrix c=%p vc=%d vt=%d", + __func__, c, vc, vt)); + CHN_LOCKASSERT(c); + + return (c->volume[vc][vt]); +} + +void +chn_vpc_reset(struct pcm_channel *c, snd_volume_class_t vc, int force) +{ + int i; + + KASSERT(c != NULL && vc >= SND_VOL_C_BEGIN && vc <= SND_VOL_C_END, + ("%s(): invalid reset c=%p vc=%d", __func__, c, vc)); + CHN_LOCKASSERT(c); + + if (force == 0 && chn_vpc_autoreset == 0) + return; + + for (i = 0; c->matrix[i] != SND_CHN_T_MAX; i++) + CHN_SETVOLUME(c, vc, c->matrix[i], + c->volume[vc][SND_CHN_T_VOL_0DB]); +} + +static u_int32_t +round_pow2(u_int32_t v) +{ + u_int32_t ret; + + if (v < 2) + v = 2; + ret = 0; + while (v >> ret) + ret++; + ret = 1 << (ret - 1); + while (ret < v) + ret <<= 1; + return ret; +} + +static u_int32_t +round_blksz(u_int32_t v, int round) +{ + u_int32_t ret, tmp; + + if (round < 1) + round = 1; + + ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1); + + if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2)) + ret >>= 1; + + tmp = ret - (ret % round); + while (tmp < 16 || tmp < round) { + ret <<= 1; + tmp = ret - (ret % round); + } + + return ret; +} + +/* + * 4Front call it DSP Policy, while we call it "Latency Profile". The idea + * is to keep 2nd buffer short so that it doesn't cause long queue during + * buffer transfer. + * + * Latency reference table for 48khz stereo 16bit: (PLAY) + * + * +---------+------------+-----------+------------+ + * | Latency | Blockcount | Blocksize | Buffersize | + * +---------+------------+-----------+------------+ + * | 0 | 2 | 64 | 128 | + * +---------+------------+-----------+------------+ + * | 1 | 4 | 128 | 512 | + * +---------+------------+-----------+------------+ + * | 2 | 8 | 512 | 4096 | + * +---------+------------+-----------+------------+ + * | 3 | 16 | 512 | 8192 | + * +---------+------------+-----------+------------+ + * | 4 | 32 | 512 | 16384 | + * +---------+------------+-----------+------------+ + * | 5 | 32 | 1024 | 32768 | + * +---------+------------+-----------+------------+ + * | 6 | 16 | 2048 | 32768 | + * +---------+------------+-----------+------------+ + * | 7 | 8 | 4096 | 32768 | + * +---------+------------+-----------+------------+ + * | 8 | 4 | 8192 | 32768 | + * +---------+------------+-----------+------------+ + * | 9 | 2 | 16384 | 32768 | + * +---------+------------+-----------+------------+ + * | 10 | 2 | 32768 | 65536 | + * +---------+------------+-----------+------------+ + * + * Recording need a different reference table. All we care is + * gobbling up everything within reasonable buffering threshold. + * + * Latency reference table for 48khz stereo 16bit: (REC) + * + * +---------+------------+-----------+------------+ + * | Latency | Blockcount | Blocksize | Buffersize | + * +---------+------------+-----------+------------+ + * | 0 | 512 | 32 | 16384 | + * +---------+------------+-----------+------------+ + * | 1 | 256 | 64 | 16384 | + * +---------+------------+-----------+------------+ + * | 2 | 128 | 128 | 16384 | + * +---------+------------+-----------+------------+ + * | 3 | 64 | 256 | 16384 | + * +---------+------------+-----------+------------+ + * | 4 | 32 | 512 | 16384 | + * +---------+------------+-----------+------------+ + * | 5 | 32 | 1024 | 32768 | + * +---------+------------+-----------+------------+ + * | 6 | 16 | 2048 | 32768 | + * +---------+------------+-----------+------------+ + * | 7 | 8 | 4096 | 32768 | + * +---------+------------+-----------+------------+ + * | 8 | 4 | 8192 | 32768 | + * +---------+------------+-----------+------------+ + * | 9 | 2 | 16384 | 32768 | + * +---------+------------+-----------+------------+ + * | 10 | 2 | 32768 | 65536 | + * +---------+------------+-----------+------------+ + * + * Calculations for other data rate are entirely based on these reference + * tables. For normal operation, Latency 5 seems give the best, well + * balanced performance for typical workload. Anything below 5 will + * eat up CPU to keep up with increasing context switches because of + * shorter buffer space and usually require the application to handle it + * aggresively through possibly real time programming technique. + * + */ +#define CHN_LATENCY_PBLKCNT_REF \ + {{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}, \ + {1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}} +#define CHN_LATENCY_PBUFSZ_REF \ + {{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16}, \ + {11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}} + +#define CHN_LATENCY_RBLKCNT_REF \ + {{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}, \ + {9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}} +#define CHN_LATENCY_RBUFSZ_REF \ + {{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16}, \ + {15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}} + +#define CHN_LATENCY_DATA_REF 192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */ + +static int +chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, + u_int32_t max, int *rblksz, int *rblkcnt) +{ + static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_PBLKCNT_REF; + static int pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_PBUFSZ_REF; + static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_RBLKCNT_REF; + static int rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_RBUFSZ_REF; + u_int32_t bufsz; + int lprofile, blksz, blkcnt; + + if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || + bps < 1 || datarate < 1 || + !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { + if (rblksz != NULL) + *rblksz = CHN_2NDBUFMAXSIZE >> 1; + if (rblkcnt != NULL) + *rblkcnt = 2; + printf("%s: FAILED dir=%d latency=%d bps=%d " + "datarate=%u max=%u\n", + __func__, dir, latency, bps, datarate, max); + return CHN_2NDBUFMAXSIZE; + } + + lprofile = chn_latency_profile; + + if (dir == PCMDIR_PLAY) { + blkcnt = pblkcnts[lprofile][latency]; + bufsz = pbufszs[lprofile][latency]; + } else { + blkcnt = rblkcnts[lprofile][latency]; + bufsz = rbufszs[lprofile][latency]; + } + + bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, + datarate)); + if (bufsz > max) + bufsz = max; + blksz = round_blksz(bufsz >> blkcnt, bps); + + if (rblksz != NULL) + *rblksz = blksz; + if (rblkcnt != NULL) + *rblkcnt = 1 << blkcnt; + + return blksz << blkcnt; +} + +static int +chn_resizebuf(struct pcm_channel *c, int latency, + int blkcnt, int blksz) +{ + struct snd_dbuf *b, *bs, *pb; + int sblksz, sblkcnt, hblksz, hblkcnt, limit = 1; + int ret; + + CHN_LOCKASSERT(c); + + if ((c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED)) || + !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC)) + return EINVAL; + + if (latency == -1) { + c->latency = -1; + latency = chn_latency; + } else if (latency == -2) { + latency = c->latency; + if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) + latency = chn_latency; + } else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) + return EINVAL; + else { + c->latency = latency; + limit = 0; + } + + bs = c->bufsoft; + b = c->bufhard; + + if (!(blksz == 0 || blkcnt == -1) && + (blksz < 16 || blksz < sndbuf_getbps(bs) || blkcnt < 2 || + (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) + return EINVAL; + + chn_calclatency(c->direction, latency, sndbuf_getbps(bs), + sndbuf_getbps(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE, + &sblksz, &sblkcnt); + + if (blksz == 0 || blkcnt == -1) { + if (blkcnt == -1) + c->flags &= ~CHN_F_HAS_SIZE; + if (c->flags & CHN_F_HAS_SIZE) { + blksz = sndbuf_getblksz(bs); + blkcnt = sndbuf_getblkcnt(bs); + } + } else + c->flags |= CHN_F_HAS_SIZE; + + if (c->flags & CHN_F_HAS_SIZE) { + /* + * The application has requested their own blksz/blkcnt. + * Just obey with it, and let them toast alone. We can + * clamp it to the nearest latency profile, but that would + * defeat the purpose of having custom control. The least + * we can do is round it to the nearest ^2 and align it. + */ + sblksz = round_blksz(blksz, sndbuf_getbps(bs)); + sblkcnt = round_pow2(blkcnt); + limit = 0; + } + + if (c->parentchannel != NULL) { + pb = CHN_BUF_PARENT(c, NULL); + CHN_UNLOCK(c); + CHN_LOCK(c->parentchannel); + chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); + CHN_UNLOCK(c->parentchannel); + CHN_LOCK(c); + limit = (limit != 0 && pb != NULL) ? + sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; + c->timeout = c->parentchannel->timeout; + } else { + hblkcnt = 2; + if (c->flags & CHN_F_HAS_SIZE) { + hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b), + sndbuf_getbps(b)); + hblkcnt = round_pow2(sndbuf_getblkcnt(bs)); + } else + chn_calclatency(c->direction, latency, + sndbuf_getbps(b), + sndbuf_getbps(b) * sndbuf_getspd(b), + CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt); + + if ((hblksz << 1) > sndbuf_getmaxsize(b)) + hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1, + sndbuf_getbps(b)); + + while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) { + if (hblkcnt < 4) + hblksz >>= 1; + else + hblkcnt >>= 1; + } + + hblksz -= hblksz % sndbuf_getbps(b); + +#if 0 + hblksz = sndbuf_getmaxsize(b) >> 1; + hblksz -= hblksz % sndbuf_getbps(b); + hblkcnt = 2; +#endif + + CHN_UNLOCK(c); + if (chn_usefrags == 0 || + CHANNEL_SETFRAGMENTS(c->methods, c->devinfo, + hblksz, hblkcnt) < 1) + sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, + c->devinfo, hblksz)); + CHN_LOCK(c); + + if (!CHN_EMPTY(c, children)) { + sblksz = round_blksz( + sndbuf_xbytes(sndbuf_getsize(b) >> 1, b, bs), + sndbuf_getbps(bs)); + sblkcnt = 2; + limit = 0; + } else if (limit != 0) + limit = sndbuf_xbytes(sndbuf_getsize(b), b, bs); + + /* + * Interrupt timeout + */ + c->timeout = ((u_int64_t)hz * sndbuf_getsize(b)) / + ((u_int64_t)sndbuf_getspd(b) * sndbuf_getbps(b)); + if (c->timeout < 1) + c->timeout = 1; + } + + if (limit > CHN_2NDBUFMAXSIZE) + limit = CHN_2NDBUFMAXSIZE; + +#if 0 + while (limit > 0 && (sblksz * sblkcnt) > limit) { + if (sblkcnt < 4) + break; + sblkcnt >>= 1; + } +#endif + + while ((sblksz * sblkcnt) < limit) + sblkcnt <<= 1; + + while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { + if (sblkcnt < 4) + sblksz >>= 1; + else + sblkcnt >>= 1; + } + + sblksz -= sblksz % sndbuf_getbps(bs); + + if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || + sndbuf_getsize(bs) != (sblkcnt * sblksz)) { + ret = sndbuf_remalloc(bs, sblkcnt, sblksz); + if (ret != 0) { + printf("%s: Failed: %d %d\n", __func__, + sblkcnt, sblksz); + return ret; + } + } + + /* + * OSSv4 docs: "By default OSS will set the low water level equal + * to the fragment size which is optimal in most cases." + */ + c->lw = sndbuf_getblksz(bs); + chn_resetbuf(c); + + if (snd_verbose > 3) + printf("%s: %s (%s) timeout=%u " + "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", + __func__, CHN_DIRSTR(c), + (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + c->timeout, + sndbuf_getsize(b), sndbuf_getblksz(b), + sndbuf_getblkcnt(b), + sndbuf_getsize(bs), sndbuf_getblksz(bs), + sndbuf_getblkcnt(bs), limit); + + return 0; +} + +int +chn_setlatency(struct pcm_channel *c, int latency) +{ + CHN_LOCKASSERT(c); + /* Destroy blksz/blkcnt, enforce latency profile. */ + return chn_resizebuf(c, latency, -1, 0); +} + +int +chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) +{ + CHN_LOCKASSERT(c); + /* Destroy latency profile, enforce blksz/blkcnt */ + return chn_resizebuf(c, -1, blkcnt, blksz); } static int @@ -941,7 +1771,7 @@ DEB(printf("want speed %d, ", speed)); if (speed <= 0) return EINVAL; - if (CANCHANGE(c)) { + if (CHN_STOPPED(c)) { r = 0; c->speed = speed; sndbuf_setspd(bs, speed); @@ -958,7 +1788,7 @@ /* * Used to be 500. It was too big! */ - if (delta > 25) + if (delta > feeder_rate_round) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); @@ -968,10 +1798,6 @@ if (r) goto out; - r = chn_setblocksize(c, 0, 0); - if (r) - goto out; - if (!(c->feederflags & (1 << FEEDER_RATE))) goto out; @@ -996,6 +1822,8 @@ sndbuf_getfmt(b)); if (!r) sndbuf_setfmt(bs, c->format); + if (!r) + r = chn_resizebuf(c, -2, 0, 0); DEB(printf("setspeed done, r = %d\n", r)); return r; } else @@ -1009,7 +1837,9 @@ r = chn_tryspeed(c, speed); if (r) { - DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed)); + if (snd_verbose > 3) + printf("Failed to set speed %d falling back to %d\n", + speed, oldspeed); r = chn_tryspeed(c, oldspeed); } return r; @@ -1023,7 +1853,7 @@ int r; CHN_LOCKASSERT(c); - if (CANCHANGE(c)) { + if (CHN_STOPPED(c)) { DEB(printf("want format %d\n", fmt)); c->format = fmt; r = chn_buildfeeder(c); @@ -1047,190 +1877,21 @@ r = chn_tryformat(c, fmt); if (r) { - DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt)); + if (snd_verbose > 3) + printf("Format change 0x%08x failed, reverting to 0x%08x\n", + fmt, oldfmt); chn_tryformat(c, oldfmt); } return r; } -/* - * given a bufsz value, round it to a power of 2 in the min-max range - * XXX only works if min and max are powers of 2 - */ -static int -round_bufsz(int bufsz, int min, int max) -{ - int tmp = min * 2; - - KASSERT((min & (min-1)) == 0, ("min %d must be power of 2\n", min)); - KASSERT((max & (max-1)) == 0, ("max %d must be power of 2\n", max)); - while (tmp <= bufsz) - tmp <<= 1; - tmp >>= 1; - if (tmp > max) - tmp = max; - return tmp; -} - -/* - * set the channel's blocksize both for soft and hard buffers. - * - * blksz should be a power of 2 between 2**4 and 2**16 -- it is useful - * that it has the same value for both bufsoft and bufhard. - * blksz == -1 computes values according to a target irq rate. - * blksz == 0 reuses previous values if available, otherwise - * behaves as for -1 - * - * blkcnt is set by the user, between 2 and (2**17)/blksz for bufsoft, - * but should be a power of 2 for bufhard to simplify life to low - * level drivers. - * Note, for the rec channel a large blkcnt is ok, - * but for the play channel we want blksz as small as possible to keep - * the delay small, because routines in the write path always try to - * keep bufhard full. - * - * Unless we have good reason to, use the values suggested by the caller. - */ -int -chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) -{ - struct snd_dbuf *b = c->bufhard; - struct snd_dbuf *bs = c->bufsoft; - int irqhz, ret, maxsz, maxsize, reqblksz; - - CHN_LOCKASSERT(c); - if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED)) { - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d", __func__, - c->name, sndbuf_getsize(bs), sndbuf_getsize(b))); - return EINVAL; - } - c->flags |= CHN_F_SETBLOCKSIZE; - - ret = 0; - DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz)); - if (blksz == 0 || blksz == -1) { /* let the driver choose values */ - if (blksz == -1) /* delete previous values */ - c->flags &= ~CHN_F_HAS_SIZE; - if (!(c->flags & CHN_F_HAS_SIZE)) { /* no previous value */ - /* - * compute a base blksz according to the target irq - * rate, then round to a suitable power of 2 - * in the range 16.. 2^17/2. - * Finally compute a suitable blkcnt. - */ - blksz = round_bufsz( (sndbuf_getbps(bs) * - sndbuf_getspd(bs)) / chn_targetirqrate, - 16, CHN_2NDBUFMAXSIZE / 2); - blkcnt = CHN_2NDBUFMAXSIZE / blksz; - } else { /* use previously defined value */ - blkcnt = sndbuf_getblkcnt(bs); - blksz = sndbuf_getblksz(bs); - } - } else { - /* - * use supplied values if reasonable. Note that here we - * might have blksz which is not a power of 2 if the - * ioctl() to compute it allows such values. - */ - ret = EINVAL; - if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE)) - goto out; - ret = 0; - c->flags |= CHN_F_HAS_SIZE; - } - - reqblksz = blksz; - if (reqblksz < sndbuf_getbps(bs)) - reqblksz = sndbuf_getbps(bs); - if (reqblksz % sndbuf_getbps(bs)) - reqblksz -= reqblksz % sndbuf_getbps(bs); - - /* adjust for different hw format/speed */ - /* - * Now compute the approx irq rate for the given (soft) blksz, - * reduce to the acceptable range and compute a corresponding blksz - * for the hard buffer. Then set the channel's blocksize and - * corresponding hardbuf value. The number of blocks used should - * be set by the device-specific routine. In fact, even the - * call to sndbuf_setblksz() should not be here! XXX - */ - - irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / blksz; - RANGE(irqhz, 16, 512); - - maxsz = sndbuf_getmaxsize(b); - if (maxsz == 0) /* virtual channels don't appear to allocate bufhard */ - maxsz = CHN_2NDBUFMAXSIZE; - blksz = round_bufsz( (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz, - 16, maxsz / 2); - - /* Increase the size of bufsoft if before increasing bufhard. */ - maxsize = sndbuf_getsize(b); - if (sndbuf_getsize(bs) > maxsize) - maxsize = sndbuf_getsize(bs); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - CHN_UNLOCK(c); - sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz)); - CHN_LOCK(c); - - /* Decrease the size of bufsoft after decreasing bufhard. */ - maxsize = sndbuf_getsize(b); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (maxsize > sndbuf_getsize(bs)) - printf("Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE()\n", - c->name, sndbuf_getsize(bs), maxsize); - if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - chn_resetbuf(c); -out1: - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d, reqblksz=%d blksz=%d maxsize=%d blkcnt=%d", - __func__, c->name, sndbuf_getsize(bs), sndbuf_getsize(b), reqblksz, - blksz, maxsize, blkcnt)); -out: - c->flags &= ~CHN_F_SETBLOCKSIZE; -#if 0 - if (1) { - static uint32_t kk = 0; - printf("%u: b %d/%d/%d : (%d)%d/0x%0x | bs %d/%d/%d : (%d)%d/0x%0x\n", ++kk, - sndbuf_getsize(b), sndbuf_getblksz(b), sndbuf_getblkcnt(b), - sndbuf_getbps(b), - sndbuf_getspd(b), sndbuf_getfmt(b), - sndbuf_getsize(bs), sndbuf_getblksz(bs), sndbuf_getblkcnt(bs), - sndbuf_getbps(bs), - sndbuf_getspd(bs), sndbuf_getfmt(bs)); - if (sndbuf_getsize(b) % sndbuf_getbps(b) || - sndbuf_getblksz(b) % sndbuf_getbps(b) || - sndbuf_getsize(bs) % sndbuf_getbps(bs) || - sndbuf_getblksz(b) % sndbuf_getbps(b)) { - printf("%u: bps/blksz alignment screwed!\n", kk); - } - } -#endif - return ret; -} - int chn_trigger(struct pcm_channel *c, int go) { #ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; #endif + struct snddev_info *d = c->parentsnddev; int ret; CHN_LOCKASSERT(c); @@ -1238,31 +1899,73 @@ if (SND_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)) sndbuf_dmabounce(b); #endif + if (!PCMTRIG_COMMON(go)) + return (CHANNEL_TRIGGER(c->methods, c->devinfo, go)); + + if (go == c->trigger) + return (0); + ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); + if (ret != 0) + return (ret); - return ret; + switch (go) { + case PCMTRIG_START: + if (snd_verbose > 3) + device_printf(c->dev, + "%s() %s: calling go=0x%08x , " + "prev=0x%08x\n", __func__, c->name, go, + c->trigger); + if (c->trigger != PCMTRIG_START) { + c->trigger = go; + CHN_UNLOCK(c); + pcm_lock(d); + CHN_INSERT_HEAD(d, c, channels.pcm.busy); + pcm_unlock(d); + CHN_LOCK(c); + } + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + if (snd_verbose > 3) + device_printf(c->dev, + "%s() %s: calling go=0x%08x , " + "prev=0x%08x\n", __func__, c->name, go, + c->trigger); + if (c->trigger == PCMTRIG_START) { + c->trigger = go; + CHN_UNLOCK(c); + pcm_lock(d); + CHN_REMOVE(d, c, channels.pcm.busy); + pcm_unlock(d); + CHN_LOCK(c); + } + break; + default: + break; + } + + return (0); } +/** + * @brief Queries sound driver for sample-aligned hardware buffer pointer index + * + * This function obtains the hardware pointer location, then aligns it to + * the current bytes-per-sample value before returning. (E.g., a channel + * running in 16 bit stereo mode would require 4 bytes per sample, so a + * hwptr value ranging from 32-35 would be returned as 32.) + * + * @param c PCM channel context + * @returns sample-aligned hardware buffer pointer index + */ int chn_getptr(struct pcm_channel *c) { -#if 0 int hwptr; - int a = (1 << c->align) - 1; CHN_LOCKASSERT(c); - hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; - /* don't allow unaligned values in the hwa ptr */ -#if 1 - hwptr &= ~a ; /* Apply channel align mask */ -#endif - hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ - return hwptr; -#endif - int hwptr; - - CHN_LOCKASSERT(c); - hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; + hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; return (hwptr - (hwptr % sndbuf_getbps(c->bufhard))); } @@ -1297,18 +2000,26 @@ static int chn_buildfeeder(struct pcm_channel *c) { + struct snddev_info *d; struct feeder_class *fc; struct pcm_feederdesc desc; - u_int32_t tmp[2], type, flags, hwfmt, *fmtlist; - int err; + struct pcm_feeder *f; + struct snd_mixer *m; + u_int32_t tmp[2], type, flags, hwfmt, parent, *fmtlist; + int err, vol, left, right; + char fmtstr[AFMTSTR_MAXSZ]; CHN_LOCKASSERT(c); - while (chn_removefeeder(c) == 0); + while (chn_removefeeder(c) == 0) + ; KASSERT((c->feeder == NULL), ("feeder chain not empty")); c->align = sndbuf_getalign(c->bufsoft); - if (SLIST_EMPTY(&c->children)) { + if (CHN_EMPTY(c, children) || c->direction == PCMDIR_REC) { + /* + * Virtual rec need this. + */ fc = feeder_getclass(NULL); KASSERT(fc != NULL, ("can't find root feeder")); @@ -1319,10 +2030,10 @@ return err; } c->feeder->desc->out = c->format; - } else { + } else if (c->direction == PCMDIR_PLAY) { if (c->flags & CHN_F_HAS_VCHAN) { desc.type = FEEDER_MIXER; - desc.in = 0; + desc.in = c->format; } else { DEB(printf("can't decide which feeder type to use!\n")); return EOPNOTSUPP; @@ -1342,25 +2053,81 @@ return err; } - } + } else + return EOPNOTSUPP; + + d = c->parentsnddev; + + if (c->direction == PCMDIR_PLAY && (d->flags & SD_F_SOFTPCMVOL) && + d->mixer_dev != NULL) + m = d->mixer_dev->si_drv1; + else + m = NULL; + c->feederflags &= ~(1 << FEEDER_VOLUME); - if (c->direction == PCMDIR_PLAY && - !(c->flags & CHN_F_VIRTUAL) && - c->parentsnddev && (c->parentsnddev->flags & SD_F_SOFTPCMVOL) && - c->parentsnddev->mixer_dev) + if (((d->flags & SD_F_VPC) && CHN_EMPTY(c, children)) || + (!(d->flags & SD_F_VPC) && (d->flags & SD_F_SOFTPCMVOL) && + !(c->flags & CHN_F_VIRTUAL))) c->feederflags |= 1 << FEEDER_VOLUME; + + c->feederflags &= ~(1 << FEEDER_SWAPLR); + if (!(c->flags & CHN_F_VIRTUAL) && ((c->direction == PCMDIR_PLAY && + (d->flags & SD_F_PSWAPLR)) || (c->direction == PCMDIR_REC && + (d->flags & SD_F_RSWAPLR)))) + c->feederflags |= 1 << FEEDER_SWAPLR; + flags = c->feederflags; + fmtlist = chn_getcaps(c)->fmtlist; DEB(printf("feederflags %x\n", flags)); - for (type = FEEDER_RATE; type <= FEEDER_LAST; type++) { + for (type = FEEDER_RATE; type < FEEDER_LAST; type++) { if (flags & (1 << type)) { desc.type = type; desc.in = 0; desc.out = 0; desc.flags = 0; DEB(printf("find feeder type %d, ", type)); + if (type == FEEDER_VOLUME || type == FEEDER_RATE) { + if (c->feeder->desc->out & AFMT_32BIT) + strlcpy(fmtstr,"s32le", + sizeof(fmtstr)); + else if (c->feeder->desc->out & AFMT_24BIT) + strlcpy(fmtstr, "s24le", + sizeof(fmtstr)); + else { + /* + * 8bit doesn't provide enough headroom + * for proper processing without + * creating too much noises. Force to + * 16bit instead. + */ + strlcpy(fmtstr, "s16le", + sizeof(fmtstr)); + } + if (!(c->feeder->desc->out & AFMT_8BIT) && + c->feeder->desc->out & AFMT_BIGENDIAN) + afmtstr_swap_endian(fmtstr); + if (!(c->feeder->desc->out & AFMT_SIGNED)) + afmtstr_swap_sign(fmtstr); + desc.in = afmtstr2afmt(NULL, fmtstr, + AFMTSTR_MONO_RETURN); + if (desc.in == 0) + desc.in = AFMT_S16_LE; + /* feeder_volume need stereo processing */ + if (type == FEEDER_VOLUME || + (c->feeder->desc->out & AFMT_STEREO) || + (type != FEEDER_VOLUME && + (flags & (1 << FEEDER_VOLUME)))) + desc.in |= AFMT_STEREO; + desc.out = desc.in; + } else if (type == FEEDER_SWAPLR) { + desc.in = c->feeder->desc->out; + desc.in |= AFMT_STEREO; + desc.out = desc.in; + } + fc = feeder_getclass(&desc); DEB(printf("got %p\n", fc)); if (fc == NULL) { @@ -1369,8 +2136,11 @@ return EOPNOTSUPP; } + if (desc.in == 0 || desc.out == 0) + desc = *fc->desc; + DEB(printf("build fmtchain from 0x%08x to 0x%08x: ", c->feeder->desc->out, fc->desc->in)); - tmp[0] = fc->desc->in; + tmp[0] = desc.in; tmp[1] = 0; if (chn_fmtchain(c, tmp) == 0) { DEB(printf("failed\n")); @@ -1379,7 +2149,7 @@ } DEB(printf("ok\n")); - err = chn_addfeeder(c, fc, fc->desc); + err = chn_addfeeder(c, fc, &desc); if (err) { DEB(printf("can't add feeder %p, output 0x%x, err %d\n", fc, fc->desc->out, err)); @@ -1399,126 +2169,181 @@ if (hwfmt == 0 || !fmtvalid(hwfmt, fmtlist)) { DEB(printf("Invalid hardware format: 0x%08x\n", hwfmt)); return ENODEV; + } else if (c->direction == PCMDIR_REC && !CHN_EMPTY(c, children)) { + /* + * Kind of awkward. This whole "MIXER" concept need a + * rethinking, I guess :) . Recording is the inverse + * of Playback, which is why we push mixer vchan down here. + */ + if (c->flags & CHN_F_HAS_VCHAN) { + desc.type = FEEDER_MIXER; + desc.in = c->format; + } else + return EOPNOTSUPP; + desc.out = c->format; + desc.flags = 0; + fc = feeder_getclass(&desc); + if (fc == NULL) + return EOPNOTSUPP; + + err = chn_addfeeder(c, fc, &desc); + if (err != 0) + return err; } sndbuf_setfmt(c->bufhard, hwfmt); - if ((flags & (1 << FEEDER_VOLUME))) { - u_int32_t parent = SOUND_MIXER_NONE; - int vol, left, right; - - vol = 100 | (100 << 8); - - CHN_UNLOCK(c); - /* - * XXX This is ugly! The way mixer subs being so secretive - * about its own internals force us to use this silly - * monkey trick. - */ - if (mixer_ioctl(c->parentsnddev->mixer_dev, - MIXER_READ(SOUND_MIXER_PCM), (caddr_t)&vol, -1, NULL) != 0) - device_printf(c->dev, "Soft PCM Volume: Failed to read default value\n"); - left = vol & 0x7f; - right = (vol >> 8) & 0x7f; - if (c->parentsnddev != NULL && - c->parentsnddev->mixer_dev != NULL && - c->parentsnddev->mixer_dev->si_drv1 != NULL) - parent = mix_getparent( - c->parentsnddev->mixer_dev->si_drv1, - SOUND_MIXER_PCM); - if (parent != SOUND_MIXER_NONE) { - vol = 100 | (100 << 8); - if (mixer_ioctl(c->parentsnddev->mixer_dev, - MIXER_READ(parent), - (caddr_t)&vol, -1, NULL) != 0) - device_printf(c->dev, "Soft Volume: Failed to read parent default value\n"); - left = (left * (vol & 0x7f)) / 100; - right = (right * ((vol >> 8) & 0x7f)) / 100; + if (flags & (1 << FEEDER_VOLUME)) { + if (m != NULL) { + CHN_UNLOCK(c); + vol = mix_get(m, SOUND_MIXER_PCM); + if (vol == -1) { + device_printf(c->dev, + "Soft PCM Volume: Failed to read " + "default value\n"); + vol = 100 | (100 << 8); + } + left = vol & 0x7f; + right = (vol >> 8) & 0x7f; + parent = mix_getparent(m, SOUND_MIXER_PCM); + if (parent != SOUND_MIXER_NONE) { + vol = mix_get(m, parent); + if (vol == -1) { + device_printf(c->dev, + "Soft Volume: Failed to read " + "parent default value\n"); + vol = 100 | (100 << 8); + } + left = (left * (vol & 0x7f)) / 100; + right = (right * ((vol >> 8) & 0x7f)) / 100; + } + CHN_LOCK(c); + CHN_SETVOLUME(c, SND_VOL_C_MASTER, SND_CHN_T_FL, left); + CHN_SETVOLUME(c, SND_VOL_C_MASTER, SND_CHN_T_FR, right); } - CHN_LOCK(c); - chn_setvolume(c, left, right); + f = chn_findfeeder(c, FEEDER_VOLUME); + if (f != NULL) + FEEDER_SET(f, FEEDVOL_CLASS, SND_VOL_C_PCM); } - return 0; + return (0); } int chn_notify(struct pcm_channel *c, u_int32_t flags) { - struct pcmchan_children *pce; - struct pcm_channel *child; - int run; + int err, run, nrun; - CHN_LOCK(c); + CHN_LOCKASSERT(c); - if (SLIST_EMPTY(&c->children)) { - CHN_UNLOCK(c); - return ENODEV; - } + if (CHN_EMPTY(c, children)) + return (ENODEV); + + err = 0; - run = (c->flags & CHN_F_TRIGGERED)? 1 : 0; /* - * if the hwchan is running, we can't change its rate, format or + * If the hwchan is running, we can't change its rate, format or * blocksize */ + run = (CHN_STARTED(c)) ? 1 : 0; if (run) flags &= CHN_N_VOLUME | CHN_N_TRIGGER; if (flags & CHN_N_RATE) { - /* - * we could do something here, like scan children and decide on - * the most appropriate rate to mix at, but we don't for now - */ + /* XXX I'll make good use of this someday. */ } if (flags & CHN_N_FORMAT) { - /* - * we could do something here, like scan children and decide on - * the most appropriate mixer feeder to use, but we don't for now - */ + /* XXX I'll make good use of this someday. */ } if (flags & CHN_N_VOLUME) { - /* - * we could do something here but we don't for now - */ + /* XXX I'll make good use of this someday. */ } if (flags & CHN_N_BLOCKSIZE) { - int blksz; /* - * scan the children, find the lowest blocksize and use that - * for the hard blocksize + * Set to default latency profile */ - blksz = sndbuf_getmaxsize(c->bufhard) / 2; - SLIST_FOREACH(pce, &c->children, link) { - child = pce->channel; - CHN_LOCK(child); - if (sndbuf_getblksz(child->bufhard) < blksz) - blksz = sndbuf_getblksz(child->bufhard); - CHN_UNLOCK(child); - } - chn_setblocksize(c, 2, blksz); + chn_setlatency(c, chn_latency); } if (flags & CHN_N_TRIGGER) { - int nrun; - /* - * scan the children, and figure out if any are running - * if so, we need to be running, otherwise we need to be stopped - * if we aren't in our target sstate, move to it - */ - nrun = 0; - SLIST_FOREACH(pce, &c->children, link) { - child = pce->channel; - CHN_LOCK(child); - if (child->flags & CHN_F_TRIGGERED) - nrun = 1; - CHN_UNLOCK(child); - } + nrun = CHN_EMPTY(c, children.busy) ? 0 : 1; if (nrun && !run) - chn_start(c, 1); + err = chn_start(c, 1); if (!nrun && run) chn_abort(c); } - CHN_UNLOCK(c); - return 0; + + return (err); +} + +/** + * @brief Fetch array of supported discrete sample rates + * + * Wrapper for CHANNEL_GETRATES. Please see channel_if.m:getrates() for + * detailed information. + * + * @note If the operation isn't supported, this function will just return 0 + * (no rates in the array), and *rates will be set to NULL. Callers + * should examine rates @b only if this function returns non-zero. + * + * @param c pcm channel to examine + * @param rates pointer to array of integers; rate table will be recorded here + * + * @return number of rates in the array pointed to be @c rates + */ +int +chn_getrates(struct pcm_channel *c, int **rates) +{ + KASSERT(rates != NULL, ("rates is null")); + CHN_LOCKASSERT(c); + return CHANNEL_GETRATES(c->methods, c->devinfo, rates); +} + +/** + * @brief Remove channel from a sync group, if there is one. + * + * This function is initially intended for the following conditions: + * - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl) + * - Closing a device. (A channel can't be destroyed if it's still in use.) + * + * @note Before calling this function, the syncgroup list mutex must be + * held. (Consider pcm_channel::sm protected by the SG list mutex + * whether @c c is locked or not.) + * + * @param c channel device to be started or closed + * @returns If this channel was the only member of a group, the group ID + * is returned to the caller so that the caller can release it + * via free_unr() after giving up the syncgroup lock. Else it + * returns 0. + */ +int +chn_syncdestroy(struct pcm_channel *c) +{ + struct pcmchan_syncmember *sm; + struct pcmchan_syncgroup *sg; + int sg_id; + + sg_id = 0; + + PCM_SG_LOCKASSERT(MA_OWNED); + + if (c->sm != NULL) { + sm = c->sm; + sg = sm->parent; + c->sm = NULL; + + KASSERT(sg != NULL, ("syncmember has null parent")); + + SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link); + free(sm, M_DEVBUF); + + if (SLIST_EMPTY(&sg->members)) { + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + sg_id = sg->id; + free(sg, M_DEVBUF); + } + } + + return sg_id; } void @@ -1532,3 +2357,12 @@ { CHN_UNLOCK(c); } + +#ifdef OSSV4_EXPERIMENT +int +chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak) +{ + CHN_LOCKASSERT(c); + return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak); +} +#endif --- sys/dev/sound/pcm/channel.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/channel.h Thu Jul 12 12:04:19 2007 @@ -23,13 +23,10 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/channel.h,v 1.31.2.1 2005/12/30 19:55:54 netchild Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/channel.h,v 1.37 2007/06/16 03:37:28 ariff Exp $ */ -struct pcmchan_children { - SLIST_ENTRY(pcmchan_children) link; - struct pcm_channel *channel; -}; +#include struct pcmchan_caps { u_int32_t minspeed, maxspeed; @@ -37,17 +34,50 @@ u_int32_t caps; }; -#define CHN_NAMELEN 32 +/* Forward declarations */ +struct pcm_channel; +struct pcmchan_syncgroup; +struct pcmchan_syncmember; + +extern struct mtx snd_pcm_syncgroups_mtx; +extern SLIST_HEAD(pcm_synclist, pcmchan_syncgroup) snd_pcm_syncgroups; + +#define PCM_SG_LOCK() mtx_lock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_TRYLOCK() mtx_trylock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_UNLOCK() mtx_unlock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_LOCKASSERT(arg) mtx_assert(&snd_pcm_syncgroups_mtx, arg) + +/** + * @brief Specifies an audio device sync group + */ +struct pcmchan_syncgroup { + SLIST_ENTRY(pcmchan_syncgroup) link; + SLIST_HEAD(, pcmchan_syncmember) members; + int id; /**< Group identifier; set to address of group. */ +}; + +/** + * @brief Specifies a container for members of a sync group + */ +struct pcmchan_syncmember { + SLIST_ENTRY(pcmchan_syncmember) link; + struct pcmchan_syncgroup *parent; /**< group head */ + struct pcm_channel *ch; +}; + +#define CHN_NAMELEN 32 +#define CHN_COMM_UNUSED "" +#define CHN_COMM_UNKNOWN "" + struct pcm_channel { kobj_t methods; - int num; pid_t pid; int refcount; struct pcm_feeder *feeder; u_int32_t align; - int volume; + int latency; u_int32_t speed; u_int32_t format; u_int32_t flags; @@ -55,17 +85,159 @@ u_int32_t blocks; int direction; - unsigned int interrupts, xruns; + unsigned int interrupts, xruns, feedcount; + unsigned int timeout; struct snd_dbuf *bufhard, *bufsoft; struct snddev_info *parentsnddev; struct pcm_channel *parentchannel; void *devinfo; device_t dev; + int unit; char name[CHN_NAMELEN]; + char comm[MAXCOMLEN + 1]; struct mtx *lock; - SLIST_HEAD(, pcmchan_children) children; + int trigger; + /** + * For interrupt manipulations. + */ + struct cv intr_cv; + /** + * Increment,decrement this around operations that temporarily yield + * lock. + */ + unsigned int inprog; + /** + * Special channel operations should examine @c inprog after acquiring + * lock. If zero, operations may continue. Else, thread should + * wait on this cv for previous operation to finish. + */ + struct cv cv; + /** + * Low water mark for select()/poll(). + * + * This is initialized to the channel's fragment size, and will be + * overwritten if a new fragment size is set. Users may alter this + * value directly with the @c SNDCTL_DSP_LOW_WATER ioctl. + */ + unsigned int lw; + /** + * If part of a sync group, this will point to the syncmember + * container. + */ + struct pcmchan_syncmember *sm; +#ifdef OSSV4_EXPERIMENT + u_int16_t lpeak, rpeak; /**< Peak value from 0-32767. */ +#endif + + struct { + SLIST_HEAD(, pcm_channel) head; + SLIST_ENTRY(pcm_channel) link; + struct { + SLIST_HEAD(, pcm_channel) head; + SLIST_ENTRY(pcm_channel) link; + } busy; + } children; + + struct { + struct { + SLIST_ENTRY(pcm_channel) link; + struct { + SLIST_ENTRY(pcm_channel) link; + } busy; + struct { + SLIST_ENTRY(pcm_channel) link; + } opened; + } pcm; + } channels; + + int volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; + int matrix[SND_CHN_T_MAX + 1]; + + void *data1, *data2; }; +#define CHN_HEAD(x, y) &(x)->y.head +#define CHN_INIT(x, y) SLIST_INIT(CHN_HEAD(x, y)) +#define CHN_LINK(y) y.link +#define CHN_EMPTY(x, y) SLIST_EMPTY(CHN_HEAD(x, y)) +#define CHN_FIRST(x, y) SLIST_FIRST(CHN_HEAD(x, y)) + +#define CHN_FOREACH(x, y, z) \ + SLIST_FOREACH(x, CHN_HEAD(y, z), CHN_LINK(z)) + +#define CHN_FOREACH_SAFE(w, x, y, z) \ + SLIST_FOREACH_SAFE(w, CHN_HEAD(x, z), CHN_LINK(z), y) + +#define CHN_INSERT_HEAD(x, y, z) \ + SLIST_INSERT_HEAD(CHN_HEAD(x, z), y, CHN_LINK(z)) + +#define CHN_INSERT_AFTER(x, y, z) \ + SLIST_INSERT_AFTER(x, y, CHN_LINK(z)) + +#define CHN_REMOVE(x, y, z) \ + SLIST_REMOVE(CHN_HEAD(x, z), y, pcm_channel, CHN_LINK(z)) + +#define CHN_INSERT_HEAD_SAFE(x, y, z) do { \ + struct pcm_channel *t = NULL; \ + CHN_FOREACH(t, x, z) { \ + if (t == y) \ + break; \ + } \ + if (t != y) \ + CHN_INSERT_HEAD(x, y, z); \ +} while(0) + +#define CHN_INSERT_AFTER_SAFE(w, x, y, z) do { \ + struct pcm_channel *t = NULL; \ + CHN_FOREACH(t, w, z) { \ + if (t == y) \ + break; \ + } \ + if (t != y) \ + CHN_INSERT_AFTER(x, y, z); \ +} while(0) + +#define CHN_REMOVE_SAFE(x, y, z) do { \ + struct pcm_channel *t = NULL; \ + CHN_FOREACH(t, x, z) { \ + if (t == y) \ + break; \ + } \ + if (t == y) \ + CHN_REMOVE(x, y, z); \ +} while(0) + +#define CHN_INSERT_SORT(w, x, y, z) do { \ + struct pcm_channel *t, *a = NULL; \ + CHN_FOREACH(t, x, z) { \ + if ((y)->unit w t->unit) \ + a = t; \ + else \ + break; \ + } \ + if (a != NULL) \ + CHN_INSERT_AFTER(a, y, z); \ + else \ + CHN_INSERT_HEAD(x, y, z); \ +} while(0) + +#define CHN_INSERT_SORT_ASCEND(x, y, z) CHN_INSERT_SORT(>, x, y, z) +#define CHN_INSERT_SORT_DESCEND(x, y, z) CHN_INSERT_SORT(<, x, y, z) + +#define CHN_UNIT(x) (snd_unit2u((x)->unit)) +#define CHN_DEV(x) (snd_unit2d((x)->unit)) +#define CHN_CHAN(x) (snd_unit2c((x)->unit)) + +#define CHN_BUF_PARENT(x, y) \ + (((x) != NULL && (x)->parentchannel != NULL && \ + (x)->parentchannel->bufhard != NULL) ? \ + (x)->parentchannel->bufhard : (y)) + +#define CHN_BROADCAST(x) do { \ + if ((x)->cv_waiters != 0) \ + cv_broadcastpri(x, PRIBIO); \ +} while(0) + #include "channel_if.h" int chn_reinit(struct pcm_channel *c); @@ -81,9 +253,15 @@ int chn_setdir(struct pcm_channel *c, int dir); int chn_reset(struct pcm_channel *c, u_int32_t fmt); int chn_setvolume(struct pcm_channel *c, int left, int right); +int chn_setvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt, int val); +int chn_getvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt); +void chn_vpc_reset(struct pcm_channel *c, snd_volume_class_t vc, int force); int chn_setspeed(struct pcm_channel *c, int speed); int chn_setformat(struct pcm_channel *c, u_int32_t fmt); int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz); +int chn_setlatency(struct pcm_channel *c, int latency); int chn_trigger(struct pcm_channel *c, int go); int chn_getptr(struct pcm_channel *c); struct pcmchan_caps *chn_getcaps(struct pcm_channel *c); @@ -102,21 +280,63 @@ void chn_lock(struct pcm_channel *c); void chn_unlock(struct pcm_channel *c); +int chn_getrates(struct pcm_channel *c, int **rates); +int chn_syncdestroy(struct pcm_channel *c); + +#define CHN_SETVOLUME(x...) chn_setvolume_matrix(x) +#if defined(SND_DIAGNOSTIC) || defined(INVARIANTS) +#define CHN_GETVOLUME(x...) chn_getvolume_matrix(x) +#else +#define CHN_GETVOLUME(x, y, z) ((x)->volume[y][z]) +#endif + +#ifdef OSSV4_EXPERIMENT +int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); +#endif + #ifdef USING_MUTEX #define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock)) #define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock)) +#define CHN_TRYLOCK(c) mtx_trylock((struct mtx *)((c)->lock)) #define CHN_LOCKASSERT(c) mtx_assert((struct mtx *)((c)->lock), MA_OWNED) #else #define CHN_LOCK(c) #define CHN_UNLOCK(c) +#define CHN_TRYLOCK(c) #define CHN_LOCKASSERT(c) #endif int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); -#define PCMDIR_VIRTUAL 2 -#define PCMDIR_PLAY 1 -#define PCMDIR_REC -1 +#define AFMTSTR_NONE 0 /* "s16le" */ +#define AFMTSTR_SIMPLE 1 /* "s16le:s" */ +#define AFMTSTR_NUM 2 /* "s16le:2" */ +#define AFMTSTR_FULL 3 /* "s16le:stereo" */ + +#define AFMTSTR_MAXSZ 13 /* include null terminator */ + +#define AFMTSTR_MONO_RETURN 0 +#define AFMTSTR_STEREO_RETURN 1 + +struct afmtstr_table { + char *fmtstr; + u_int32_t format; +}; + +int afmtstr_swap_sign(char *); +int afmtstr_swap_endian(char *); +u_int32_t afmtstr2afmt(struct afmtstr_table *, const char *, int); +u_int32_t afmt2afmtstr(struct afmtstr_table *, u_int32_t, char *, size_t, int, int); + +extern int chn_latency; +extern int chn_latency_profile; +extern int report_soft_formats; + +#define PCMDIR_FAKE 0 +#define PCMDIR_PLAY 1 +#define PCMDIR_PLAY_VIRTUAL 2 +#define PCMDIR_REC -1 +#define PCMDIR_REC_VIRTUAL -2 #define PCMTRIG_START 1 #define PCMTRIG_EMLDMAWR 2 @@ -124,11 +344,16 @@ #define PCMTRIG_STOP 0 #define PCMTRIG_ABORT -1 +#define PCMTRIG_COMMON(x) ((x) == PCMTRIG_START || \ + (x) == PCMTRIG_STOP || \ + (x) == PCMTRIG_ABORT) + #define CHN_F_CLOSING 0x00000004 /* a pending close */ #define CHN_F_ABORTING 0x00000008 /* a pending abort */ #define CHN_F_RUNNING 0x00000010 /* dma is running */ #define CHN_F_TRIGGERED 0x00000020 #define CHN_F_NOTRIGGER 0x00000040 +#define CHN_F_SLEEPING 0x00000080 #define CHN_F_BUSY 0x00001000 /* has been opened */ #define CHN_F_HAS_SIZE 0x00002000 /* user set block size */ @@ -141,8 +366,11 @@ #define CHN_F_VIRTUAL 0x10000000 /* not backed by hardware */ -#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | \ - CHN_F_HAS_VCHAN | CHN_F_VIRTUAL) +#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | \ + CHN_F_HAS_VCHAN | CHN_F_VIRTUAL) + +#define CHN_F_MMAP_INVALID (CHN_F_DEAD | CHN_F_RUNNING) + #define CHN_N_RATE 0x00000001 @@ -150,6 +378,26 @@ #define CHN_N_VOLUME 0x00000004 #define CHN_N_BLOCKSIZE 0x00000008 #define CHN_N_TRIGGER 0x00000010 + +#define CHN_LATENCY_MIN 0 +#define CHN_LATENCY_MAX 10 +#define CHN_LATENCY_DEFAULT 5 +#define CHN_POLICY_MIN CHN_LATENCY_MIN +#define CHN_POLICY_MAX CHN_LATENCY_MAX +#define CHN_POLICY_DEFAULT CHN_LATENCY_DEFAULT + +#define CHN_LATENCY_PROFILE_MIN 0 +#define CHN_LATENCY_PROFILE_MAX 1 +#define CHN_LATENCY_PROFILE_DEFAULT CHN_LATENCY_PROFILE_MAX + +#define CHN_STARTED(c) ((c)->flags & CHN_F_TRIGGERED) +#define CHN_STOPPED(c) (!CHN_STARTED(c)) +#define CHN_DIRSTR(c) (((c)->direction == PCMDIR_PLAY) ? \ + "PCMDIR_PLAY" : "PCMDIR_REC") + +#define CHN_TIMEOUT 5 +#define CHN_TIMEOUT_MIN 1 +#define CHN_TIMEOUT_MAX 10 /* * This should be large enough to hold all pcm data between --- sys/dev/sound/pcm/channel_if.m.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/channel_if.m Thu Jul 12 12:04:19 2007 @@ -25,7 +25,7 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $FreeBSD: src/sys/dev/sound/pcm/channel_if.m,v 1.5 2005/01/06 01:43:20 imp Exp $ +# $FreeBSD: src/sys/dev/sound/pcm/channel_if.m,v 1.7 2007/03/16 17:16:24 ariff Exp $ # #include @@ -70,6 +70,25 @@ return 0; } + static int + channel_nogetpeaks(kobj_t obj, void *data, int *lpeak, int *rpeak) + { + return -1; + } + + static int + channel_nogetrates(kobj_t obj, void *data, int **rates) + { + *rates = NULL; + return 0; + } + + static int + channel_nosetfragments(kobj_t obj, void *data, u_int32_t blocksize, u_int32_t blockcount) + { + return 0; + } + }; METHOD void* init { @@ -119,6 +138,13 @@ u_int32_t blocksize; }; +METHOD int setfragments { + kobj_t obj; + void *data; + u_int32_t blocksize; + u_int32_t blockcount; +} DEFAULT channel_nosetfragments; + METHOD int trigger { kobj_t obj; void *data; @@ -140,3 +166,54 @@ void *data; u_int32_t changed; } DEFAULT channel_nonotify; + +/** + * @brief Retrieve channel peak values + * + * This function is intended to obtain peak volume values for samples + * played/recorded on a channel. Values are on a linear scale from 0 to + * 32767. If the channel is monaural, a single value should be recorded + * in @c lpeak. + * + * If hardware support isn't available, the SNDCTL_DSP_GET[IO]PEAKS + * operation should return EINVAL. However, we may opt to provide + * software support that the user may toggle via sysctl/mixext. + * + * @param obj standard kobj object (usually @c channel->methods) + * @param data driver-specific data (usually @c channel->devinfo) + * @param lpeak pointer to store left peak level + * @param rpeak pointer to store right peak level + * + * @retval -1 Error; usually operation isn't supported. + * @retval 0 success + */ +METHOD int getpeaks { + kobj_t obj; + void *data; + int *lpeak; + int *rpeak; +} DEFAULT channel_nogetpeaks; + +/** + * @brief Retrieve discrete supported sample rates + * + * Some cards operate at fixed rates, and this call is intended to retrieve + * those rates primarily for when in-kernel rate adjustment is undesirable + * (e.g., application wants direct DMA access after setting a channel to run + * "uncooked"). + * + * The parameter @c rates is a double pointer which will be reset to + * point to an array of supported sample rates. The number of elements + * in the array is returned to the caller. + * + * @param obj standard kobj object (usually @c channel->methods) + * @param data driver-specific data (usually @c channel->devinfo) + * @param rates rate array pointer + * + * @return Number of rates in the array + */ +METHOD int getrates { + kobj_t obj; + void *data; + int **rates; +} DEFAULT channel_nogetrates; --- sys/dev/sound/pcm/dsp.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/dsp.c Thu Jul 12 12:04:19 2007 @@ -24,17 +24,32 @@ * SUCH DAMAGE. */ -#include -#include - #include +#include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/dsp.c,v 1.80.2.7 2007/07/08 14:01:11 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/dsp.c,v 1.107 2007/07/04 12:33:11 ariff Exp $"); static int dsp_mmap_allow_prot_exec = 0; SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RW, &dsp_mmap_allow_prot_exec, 0, "linux mmap compatibility"); +struct dsp_cdevinfo { + struct pcm_channel *rdch, *wrch; + struct pcm_channel *volch; + int busy, simplex; + TAILQ_ENTRY(dsp_cdevinfo) link; +}; + +#define PCM_RDCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->rdch) +#define PCM_WRCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->wrch) +#define PCM_VOLCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->volch) +#define PCM_SIMPLEX(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->simplex) + +#define DSP_CDEVINFO_CACHESIZE 8 + +#define DSP_REGISTERED(x, y) (PCM_REGISTERED(x) && \ + (y) != NULL && (y)->si_drv1 != NULL) + #define OLDPCM_IOCTL static d_open_t dsp_open; @@ -47,7 +62,6 @@ struct cdevsw dsp_cdevsw = { .d_version = D_VERSION, - .d_flags = D_NEEDGIANT, .d_open = dsp_open, .d_close = dsp_close, .d_read = dsp_read, @@ -59,408 +73,1047 @@ }; #ifdef USING_DEVFS -static eventhandler_tag dsp_ehtag; +static eventhandler_tag dsp_ehtag = NULL; +static int dsp_umax = -1; +static int dsp_cmax = -1; +#endif + +static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); +static int dsp_oss_syncstart(int sg_id); +static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy); +#ifdef OSSV4_EXPERIMENT +static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled); +static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); +static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); +static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); +static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); +static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); +static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); +static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif static struct snddev_info * dsp_get_info(struct cdev *dev) { - struct snddev_info *d; - int unit; - - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return NULL; - d = devclass_get_softc(pcm_devclass, unit); - - return d; + return (devclass_get_softc(pcm_devclass, PCMUNIT(dev))); } -static u_int32_t +static uint32_t dsp_get_flags(struct cdev *dev) { device_t bdev; - int unit; - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return 0xffffffff; - bdev = devclass_get_device(pcm_devclass, unit); + bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); - return pcm_getflags(bdev); + return ((bdev != NULL) ? pcm_getflags(bdev) : 0xffffffff); } static void -dsp_set_flags(struct cdev *dev, u_int32_t flags) +dsp_set_flags(struct cdev *dev, uint32_t flags) { device_t bdev; - int unit; - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return; - bdev = devclass_get_device(pcm_devclass, unit); + bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); - pcm_setflags(bdev, flags); + if (bdev != NULL) + pcm_setflags(bdev, flags); } /* * return the channels associated with an open device instance. - * set the priority if the device is simplex and one direction (only) is - * specified. * lock channels specified. */ static int -getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch, u_int32_t prio) +getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch, + uint32_t prio) { struct snddev_info *d; - u_int32_t flags; - - flags = dsp_get_flags(dev); - d = dsp_get_info(dev); - pcm_inprog(d, 1); - pcm_lock(d); - KASSERT((flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \ - ("getchns: read and write both prioritised")); + struct pcm_channel *ch; + uint32_t flags; - if ((flags & SD_F_PRIO_SET) == 0 && (prio != (SD_F_PRIO_RD | SD_F_PRIO_WR))) { - flags |= prio & (SD_F_PRIO_RD | SD_F_PRIO_WR); + if (PCM_SIMPLEX(dev) != 0) { + d = dsp_get_info(dev); + if (!PCM_REGISTERED(d)) + return (ENXIO); + pcm_lock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); + /* + * Note: order is important - + * pcm flags -> prio query flags -> wild guess + */ + ch = NULL; + flags = dsp_get_flags(dev); + if (flags & SD_F_PRIO_WR) { + ch = PCM_RDCH(dev); + PCM_RDCH(dev) = NULL; + } else if (flags & SD_F_PRIO_RD) { + ch = PCM_WRCH(dev); + PCM_WRCH(dev) = NULL; + } else if (prio & SD_F_PRIO_WR) { + ch = PCM_RDCH(dev); + PCM_RDCH(dev) = NULL; + flags |= SD_F_PRIO_WR; + } else if (prio & SD_F_PRIO_RD) { + ch = PCM_WRCH(dev); + PCM_WRCH(dev) = NULL; + flags |= SD_F_PRIO_RD; + } else if (PCM_WRCH(dev) != NULL) { + ch = PCM_RDCH(dev); + PCM_RDCH(dev) = NULL; + flags |= SD_F_PRIO_WR; + } else if (PCM_RDCH(dev) != NULL) { + ch = PCM_WRCH(dev); + PCM_WRCH(dev) = NULL; + flags |= SD_F_PRIO_RD; + } + PCM_SIMPLEX(dev) = 0; dsp_set_flags(dev, flags); - } - - *rdch = dev->si_drv1; - *wrch = dev->si_drv2; - if ((flags & SD_F_SIMPLEX) && (flags & SD_F_PRIO_SET)) { - if (prio) { - if (*rdch && flags & SD_F_PRIO_WR) { - dev->si_drv1 = NULL; - *rdch = pcm_getfakechan(d); - } else if (*wrch && flags & SD_F_PRIO_RD) { - dev->si_drv2 = NULL; - *wrch = pcm_getfakechan(d); - } + if (ch != NULL) { + CHN_LOCK(ch); + pcm_chnref(ch, -1); + pcm_chnrelease(ch); } - - pcm_getfakechan(d)->flags |= CHN_F_BUSY; + PCM_RELEASE(d); + pcm_unlock(d); } - pcm_unlock(d); - if (*rdch && *rdch != pcm_getfakechan(d) && (prio & SD_F_PRIO_RD)) + *rdch = PCM_RDCH(dev); + *wrch = PCM_WRCH(dev); + + if (*rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_LOCK(*rdch); - if (*wrch && *wrch != pcm_getfakechan(d) && (prio & SD_F_PRIO_WR)) + if (*wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_LOCK(*wrch); - return 0; + return (0); } /* unlock specified channels */ static void -relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, u_int32_t prio) +relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, + uint32_t prio) +{ + if (wrch != NULL && (prio & SD_F_PRIO_WR)) + CHN_UNLOCK(wrch); + if (rdch != NULL && (prio & SD_F_PRIO_RD)) + CHN_UNLOCK(rdch); +} + +static void +dsp_cdevinfo_alloc(struct cdev *dev, + struct pcm_channel *rdch, struct pcm_channel *wrch, + struct pcm_channel *volch) { struct snddev_info *d; + struct dsp_cdevinfo *cdi; + int simplex; d = dsp_get_info(dev); - if (wrch && wrch != pcm_getfakechan(d) && (prio & SD_F_PRIO_WR)) - CHN_UNLOCK(wrch); - if (rdch && rdch != pcm_getfakechan(d) && (prio & SD_F_PRIO_RD)) - CHN_UNLOCK(rdch); - pcm_inprog(d, -1); + + KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 == NULL && + ((rdch == NULL && wrch == NULL) || rdch != wrch), + ("bogus %s(), what are you trying to accomplish here?", __func__)); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_OWNED); + + simplex = (dsp_get_flags(dev) & SD_F_SIMPLEX) ? 1 : 0; + + /* + * Scan for free instance entry and put it into the end of list. + * Create new one if necessary. + */ + TAILQ_FOREACH(cdi, &d->dsp_cdevinfo_pool, link) { + if (cdi->busy != 0) + break; + cdi->rdch = rdch; + cdi->wrch = wrch; + cdi->volch = volch; + cdi->simplex = simplex; + cdi->busy = 1; + TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); + TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link); + dev->si_drv1 = cdi; + return; + } + pcm_unlock(d); + cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO); + pcm_lock(d); + cdi->rdch = rdch; + cdi->wrch = wrch; + cdi->volch = volch; + cdi->simplex = simplex; + cdi->busy = 1; + TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link); + dev->si_drv1 = cdi; +} + +static void +dsp_cdevinfo_free(struct cdev *dev) +{ + struct snddev_info *d; + struct dsp_cdevinfo *cdi, *tmp; + uint32_t flags; + int i; + + d = dsp_get_info(dev); + + KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 != NULL && + PCM_RDCH(dev) == NULL && PCM_WRCH(dev) == NULL && + PCM_VOLCH(dev) == NULL, + ("bogus %s(), what are you trying to accomplish here?", __func__)); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_OWNED); + + cdi = dev->si_drv1; + dev->si_drv1 = NULL; + cdi->rdch = NULL; + cdi->wrch = NULL; + cdi->volch = NULL; + cdi->simplex = 0; + cdi->busy = 0; + + /* + * Once it is free, move it back to the beginning of list for + * faster new entry allocation. + */ + TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); + TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link); + + /* + * Scan the list, cache free entries up to DSP_CDEVINFO_CACHESIZE. + * Reset simplex flags. + */ + flags = dsp_get_flags(dev) & ~SD_F_PRIO_SET; + i = DSP_CDEVINFO_CACHESIZE; + TAILQ_FOREACH_SAFE(cdi, &d->dsp_cdevinfo_pool, link, tmp) { + if (cdi->busy != 0) { + if (cdi->simplex == 0) { + if (cdi->rdch != NULL) + flags |= SD_F_PRIO_RD; + if (cdi->wrch != NULL) + flags |= SD_F_PRIO_WR; + } + } else { + if (i == 0) { + TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); + free(cdi, M_DEVBUF); + } else + i--; + } + } + dsp_set_flags(dev, flags); +} + +void +dsp_cdevinfo_init(struct snddev_info *d) +{ + struct dsp_cdevinfo *cdi; + int i; + + KASSERT(d != NULL, ("NULL snddev_info")); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_NOTOWNED); + + TAILQ_INIT(&d->dsp_cdevinfo_pool); + for (i = 0; i < DSP_CDEVINFO_CACHESIZE; i++) { + cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO); + TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link); + } +} + +void +dsp_cdevinfo_flush(struct snddev_info *d) +{ + struct dsp_cdevinfo *cdi, *tmp; + + KASSERT(d != NULL, ("NULL snddev_info")); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_NOTOWNED); + + cdi = TAILQ_FIRST(&d->dsp_cdevinfo_pool); + while (cdi != NULL) { + tmp = TAILQ_NEXT(cdi, link); + free(cdi, M_DEVBUF); + cdi = tmp; + } + TAILQ_INIT(&d->dsp_cdevinfo_pool); } +/* duplex / simplex cdev type */ +enum { + DSP_CDEV_TYPE_RDONLY, /* simplex read-only (record) */ + DSP_CDEV_TYPE_WRONLY, /* simplex write-only (play) */ + DSP_CDEV_TYPE_RDWR, /* duplex read, write, or both */ +}; + +enum { + DSP_CDEV_VOLCTL_NONE, + DSP_CDEV_VOLCTL_READ, + DSP_CDEV_VOLCTL_WRITE, +}; + +#define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) +#define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) +#define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) +#define DSP_F_READ(x) ((x) & FREAD) +#define DSP_F_WRITE(x) ((x) & FWRITE) + +static const struct { + int type; + char *name; + char *sep; + char *alias; + int use_sep; + int hw; + int max; + int volctl; + uint32_t fmt, spd; + int query; +} dsp_cdevs[] = { + { SND_DEV_DSP, "dsp", ".", NULL, 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_AUDIO, "audio", ".", NULL, 0, 0, 0, 0, + AFMT_MU_LAW, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP16, "dspW", ".", NULL, 0, 0, 0, 0, + AFMT_S16_LE, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSPHW_PLAY, "dsp", ".p", NULL, 1, 1, SND_MAXHWCHAN, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_WRONLY }, + { SND_DEV_DSPHW_VPLAY, "dsp", ".vp", NULL, 1, 1, SND_MAXVCHANS, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_WRONLY }, + { SND_DEV_DSPHW_REC, "dsp", ".r", NULL, 1, 1, SND_MAXHWCHAN, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_RDONLY }, + { SND_DEV_DSPHW_VREC, "dsp", ".vr", NULL, 1, 1, SND_MAXVCHANS, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_RDONLY }, + { SND_DEV_DSPHW_CD, "dspcd", ".", NULL, 0, 0, 0, 0, + AFMT_S16_LE | AFMT_STEREO, 44100, DSP_CDEV_TYPE_RDWR }, + /* Low priority, OSSv4 aliases. */ + { SND_DEV_DSP, "dsp_ac3", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_mmap", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_multich", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_spdifout", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_spdifin", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, +}; + +#define DSP_FIXUP_ERROR() do { \ + prio = dsp_get_flags(i_dev); \ + if (!DSP_F_VALID(flags)) \ + error = EINVAL; \ + if (!DSP_F_DUPLEX(flags) && \ + ((DSP_F_READ(flags) && d->reccount == 0) || \ + (DSP_F_WRITE(flags) && d->playcount == 0))) \ + error = ENOTSUP; \ + else if (!DSP_F_DUPLEX(flags) && (prio & SD_F_SIMPLEX) && \ + ((DSP_F_READ(flags) && (prio & SD_F_PRIO_WR)) || \ + (DSP_F_WRITE(flags) && (prio & SD_F_PRIO_RD)))) \ + error = EBUSY; \ + else if (DSP_REGISTERED(d, i_dev)) \ + error = EBUSY; \ +} while(0) + static int dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct pcm_channel *rdch, *wrch; struct snddev_info *d; - u_int32_t fmt; - int devtype; - int error; - int chnum; + uint32_t fmt, spd, prio, volctl; + int i, error, rderror, wrerror, devtype, wdevunit, rdevunit; + /* Kind of impossible.. */ if (i_dev == NULL || td == NULL) - return ENODEV; - - if ((flags & (FREAD | FWRITE)) == 0) - return EINVAL; + return (ENODEV); d = dsp_get_info(i_dev); - devtype = PCMDEV(i_dev); - chnum = -1; - - /* decide default format */ - switch (devtype) { - case SND_DEV_DSP16: - fmt = AFMT_S16_LE; - break; + if (!PCM_REGISTERED(d)) + return (EBADF); - case SND_DEV_DSP: - fmt = AFMT_U8; - break; + PCM_GIANT_ENTER(d); - case SND_DEV_AUDIO: - fmt = AFMT_MU_LAW; - break; + /* Lock snddev so nobody else can monkey with it. */ + pcm_lock(d); + PCM_WAIT(d); - case SND_DEV_NORESET: - fmt = 0; - break; + /* + * Try to acquire cloned device before someone else pick it. + * ENODEV means this is not a cloned droids. + */ + error = snd_clone_acquire(i_dev); + if (!(error == 0 || error == ENODEV)) { + DSP_FIXUP_ERROR(); + pcm_unlock(d); + PCM_GIANT_EXIT(d); + return (error); + } - case SND_DEV_DSPREC: - fmt = AFMT_U8; - if (flags & FWRITE) - return EINVAL; - chnum = PCMCHAN(i_dev); - break; + error = 0; + DSP_FIXUP_ERROR(); - default: - panic("impossible devtype %d", devtype); + if (error != 0) { + (void)snd_clone_release(i_dev); + pcm_unlock(d); + PCM_GIANT_EXIT(d); + return (error); } - /* lock snddev so nobody else can monkey with it */ - pcm_lock(d); - - rdch = i_dev->si_drv1; - wrch = i_dev->si_drv2; + /* + * That is just enough. Acquire and unlock pcm lock so + * the other will just have to wait until we finish doing + * everything. + */ + PCM_ACQUIRE(d); + pcm_unlock(d); - if (rdch || wrch || ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && - (flags & (FREAD | FWRITE)) == (FREAD | FWRITE))) { - /* simplex or not, better safe than sorry. */ - pcm_unlock(d); - return EBUSY; + devtype = PCMDEV(i_dev); + wdevunit = -1; + rdevunit = -1; + fmt = 0; + spd = 0; + volctl = DSP_CDEV_VOLCTL_NONE; + + for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + if (devtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) + continue; + /* + * Volume control only valid for DSPHW devices, + * and it must be opened in opposite direction be it + * simplex or duplex. Anything else will be handled + * as usual. + */ + if (dsp_cdevs[i].query == DSP_CDEV_TYPE_WRONLY) { + if (dsp_cdevs[i].volctl != 0 && + DSP_F_READ(flags)) { + volctl = DSP_CDEV_VOLCTL_WRITE; + flags &= ~FREAD; + flags |= FWRITE; + } + if (DSP_F_READ(flags)) { + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (ENOTSUP); + } + wdevunit = dev2unit(i_dev); + } else if (dsp_cdevs[i].query == DSP_CDEV_TYPE_RDONLY) { + if (dsp_cdevs[i].volctl != 0 && + DSP_F_WRITE(flags)) { + volctl = DSP_CDEV_VOLCTL_READ; + flags &= ~FWRITE; + flags |= FREAD; + } + if (DSP_F_WRITE(flags)) { + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (ENOTSUP); + } + rdevunit = dev2unit(i_dev); + } + fmt = dsp_cdevs[i].fmt; + spd = dsp_cdevs[i].spd; + break; } + /* No matching devtype? */ + if (fmt == 0 || spd == 0) + panic("impossible devtype %d", devtype); + + rdch = NULL; + wrch = NULL; + rderror = 0; + wrerror = 0; + /* * if we get here, the open request is valid- either: * * we were previously not open * * we were open for play xor record and the opener wants * the non-open direction */ - if (flags & FREAD) { + if (DSP_F_READ(flags)) { /* open for read */ - pcm_unlock(d); - error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, chnum); - if (error != 0 && error != EBUSY && chnum != -1 && (flags & FWRITE)) - error = pcm_chnalloc(d, &rdch, PCMDIR_REC, td->td_proc->p_pid, -1); - - if (error == 0 && (chn_reset(rdch, fmt) || - (fmt && chn_setspeed(rdch, DSP_DEFAULT_SPEED)))) - error = ENODEV; + rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC, + td->td_proc->p_pid, td->td_proc->p_comm, rdevunit); - if (error != 0) { - if (rdch) + if (rderror == 0 && (chn_reset(rdch, fmt) != 0 || + (chn_setspeed(rdch, spd) != 0))) + rderror = ENXIO; + + if (volctl == DSP_CDEV_VOLCTL_READ) + rderror = 0; + + if (rderror != 0) { + if (rdch != NULL) + pcm_chnrelease(rdch); + if (!DSP_F_DUPLEX(flags)) { + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (rderror); + } + rdch = NULL; + } else if (volctl == DSP_CDEV_VOLCTL_READ) { + if (rdch != NULL) { + pcm_chnref(rdch, 1); pcm_chnrelease(rdch); - return error; + } + } else { + if (flags & O_NONBLOCK) + rdch->flags |= CHN_F_NBIO; + pcm_chnref(rdch, 1); + if (volctl == DSP_CDEV_VOLCTL_NONE) + chn_vpc_reset(rdch, SND_VOL_C_PCM, 0); + CHN_UNLOCK(rdch); } + } - if (flags & O_NONBLOCK) - rdch->flags |= CHN_F_NBIO; - pcm_chnref(rdch, 1); - CHN_UNLOCK(rdch); - pcm_lock(d); + if (DSP_F_WRITE(flags)) { + /* open for write */ + wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, + td->td_proc->p_pid, td->td_proc->p_comm, wdevunit); + + if (wrerror == 0 && (chn_reset(wrch, fmt) != 0 || + (chn_setspeed(wrch, spd) != 0))) + wrerror = ENXIO; + + if (volctl == DSP_CDEV_VOLCTL_WRITE) + wrerror = 0; + + if (wrerror != 0) { + if (wrch != NULL) + pcm_chnrelease(wrch); + if (!DSP_F_DUPLEX(flags)) { + if (rdch != NULL) { + /* + * Lock, deref and release previously + * created record channel + */ + CHN_LOCK(rdch); + pcm_chnref(rdch, -1); + pcm_chnrelease(rdch); + } + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (wrerror); + } + wrch = NULL; + } else if (volctl == DSP_CDEV_VOLCTL_WRITE) { + if (wrch != NULL) { + pcm_chnref(wrch, 1); + pcm_chnrelease(wrch); + } + } else { + if (flags & O_NONBLOCK) + wrch->flags |= CHN_F_NBIO; + pcm_chnref(wrch, 1); + if (volctl == DSP_CDEV_VOLCTL_NONE) + chn_vpc_reset(wrch, SND_VOL_C_PCM, 0); + CHN_UNLOCK(wrch); + } } - if (flags & FWRITE) { - /* open for write */ - pcm_unlock(d); - error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, chnum); - if (error != 0 && error != EBUSY && chnum != -1 && (flags & FREAD)) - error = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, td->td_proc->p_pid, -1); - - if (error == 0 && (chn_reset(wrch, fmt) || - (fmt && chn_setspeed(wrch, DSP_DEFAULT_SPEED)))) - error = ENODEV; - if (error != 0) { - if (wrch) - pcm_chnrelease(wrch); - if (rdch) { - /* - * Lock, deref and release previously created record channel - */ - CHN_LOCK(rdch); - pcm_chnref(rdch, -1); - pcm_chnrelease(rdch); - } - - return error; - } - - if (flags & O_NONBLOCK) - wrch->flags |= CHN_F_NBIO; - pcm_chnref(wrch, 1); - CHN_UNLOCK(wrch); - pcm_lock(d); + pcm_lock(d); + + /* + * We're done. Allocate channels information for this cdev. + */ + switch (volctl) { + case DSP_CDEV_VOLCTL_READ: + KASSERT(wrch == NULL, ("wrch=%p not null!", wrch)); + dsp_cdevinfo_alloc(i_dev, NULL, NULL, rdch); + break; + case DSP_CDEV_VOLCTL_WRITE: + KASSERT(rdch == NULL, ("rdch=%p not null!", rdch)); + dsp_cdevinfo_alloc(i_dev, NULL, NULL, wrch); + break; + case DSP_CDEV_VOLCTL_NONE: + default: + if (wrch == NULL && rdch == NULL) { + (void)snd_clone_release(i_dev); + PCM_RELEASE(d); + pcm_unlock(d); + PCM_GIANT_EXIT(d); + if (wrerror != 0) + return (wrerror); + if (rderror != 0) + return (rderror); + return (EINVAL); + } + dsp_cdevinfo_alloc(i_dev, rdch, wrch, NULL); + if (rdch != NULL) + CHN_INSERT_HEAD(d, rdch, channels.pcm.opened); + if (wrch != NULL) + CHN_INSERT_HEAD(d, wrch, channels.pcm.opened); + break; } - i_dev->si_drv1 = rdch; - i_dev->si_drv2 = wrch; + /* + * Increase clone refcount for its automatic garbage collector. + */ + (void)snd_clone_ref(i_dev); + PCM_RELEASE(d); pcm_unlock(d); - return 0; + + PCM_GIANT_LEAVE(d); + + return (0); } static int dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { - struct pcm_channel *rdch, *wrch; + struct pcm_channel *rdch, *wrch, *volch; struct snddev_info *d; - int refs; + int sg_ids, rdref, wdref; d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); + + PCM_GIANT_ENTER(d); + pcm_lock(d); - rdch = i_dev->si_drv1; - wrch = i_dev->si_drv2; - pcm_unlock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); + + rdch = PCM_RDCH(i_dev); + wrch = PCM_WRCH(i_dev); + volch = PCM_VOLCH(i_dev); + + PCM_RDCH(i_dev) = NULL; + PCM_WRCH(i_dev) = NULL; + PCM_VOLCH(i_dev) = NULL; + + rdref = -1; + wdref = -1; + + if (volch != NULL) { + if (volch == rdch) + rdref--; + else if (volch == wrch) + wdref--; + else { + CHN_LOCK(volch); + pcm_chnref(volch, -1); + CHN_UNLOCK(volch); + } + } + + if (rdch != NULL) + CHN_REMOVE(d, rdch, channels.pcm.opened); + if (wrch != NULL) + CHN_REMOVE(d, wrch, channels.pcm.opened); + + if (rdch != NULL || wrch != NULL) { + pcm_unlock(d); + if (rdch != NULL) { + /* + * The channel itself need not be locked because: + * a) Adding a channel to a syncgroup happens only + * in dsp_ioctl(), which cannot run concurrently + * to dsp_close(). + * b) The syncmember pointer (sm) is protected by + * the global syncgroup list lock. + * c) A channel can't just disappear, invalidating + * pointers, unless it's closed/dereferenced + * first. + */ + PCM_SG_LOCK(); + sg_ids = chn_syncdestroy(rdch); + PCM_SG_UNLOCK(); + if (sg_ids != 0) + free_unr(pcmsg_unrhdr, sg_ids); - if (rdch || wrch) { - refs = 0; - if (rdch) { CHN_LOCK(rdch); - refs += pcm_chnref(rdch, -1); + pcm_chnref(rdch, rdref); chn_abort(rdch); /* won't sleep */ - rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD); + rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | + CHN_F_DEAD); chn_reset(rdch, 0); pcm_chnrelease(rdch); } - if (wrch) { - CHN_LOCK(wrch); - refs += pcm_chnref(wrch, -1); + if (wrch != NULL) { /* - * XXX: Maybe the right behaviour is to abort on non_block. - * It seems that mplayer flushes the audio queue by quickly - * closing and re-opening. In FBSD, there's a long pause - * while the audio queue flushes that I presume isn't there in - * linux. + * Please see block above. */ + PCM_SG_LOCK(); + sg_ids = chn_syncdestroy(wrch); + PCM_SG_UNLOCK(); + if (sg_ids != 0) + free_unr(pcmsg_unrhdr, sg_ids); + + CHN_LOCK(wrch); + pcm_chnref(wrch, wdref); chn_flush(wrch); /* may sleep */ - wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD); + wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | + CHN_F_DEAD); chn_reset(wrch, 0); pcm_chnrelease(wrch); } - pcm_lock(d); - if (rdch) - i_dev->si_drv1 = NULL; - if (wrch) - i_dev->si_drv2 = NULL; - /* - * If there are no more references, release the channels. - */ - if (refs == 0 && i_dev->si_drv1 == NULL && - i_dev->si_drv2 == NULL) { - if (pcm_getfakechan(d)) - pcm_getfakechan(d)->flags = 0; - /* What is this?!? */ - dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT); - } - pcm_unlock(d); } - return 0; + + dsp_cdevinfo_free(i_dev); + /* + * Release clone busy state and unref it so the automatic + * garbage collector will get the hint and do the remaining + * cleanup process. + */ + (void)snd_clone_release(i_dev); + (void)snd_clone_unref(i_dev); + + PCM_RELEASE(d); + pcm_unlock(d); + + PCM_GIANT_LEAVE(d); + + return (0); } -static int -dsp_read(struct cdev *i_dev, struct uio *buf, int flag) +static __inline int +dsp_io_ops(struct cdev *i_dev, struct uio *buf) { - struct pcm_channel *rdch, *wrch; - int ret; + struct snddev_info *d; + struct pcm_channel **ch, *rdch, *wrch; + int (*chn_io)(struct pcm_channel *, struct uio *); + int prio, ret; + pid_t runpid; + + KASSERT(i_dev != NULL && buf != NULL && + (buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE), + ("%s(): io train wreck!", __func__)); - getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD); + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); + + PCM_GIANT_ENTER(d); + + switch (buf->uio_rw) { + case UIO_READ: + prio = SD_F_PRIO_RD; + ch = &rdch; + chn_io = chn_read; + break; + case UIO_WRITE: + prio = SD_F_PRIO_WR; + ch = &wrch; + chn_io = chn_write; + break; + default: + panic("invalid/corrupted uio direction: %d", buf->uio_rw); + break; + } - KASSERT(rdch, ("dsp_read: nonexistant channel")); - KASSERT(rdch->flags & CHN_F_BUSY, ("dsp_read: nonbusy channel")); + rdch = NULL; + wrch = NULL; + runpid = buf->uio_td->td_proc->p_pid; + + getchns(i_dev, &rdch, &wrch, prio); + + if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) { + PCM_GIANT_EXIT(d); + return (EBADF); + } - if (rdch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { - relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); - return EINVAL; + if (((*ch)->flags & (CHN_F_MAPPED | CHN_F_DEAD)) || + (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) { + relchns(i_dev, rdch, wrch, prio); + PCM_GIANT_EXIT(d); + return (EINVAL); + } else if (!((*ch)->flags & CHN_F_RUNNING)) { + (*ch)->flags |= CHN_F_RUNNING; + (*ch)->pid = runpid; } - if (!(rdch->flags & CHN_F_RUNNING)) - rdch->flags |= CHN_F_RUNNING; - ret = chn_read(rdch, buf); - relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); - return ret; + /* + * chn_read/write must give up channel lock in order to copy bytes + * from/to userland, so up the "in progress" counter to make sure + * someone else doesn't come along and muss up the buffer. + */ + ++(*ch)->inprog; + ret = chn_io(*ch, buf); + --(*ch)->inprog; + + CHN_BROADCAST(&(*ch)->cv); + + relchns(i_dev, rdch, wrch, prio); + + PCM_GIANT_LEAVE(d); + + return (ret); +} + +static int +dsp_read(struct cdev *i_dev, struct uio *buf, int flag) +{ + return (dsp_io_ops(i_dev, buf)); } static int dsp_write(struct cdev *i_dev, struct uio *buf, int flag) { - struct pcm_channel *rdch, *wrch; - int ret; + return (dsp_io_ops(i_dev, buf)); +} + +static int +dsp_get_volume_channel(struct cdev *dev, struct pcm_channel **volch) +{ + struct snddev_info *d; + struct pcm_channel *c; + int unit; + + KASSERT(dev != NULL && volch != NULL, + ("%s(): NULL query dev=%p volch=%p", __func__, dev, volch)); + + d = dsp_get_info(dev); + if (!PCM_REGISTERED(d)) { + *volch = NULL; + return (EINVAL); + } + + mtx_assert(d->lock, MA_NOTOWNED); - getchns(i_dev, &rdch, &wrch, SD_F_PRIO_WR); + *volch = NULL; - KASSERT(wrch, ("dsp_write: nonexistant channel")); - KASSERT(wrch->flags & CHN_F_BUSY, ("dsp_write: nonbusy channel")); + c = PCM_VOLCH(dev); + if (c != NULL) { + if (!(c->feederflags & (1 << FEEDER_VOLUME))) + return (-1); + *volch = c; + return (0); + } + + pcm_lock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); + + unit = dev2unit(dev); - if (wrch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { - relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); - return EINVAL; + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->unit != unit) { + CHN_UNLOCK(c); + continue; + } + *volch = c; + pcm_chnref(c, 1); + PCM_VOLCH(dev) = c; + CHN_UNLOCK(c); + PCM_RELEASE(d); + pcm_unlock(d); + return ((c->feederflags & (1 << FEEDER_VOLUME)) ? 0 : -1); } - if (!(wrch->flags & CHN_F_RUNNING)) - wrch->flags |= CHN_F_RUNNING; - ret = chn_write(wrch, buf); - relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); - return ret; + PCM_RELEASE(d); + pcm_unlock(d); + + return (EINVAL); } static int -dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd, + caddr_t arg) { - struct pcm_channel *chn, *rdch, *wrch; struct snddev_info *d; - int kill; - int ret = 0, *arg_i = (int *)arg, tmp; + struct pcm_channel *rdch, *wrch; + int j, devtype, ret; - /* - * this is an evil hack to allow broken apps to perform mixer ioctls - * on dsp devices. - */ + d = dsp_get_info(dev); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_VPC)) + return (-1); - d = dsp_get_info(i_dev); - if (IOCGROUP(cmd) == 'M') { - /* - * This is at least, a bug to bug compatible with OSS. - */ - if (d->mixer_dev != NULL) - return mixer_ioctl(d->mixer_dev, cmd, arg, -1, td); - else - return EBADF; + mtx_assert(d->lock, MA_NOTOWNED); + + j = cmd & 0xff; + + rdch = PCM_RDCH(dev); + wrch = PCM_WRCH(dev); + + /* No specific channel, look into cache */ + if (volch == NULL) + volch = PCM_VOLCH(dev); + + /* Look harder */ + if (volch == NULL) { + if (j == SOUND_MIXER_RECLEV && rdch != NULL) + volch = rdch; + else if (j == SOUND_MIXER_PCM && wrch != NULL) + volch = wrch; } - getchns(i_dev, &rdch, &wrch, 0); + devtype = PCMDEV(dev); - kill = 0; - if (wrch && (wrch->flags & CHN_F_DEAD)) - kill |= 1; - if (rdch && (rdch->flags & CHN_F_DEAD)) - kill |= 2; - if (kill == 3) { - relchns(i_dev, rdch, wrch, 0); - return EINVAL; + /* Look super harder */ + if (volch == NULL && + (devtype == SND_DEV_DSPHW_PLAY || devtype == SND_DEV_DSPHW_VPLAY || + devtype == SND_DEV_DSPHW_REC || devtype == SND_DEV_DSPHW_VREC)) { + ret = dsp_get_volume_channel(dev, &volch); + if (ret != 0) + return (ret); + if (volch == NULL) + return (EINVAL); } - if (kill & 1) - wrch = NULL; - if (kill & 2) - rdch = NULL; - - switch(cmd) { -#ifdef OLDPCM_IOCTL - /* - * we start with the new ioctl interface. - */ - case AIONWRITE: /* how many bytes can write ? */ - if (wrch) { - CHN_LOCK(wrch); -/* - if (wrch && wrch->bufhard.dl) - while (chn_wrfeed(wrch) == 0); -*/ - *arg_i = sndbuf_getfree(wrch->bufsoft); - CHN_UNLOCK(wrch); - } else { - *arg_i = 0; - ret = EINVAL; + + /* Final validation */ + if (volch != NULL) { + CHN_LOCK(volch); + if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { + CHN_UNLOCK(volch); + return (-1); + } + if (volch->direction == PCMDIR_PLAY) + wrch = volch; + else + rdch = volch; + } + + ret = EINVAL; + + if (volch != NULL && + ((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) || + (j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) { + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { + CHN_SETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FL, + *(int *)arg & 0x7f); + CHN_SETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FR, + (*(int *)arg >> 8) & 0x7f); + } else if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { + *(int *)arg = CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FL); + *(int *)arg |= CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FR) << 8; + } + ret = 0; + } else if (rdch != NULL || wrch != NULL) { + switch (j) { + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_CAPS: + case SOUND_MIXER_STEREODEVS: + if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { + *(int *)arg = 0; + if (rdch != NULL) + *(int *)arg |= SOUND_MASK_RECLEV; + if (wrch != NULL) + *(int *)arg |= SOUND_MASK_PCM; + } + ret = 0; + break; + case SOUND_MIXER_RECMASK: + case SOUND_MIXER_RECSRC: + if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) + *(int *)arg = 0; + ret = 0; + break; + default: + break; + } + } + + if (volch != NULL) + CHN_UNLOCK(volch); + + return (ret); +} + +static int +dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + struct pcm_channel *chn, *rdch, *wrch; + struct snddev_info *d; + int *arg_i, ret, tmp, xcmd; + + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); + + PCM_GIANT_ENTER(d); + + arg_i = (int *)arg; + ret = 0; + xcmd = 0; + chn = NULL; + + if (IOCGROUP(cmd) == 'M') { + ret = dsp_ioctl_channel(i_dev, PCM_VOLCH(i_dev), cmd, arg); + if (ret != -1) { + PCM_GIANT_EXIT(d); + return (ret); + } + + if (d->mixer_dev != NULL) { + PCM_ACQUIRE_QUICK(d); + ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, + MIXER_CMD_DIRECT); + PCM_RELEASE_QUICK(d); + } else + ret = EBADF; + + PCM_GIANT_EXIT(d); + + return (ret); + } + + /* + * Certain ioctls may be made on any type of device (audio, mixer, + * and MIDI). Handle those special cases here. + */ + if (IOCGROUP(cmd) == 'X') { + PCM_ACQUIRE_QUICK(d); + switch(cmd) { + case SNDCTL_SYSINFO: + sound_oss_sysinfo((oss_sysinfo *)arg); + break; + case SNDCTL_AUDIOINFO: + ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg); + break; + case SNDCTL_MIXERINFO: + ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); + break; + default: + ret = EINVAL; + } + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (ret); + } + + getchns(i_dev, &rdch, &wrch, 0); + + if (wrch != NULL && (wrch->flags & CHN_F_DEAD)) + wrch = NULL; + if (rdch != NULL && (rdch->flags & CHN_F_DEAD)) + rdch = NULL; + + if (wrch == NULL && rdch == NULL) { + PCM_GIANT_EXIT(d); + return (EINVAL); + } + + switch(cmd) { +#ifdef OLDPCM_IOCTL + /* + * we start with the new ioctl interface. + */ + case AIONWRITE: /* how many bytes can write ? */ + if (wrch) { + CHN_LOCK(wrch); +/* + if (wrch && wrch->bufhard.dl) + while (chn_wrfeed(wrch) == 0); +*/ + *arg_i = sndbuf_getfree(wrch->bufsoft); + CHN_UNLOCK(wrch); + } else { + *arg_i = 0; + ret = EINVAL; } break; @@ -470,6 +1123,7 @@ p->play_size = 0; p->rec_size = 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, p->play_size); @@ -482,6 +1136,7 @@ p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); } break; case AIOGSIZE: /* get the current blocksize */ @@ -512,6 +1167,7 @@ ret = EINVAL; break; } + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); if (cmd == AIOSFMT && p->play_format != 0) { @@ -538,6 +1194,7 @@ p->rec_rate = 0; p->rec_format = 0; } + PCM_RELEASE_QUICK(d); } break; @@ -547,6 +1204,7 @@ struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; struct cdev *pdev; + pcm_lock(d); if (rdch) { CHN_LOCK(rdch); rcaps = chn_getcaps(rdch); @@ -570,10 +1228,11 @@ p->mixers = 1; /* default: one mixer */ p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0; p->left = p->right = 100; - if (rdch) - CHN_UNLOCK(rdch); if (wrch) CHN_UNLOCK(wrch); + if (rdch) + CHN_UNLOCK(rdch); + pcm_unlock(d); } break; @@ -618,11 +1277,11 @@ DEB( printf("FIOASYNC\n") ; ) break; - case SNDCTL_DSP_NONBLOCK: + case SNDCTL_DSP_NONBLOCK: /* set non-blocking i/o */ case FIONBIO: /* set/clear non-blocking i/o */ if (rdch) { CHN_LOCK(rdch); - if (*arg_i) + if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) rdch->flags |= CHN_F_NBIO; else rdch->flags &= ~CHN_F_NBIO; @@ -630,7 +1289,7 @@ } if (wrch) { CHN_LOCK(wrch); - if (*arg_i) + if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) wrch->flags |= CHN_F_NBIO; else wrch->flags &= ~CHN_F_NBIO; @@ -653,10 +1312,11 @@ *arg_i = 0; ret = EINVAL; } - break ; + break; case SNDCTL_DSP_SETBLKSIZE: RANGE(*arg_i, 16, 65536); + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, *arg_i); @@ -667,6 +1327,7 @@ chn_setblocksize(rdch, 2, *arg_i); CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_RESET: @@ -690,7 +1351,7 @@ /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); - chn_sync(wrch, sndbuf_getsize(wrch->bufsoft) - 4); + chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; @@ -698,6 +1359,7 @@ case SNDCTL_DSP_SPEED: /* chn_setspeed may sleep */ tmp = 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setspeed(wrch, *arg_i); @@ -711,6 +1373,7 @@ tmp = rdch->speed; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; break; @@ -729,6 +1392,7 @@ case SNDCTL_DSP_STEREO: tmp = -1; *arg_i = (*arg_i)? AFMT_STEREO : 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | *arg_i); @@ -742,6 +1406,7 @@ tmp = (rdch->format & AFMT_STEREO)? 1 : 0; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; break; @@ -750,6 +1415,7 @@ if (*arg_i != 0) { tmp = 0; *arg_i = (*arg_i != 1)? AFMT_STEREO : 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | *arg_i); @@ -763,6 +1429,7 @@ tmp = (rdch->format & AFMT_STEREO)? 2 : 1; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; @@ -794,11 +1461,12 @@ *arg_i = 0; ret = EINVAL; } - break ; + break; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ if ((*arg_i != AFMT_QUERY)) { tmp = 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, (*arg_i) | (wrch->format & AFMT_STEREO)); @@ -812,6 +1480,7 @@ tmp = rdch->format & ~AFMT_STEREO; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; @@ -824,10 +1493,10 @@ case SNDCTL_DSP_SETFRAGMENT: DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); { - u_int32_t fragln = (*arg_i) & 0x0000ffff; - u_int32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; - u_int32_t fragsz; - u_int32_t r_maxfrags, r_fragsz; + uint32_t fragln = (*arg_i) & 0x0000ffff; + uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; + uint32_t fragsz; + uint32_t r_maxfrags, r_fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; @@ -840,6 +1509,7 @@ maxfrags = CHN_2NDBUFMAXSIZE / fragsz; DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); + PCM_ACQUIRE_QUICK(d); if (rdch) { CHN_LOCK(rdch); ret = chn_setblocksize(rdch, maxfrags, fragsz); @@ -860,6 +1530,7 @@ maxfrags = r_maxfrags; fragsz = r_fragsz; } + PCM_RELEASE_QUICK(d); fragln = 0; while (fragsz > 1) { @@ -883,7 +1554,8 @@ a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(rdch); - } + } else + ret = EINVAL; } break; @@ -901,7 +1573,8 @@ a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(wrch); - } + } else + ret = EINVAL; } break; @@ -942,9 +1615,11 @@ break; case SNDCTL_DSP_GETCAPS: + pcm_lock(d); *arg_i = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; if (rdch && wrch && !(dsp_get_flags(i_dev) & SD_F_SIMPLEX)) *arg_i |= DSP_CAP_DUPLEX; + pcm_unlock(d); break; case SOUND_PCM_READ_BITS: @@ -1007,12 +1682,11 @@ case SNDCTL_DSP_GETODELAY: if (wrch) { - struct snd_dbuf *b = wrch->bufhard; struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); /* XXX abusive DMA update: chn_wrupdate(wrch); */ - *arg_i = sndbuf_getready(b) + sndbuf_getready(bs); + *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; @@ -1032,10 +1706,348 @@ * switch to full-duplex mode if card is in half-duplex * mode and is able to work in full-duplex mode */ + pcm_lock(d); if (rdch && wrch && (dsp_get_flags(i_dev) & SD_F_SIMPLEX)) dsp_set_flags(i_dev, dsp_get_flags(i_dev)^SD_F_SIMPLEX); + pcm_unlock(d); + break; + + /* + * The following four ioctls are simple wrappers around mixer_ioctl + * with no further processing. xcmd is short for "translated + * command". + */ + case SNDCTL_DSP_GETRECVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_READ_RECLEV; + chn = rdch; + } + /* FALLTHROUGH */ + case SNDCTL_DSP_SETRECVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_WRITE_RECLEV; + chn = rdch; + } + /* FALLTHROUGH */ + case SNDCTL_DSP_GETPLAYVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_READ_PCM; + chn = wrch; + } + /* FALLTHROUGH */ + case SNDCTL_DSP_SETPLAYVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_WRITE_PCM; + chn = wrch; + } + + ret = dsp_ioctl_channel(i_dev, chn, xcmd, arg); + if (ret != -1) { + PCM_GIANT_EXIT(d); + return (ret); + } + + if (d->mixer_dev != NULL) { + PCM_ACQUIRE_QUICK(d); + ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td, + MIXER_CMD_DIRECT); + PCM_RELEASE_QUICK(d); + } else + ret = ENOTSUP; + + break; + + case SNDCTL_DSP_GET_RECSRC_NAMES: + case SNDCTL_DSP_GET_RECSRC: + case SNDCTL_DSP_SET_RECSRC: + if (d->mixer_dev != NULL) { + PCM_ACQUIRE_QUICK(d); + ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, + MIXER_CMD_DIRECT); + PCM_RELEASE_QUICK(d); + } else + ret = ENOTSUP; + break; + + /* + * The following 3 ioctls aren't very useful at the moment. For + * now, only a single channel is associated with a cdev (/dev/dspN + * instance), so there's only a single output routing to use (i.e., + * the wrch bound to this cdev). + */ + case SNDCTL_DSP_GET_PLAYTGT_NAMES: + { + oss_mixer_enuminfo *ei; + ei = (oss_mixer_enuminfo *)arg; + ei->dev = 0; + ei->ctrl = 0; + ei->version = 0; /* static for now */ + ei->strindex[0] = 0; + + if (wrch != NULL) { + ei->nvalues = 1; + strlcpy(ei->strings, wrch->name, + sizeof(ei->strings)); + } else { + ei->nvalues = 0; + ei->strings[0] = '\0'; + } + } + break; + case SNDCTL_DSP_GET_PLAYTGT: + case SNDCTL_DSP_SET_PLAYTGT: /* yes, they are the same for now */ + /* + * Re: SET_PLAYTGT + * OSSv4: "The value that was accepted by the device will + * be returned back in the variable pointed by the + * argument." + */ + if (wrch != NULL) + *arg_i = 0; + else + ret = EINVAL; + break; + + case SNDCTL_DSP_SILENCE: + /* + * Flush the software (pre-feed) buffer, but try to minimize playback + * interruption. (I.e., record unplayed samples with intent to + * restore by SNDCTL_DSP_SKIP.) Intended for application "pause" + * functionality. + */ + if (wrch == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + CHN_LOCK(wrch); + while (wrch->inprog != 0) + cv_wait(&wrch->cv, wrch->lock); + bs = wrch->bufsoft; + if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) { + bs->sl = sndbuf_getready(bs); + sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs)); + sndbuf_fillsilence(bs); + chn_start(wrch, 0); + } + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_SKIP: + /* + * OSSv4 docs: "This ioctl call discards all unplayed samples in the + * playback buffer by moving the current write position immediately + * before the point where the device is currently reading the samples." + */ + if (wrch == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + CHN_LOCK(wrch); + while (wrch->inprog != 0) + cv_wait(&wrch->cv, wrch->lock); + bs = wrch->bufsoft; + if ((bs->shadbuf != NULL) && (bs->sl > 0)) { + sndbuf_softreset(bs); + sndbuf_acquire(bs, bs->shadbuf, bs->sl); + bs->sl = 0; + chn_start(wrch, 0); + } + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_CURRENT_OPTR: + case SNDCTL_DSP_CURRENT_IPTR: + /** + * @note Changing formats resets the buffer counters, which differs + * from the 4Front drivers. However, I don't expect this to be + * much of a problem. + * + * @note In a test where @c CURRENT_OPTR is called immediately after write + * returns, this driver is about 32K samples behind whereas + * 4Front's is about 8K samples behind. Should determine source + * of discrepancy, even if only out of curiosity. + * + * @todo Actually test SNDCTL_DSP_CURRENT_IPTR. + */ + chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + /* int tmp; */ + + oss_count_t *oc = (oss_count_t *)arg; + + CHN_LOCK(chn); + bs = chn->bufsoft; +#if 0 + tmp = (sndbuf_getsize(b) + chn_getptr(chn) - sndbuf_gethwptr(b)) % sndbuf_getsize(b); + oc->samples = (sndbuf_gettotal(b) + tmp) / sndbuf_getbps(b); + oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getbps(b); +#else + oc->samples = sndbuf_gettotal(bs) / sndbuf_getbps(bs); + oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getbps(bs); +#endif + CHN_UNLOCK(chn); + } + break; + + case SNDCTL_DSP_HALT_OUTPUT: + case SNDCTL_DSP_HALT_INPUT: + chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + CHN_LOCK(chn); + chn_abort(chn); + CHN_UNLOCK(chn); + } + break; + + case SNDCTL_DSP_LOW_WATER: + /* + * Set the number of bytes required to attract attention by + * select/poll. + */ + if (wrch != NULL) { + CHN_LOCK(wrch); + wrch->lw = (*arg_i > 1) ? *arg_i : 1; + CHN_UNLOCK(wrch); + } + if (rdch != NULL) { + CHN_LOCK(rdch); + rdch->lw = (*arg_i > 1) ? *arg_i : 1; + CHN_UNLOCK(rdch); + } + break; + + case SNDCTL_DSP_GETERROR: + /* + * OSSv4 docs: "All errors and counters will automatically be + * cleared to zeroes after the call so each call will return only + * the errors that occurred after the previous invocation. ... The + * play_underruns and rec_overrun fields are the only usefull fields + * returned by OSS 4.0." + */ + { + audio_errinfo *ei = (audio_errinfo *)arg; + + bzero((void *)ei, sizeof(*ei)); + + if (wrch != NULL) { + CHN_LOCK(wrch); + ei->play_underruns = wrch->xruns; + wrch->xruns = 0; + CHN_UNLOCK(wrch); + } + if (rdch != NULL) { + CHN_LOCK(rdch); + ei->rec_overruns = rdch->xruns; + rdch->xruns = 0; + CHN_UNLOCK(rdch); + } + } + break; + + case SNDCTL_DSP_SYNCGROUP: + PCM_ACQUIRE_QUICK(d); + ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg); + PCM_RELEASE_QUICK(d); + break; + + case SNDCTL_DSP_SYNCSTART: + PCM_ACQUIRE_QUICK(d); + ret = dsp_oss_syncstart(*arg_i); + PCM_RELEASE_QUICK(d); + break; + + case SNDCTL_DSP_POLICY: + PCM_ACQUIRE_QUICK(d); + ret = dsp_oss_policy(wrch, rdch, *arg_i); + PCM_RELEASE_QUICK(d); break; +#ifdef OSSV4_EXPERIMENT + /* + * XXX The following ioctls are not yet supported and just return + * EINVAL. + */ + case SNDCTL_DSP_GETOPEAKS: + case SNDCTL_DSP_GETIPEAKS: + chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + oss_peaks_t *op = (oss_peaks_t *)arg; + int lpeak, rpeak; + + CHN_LOCK(chn); + ret = chn_getpeaks(chn, &lpeak, &rpeak); + if (ret == -1) + ret = EINVAL; + else { + (*op)[0] = lpeak; + (*op)[1] = rpeak; + } + CHN_UNLOCK(chn); + } + break; + + /* + * XXX Once implemented, revisit this for proper cv protection + * (if necessary). + */ + case SNDCTL_DSP_COOKEDMODE: + ret = dsp_oss_cookedmode(wrch, rdch, *arg_i); + break; + case SNDCTL_DSP_GET_CHNORDER: + ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg); + break; + case SNDCTL_DSP_SET_CHNORDER: + ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg); + break; + case SNDCTL_GETLABEL: + ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg); + break; + case SNDCTL_SETLABEL: + ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg); + break; + case SNDCTL_GETSONG: + ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg); + break; + case SNDCTL_SETSONG: + ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg); + break; + case SNDCTL_SETNAME: + ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg); + break; +#if 0 + /** + * @note The SNDCTL_CARDINFO ioctl was omitted per 4Front developer + * documentation. "The usability of this call is very limited. It's + * provided only for completeness of the API. OSS API doesn't have + * any concept of card. Any information returned by this ioctl calld + * is reserved exclusively for the utility programs included in the + * OSS package. Applications should not try to use for this + * information in any ways." + */ + case SNDCTL_CARDINFO: + ret = EINVAL; + break; + /** + * @note The S/PDIF interface ioctls, @c SNDCTL_DSP_READCTL and + * @c SNDCTL_DSP_WRITECTL have been omitted at the suggestion of + * 4Front Technologies. + */ + case SNDCTL_DSP_READCTL: + case SNDCTL_DSP_WRITECTL: + ret = EINVAL; + break; +#endif /* !0 (explicitly omitted ioctls) */ + +#endif /* !OSSV4_EXPERIMENT */ case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: @@ -1051,38 +2063,55 @@ ret = EINVAL; break; } - relchns(i_dev, rdch, wrch, 0); - return ret; + + PCM_GIANT_LEAVE(d); + + return (ret); } static int dsp_poll(struct cdev *i_dev, int events, struct thread *td) { - struct pcm_channel *wrch = NULL, *rdch = NULL; + struct snddev_info *d; + struct pcm_channel *wrch, *rdch; int ret, e; + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); + + PCM_GIANT_ENTER(d); + + wrch = NULL; + rdch = NULL; ret = 0; + getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - if (wrch) { + if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) { e = (events & (POLLOUT | POLLWRNORM)); if (e) ret |= chn_poll(wrch, e, td); } - if (rdch) { + + if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) { e = (events & (POLLIN | POLLRDNORM)); if (e) ret |= chn_poll(rdch, e, td); } + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - return ret; + PCM_GIANT_LEAVE(d); + + return (ret); } static int dsp_mmap(struct cdev *i_dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) { - struct pcm_channel *wrch = NULL, *rdch = NULL, *c; + struct snddev_info *d; + struct pcm_channel *wrch, *rdch, *c; /* * Reject PROT_EXEC by default. It just doesn't makes sense. @@ -1093,129 +2122,1062 @@ * */ if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec == 0) - return -1; + return (-1); + + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (-1); + + PCM_GIANT_ENTER(d); getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); -#if 0 + /* - * XXX the linux api uses the nprot to select read/write buffer - * our vm system doesn't allow this, so force write buffer + * XXX The linux api uses the nprot to select read/write buffer + * our vm system doesn't allow this, so force write buffer. + * + * This is just a quack to fool full-duplex mmap, so that at + * least playback _or_ recording works. If you really got the + * urge to make _both_ work at the same time, avoid O_RDWR. + * Just open each direction separately and mmap() it. + * + * Failure is not an option due to INVARIANTS check within + * device_pager.c, which means, we have to give up one over + * another. */ + c = (wrch != NULL) ? wrch : rdch; - if (wrch && (nprot & PROT_WRITE)) { - c = wrch; - } else if (rdch && (nprot & PROT_READ)) { - c = rdch; - } else { - return -1; - } -#else - c = wrch; -#endif - - if (c == NULL) { - relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - return -1; - } - - if (offset >= sndbuf_getsize(c->bufsoft)) { + if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) || + offset >= sndbuf_getsize(c->bufsoft) || + (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) || + (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) { relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - return -1; + PCM_GIANT_EXIT(d); + return (-1); } - if (!(c->flags & CHN_F_MAPPED)) - c->flags |= CHN_F_MAPPED; + /* XXX full-duplex quack. */ + if (wrch != NULL) + wrch->flags |= CHN_F_MAPPED; + if (rdch != NULL) + rdch->flags |= CHN_F_MAPPED; *paddr = vtophys(sndbuf_getbufofs(c->bufsoft, offset)); relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - return 0; + PCM_GIANT_LEAVE(d); + + return (0); } #ifdef USING_DEVFS -/* - * Clone logic is this: - * x E X = {dsp, dspW, audio} - * x -> x${sysctl("hw.snd.unit")} - * xN-> - * for i N = 1 to channels of device N - * if xN.i isn't busy, return its dev_t - */ +/* So much for dev_stdclone() */ +static int +dsp_stdclone(char *name, char *namep, char *sep, int use_sep, int *u, int *c) +{ + size_t len; + + len = strlen(namep); + + if (bcmp(name, namep, len) != 0) + return (ENODEV); + + name += len; + + if (isdigit(*name) == 0) + return (ENODEV); + + len = strlen(sep); + + if (*name == '0' && !(name[1] == '\0' || bcmp(name + 1, sep, len) == 0)) + return (ENODEV); + + for (*u = 0; isdigit(*name) != 0; name++) { + *u *= 10; + *u += *name - '0'; + if (*u > dsp_umax) + return (ENODEV); + } + + if (*name == '\0') + return ((use_sep == 0) ? 0 : ENODEV); + + if (bcmp(name, sep, len) != 0 || isdigit(name[len]) == 0) + return (ENODEV); + + name += len; + + if (*name == '0' && name[1] != '\0') + return (ENODEV); + + for (*c = 0; isdigit(*name) != 0; name++) { + *c *= 10; + *c += *name - '0'; + if (*c > dsp_cmax) + return (ENODEV); + } + + if (*name != '\0') + return (ENODEV); + + return (0); +} + static void -dsp_clone(void *arg, struct ucred *cred, char *name, int namelen, - struct cdev **dev) +dsp_clone(void *arg, +#if __FreeBSD_version >= 600034 + struct ucred *cred, +#endif + char *name, int namelen, struct cdev **dev) { - struct cdev *pdev; - struct snddev_info *pcm_dev; - struct snddev_channel *pcm_chan; - int i, unit, devtype; - static int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO}; - static char *devnames[3] = {"dsp", "dspW", "audio"}; + struct snddev_info *d; + struct snd_clone_entry *ce; + struct pcm_channel *c; + int i, unit, udcmask, cunit, devtype, devhw, devcmax, tumax; + char *devname, *devcmp, *devsep; + + KASSERT(dsp_umax >= 0 && dsp_cmax >= 0, ("Uninitialized unit!")); if (*dev != NULL) return; - if (pcm_devclass == NULL) - return; - devtype = 0; unit = -1; - for (i = 0; (i < 3) && (unit == -1); i++) { - devtype = devtypes[i]; - if (strcmp(name, devnames[i]) == 0) { + cunit = -1; + devtype = -1; + devhw = 0; + devcmax = -1; + tumax = -1; + devname = NULL; + devsep = NULL; + + for (i = 0; unit == -1 && + i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + devtype = dsp_cdevs[i].type; + devcmp = dsp_cdevs[i].name; + devsep = dsp_cdevs[i].sep; + devname = dsp_cdevs[i].alias; + if (devname == NULL) + devname = devcmp; + devhw = dsp_cdevs[i].hw; + devcmax = dsp_cdevs[i].max - 1; + if (strcmp(name, devcmp) == 0) unit = snd_unit; - } else { - if (dev_stdclone(name, NULL, devnames[i], &unit) != 1) - unit = -1; + else if (dsp_stdclone(name, devcmp, devsep, + dsp_cdevs[i].use_sep, &unit, &cunit) != 0) { + unit = -1; + cunit = -1; } } - if (unit == -1 || unit >= devclass_get_maxunit(pcm_devclass)) + + d = devclass_get_softc(pcm_devclass, unit); + if (!PCM_REGISTERED(d) || d->clones == NULL) return; - pcm_dev = devclass_get_softc(pcm_devclass, unit); + /* XXX Need Giant magic entry ??? */ - if (pcm_dev == NULL) + pcm_lock(d); + if (snd_clone_disabled(d->clones)) { + pcm_unlock(d); return; + } - SLIST_FOREACH(pcm_chan, &pcm_dev->channels, link) { + PCM_WAIT(d); + PCM_ACQUIRE(d); + pcm_unlock(d); - switch(devtype) { - case SND_DEV_DSP: - pdev = pcm_chan->dsp_devt; - break; - case SND_DEV_DSP16: - pdev = pcm_chan->dspW_devt; - break; - case SND_DEV_AUDIO: - pdev = pcm_chan->audio_devt; - break; - default: - panic("Unknown devtype %d", devtype); - } + udcmask = snd_u2unit(unit) | snd_d2unit(devtype); - if ((pdev != NULL) && (pdev->si_drv1 == NULL) && (pdev->si_drv2 == NULL)) { - *dev = pdev; - dev_ref(*dev); + if (devhw != 0) { + KASSERT(devcmax <= dsp_cmax, + ("overflow: devcmax=%d, dsp_cmax=%d", devcmax, dsp_cmax)); + if (cunit > devcmax) { + PCM_RELEASE_QUICK(d); return; } + udcmask |= snd_c2unit(cunit); + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->unit != udcmask) { + CHN_UNLOCK(c); + continue; + } + CHN_UNLOCK(c); + udcmask &= ~snd_c2unit(cunit); + /* + * Temporarily increase clone maxunit to overcome + * vchan flexibility. + * + * # sysctl dev.pcm.0.play.vchans=256 + * dev.pcm.0.play.vchans: 1 -> 256 + * # cat /dev/zero > /dev/dsp0.vp255 & + * [1] 17296 + * # sysctl dev.pcm.0.play.vchans=0 + * dev.pcm.0.play.vchans: 256 -> 1 + * # fg + * [1] + running cat /dev/zero > /dev/dsp0.vp255 + * ^C + * # cat /dev/zero > /dev/dsp0.vp255 + * zsh: operation not supported: /dev/dsp0.vp255 + */ + tumax = snd_clone_getmaxunit(d->clones); + if (cunit > tumax) + snd_clone_setmaxunit(d->clones, cunit); + else + tumax = -1; + goto dsp_clone_alloc; + } + /* + * Ok, so we're requesting unallocated vchan, but still + * within maximum vchan limit. + */ + if (((devtype == SND_DEV_DSPHW_VPLAY && d->pvchancount > 0) || + (devtype == SND_DEV_DSPHW_VREC && d->rvchancount > 0)) && + cunit < snd_maxautovchans) { + udcmask &= ~snd_c2unit(cunit); + tumax = snd_clone_getmaxunit(d->clones); + if (cunit > tumax) + snd_clone_setmaxunit(d->clones, cunit); + else + tumax = -1; + goto dsp_clone_alloc; + } + PCM_RELEASE_QUICK(d); + return; + } + +dsp_clone_alloc: + ce = snd_clone_alloc(d->clones, dev, &cunit, udcmask); + if (tumax != -1) + snd_clone_setmaxunit(d->clones, tumax); + if (ce != NULL) { + udcmask |= snd_c2unit(cunit); + *dev = make_dev(&dsp_cdevsw, unit2minor(udcmask), + UID_ROOT, GID_WHEEL, 0666, "%s%d%s%d", + devname, unit, devsep, cunit); + snd_clone_register(ce, *dev); } + + PCM_RELEASE_QUICK(d); + + if (*dev != NULL) + dev_ref(*dev); } static void dsp_sysinit(void *p) { + if (dsp_ehtag != NULL) + return; + /* initialize unit numbering */ + snd_unit_init(); + dsp_umax = PCMMAXUNIT; + dsp_cmax = PCMMAXCHAN; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { - if (dsp_ehtag != NULL) - EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); + if (dsp_ehtag == NULL) + return; + EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); + dsp_ehtag = NULL; } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); #endif +char * +dsp_unit2name(char *buf, size_t len, int unit) +{ + int i, dtype; + + KASSERT(buf != NULL && len != 0, + ("bogus buf=%p len=%ju", buf, (uintmax_t)len)); + + dtype = snd_unit2d(unit); + + for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + if (dtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) + continue; + snprintf(buf, len, "%s%d%s%d", dsp_cdevs[i].name, + snd_unit2u(unit), dsp_cdevs[i].sep, snd_unit2c(unit)); + return (buf); + } + + return (NULL); +} + +/** + * @brief Handler for SNDCTL_AUDIOINFO. + * + * Gathers information about the audio device specified in ai->dev. If + * ai->dev == -1, then this function gathers information about the current + * device. If the call comes in on a non-audio device and ai->dev == -1, + * return EINVAL. + * + * This routine is supposed to go practically straight to the hardware, + * getting capabilities directly from the sound card driver, side-stepping + * the intermediate channel interface. + * + * Note, however, that the usefulness of this command is significantly + * decreased when requesting info about any device other than the one serving + * the request. While each snddev_channel refers to a specific device node, + * the converse is *not* true. Currently, when a sound device node is opened, + * the sound subsystem scans for an available audio channel (or channels, if + * opened in read+write) and then assigns them to the si_drv[12] private + * data fields. As a result, any information returned linking a channel to + * a specific character device isn't necessarily accurate. + * + * @note + * Calling threads must not hold any snddev_info or pcm_channel locks. + * + * @param dev device on which the ioctl was issued + * @param ai ioctl request data container + * + * @retval 0 success + * @retval EINVAL ai->dev specifies an invalid device + * + * @todo Verify correctness of Doxygen tags. ;) + */ +int +dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) +{ + struct pcmchan_caps *caps; + struct pcm_channel *ch; + struct snddev_info *d; + uint32_t fmts; + int i, nchan, *rates, minch, maxch; + char *devname, buf[CHN_NAMELEN]; + + /* + * If probing the device that received the ioctl, make sure it's a + * DSP device. (Users may use this ioctl with /dev/mixer and + * /dev/midi.) + */ + if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) + return (EINVAL); + + ch = NULL; + devname = NULL; + nchan = 0; + bzero(buf, sizeof(buf)); + + /* + * Search for the requested audio device (channel). Start by + * iterating over pcm devices. + */ + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + + /* XXX Need Giant magic entry ??? */ + + /* See the note in function docblock */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_lock(d); + + CHN_FOREACH(ch, d, channels.pcm) { + mtx_assert(ch->lock, MA_NOTOWNED); + CHN_LOCK(ch); + if (ai->dev == -1) { + if (DSP_REGISTERED(d, i_dev) && + (ch == PCM_RDCH(i_dev) || /* record ch */ + ch == PCM_WRCH(i_dev))) { /* playback ch */ + devname = dsp_unit2name(buf, + sizeof(buf), ch->unit); + } + } else if (ai->dev == nchan) { + devname = dsp_unit2name(buf, sizeof(buf), + ch->unit); + } + if (devname != NULL) + break; + CHN_UNLOCK(ch); + ++nchan; + } + + if (devname != NULL) { + /* + * At this point, the following synchronization stuff + * has happened: + * - a specific PCM device is locked. + * - a specific audio channel has been locked, so be + * sure to unlock when exiting; + */ + + caps = chn_getcaps(ch); + + /* + * With all handles collected, zero out the user's + * container and begin filling in its fields. + */ + bzero((void *)ai, sizeof(oss_audioinfo)); + + ai->dev = nchan; + strlcpy(ai->name, ch->name, sizeof(ai->name)); + + if ((ch->flags & CHN_F_BUSY) == 0) + ai->busy = 0; + else + ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; + + /** + * @note + * @c cmd - OSSv4 docs: "Only supported under Linux at + * this moment." Cop-out, I know, but I'll save + * running around in the process table for later. + * Is there a risk of leaking information? + */ + ai->pid = ch->pid; + + /* + * These flags stolen from SNDCTL_DSP_GETCAPS handler. + * Note, however, that a single channel operates in + * only one direction, so DSP_CAP_DUPLEX is out. + */ + /** + * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep + * these in pcmchan::caps? + */ + ai->caps = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; + + /* + * Collect formats supported @b natively by the + * device. Also determine min/max channels. (I.e., + * mono, stereo, or both?) + * + * If any channel is stereo, maxch = 2; + * if all channels are stereo, minch = 2, too; + * if any channel is mono, minch = 1; + * and if all channels are mono, maxch = 1. + */ + minch = 0; + maxch = 0; + fmts = 0; + for (i = 0; caps->fmtlist[i]; i++) { + fmts |= caps->fmtlist[i]; + if (caps->fmtlist[i] & AFMT_STEREO) { + minch = (minch == 0) ? 2 : minch; + maxch = 2; + } else { + minch = 1; + maxch = (maxch == 0) ? 1 : maxch; + } + } + + if (ch->direction == PCMDIR_PLAY) + ai->oformats = fmts; + else + ai->iformats = fmts; + + /** + * @note + * @c magic - OSSv4 docs: "Reserved for internal use + * by OSS." + * + * @par + * @c card_number - OSSv4 docs: "Number of the sound + * card where this device belongs or -1 if this + * information is not available. Applications + * should normally not use this field for any + * purpose." + */ + ai->card_number = -1; + /** + * @todo @c song_name - depends first on + * SNDCTL_[GS]ETSONG @todo @c label - depends + * on SNDCTL_[GS]ETLABEL + * @todo @c port_number - routing information? + */ + ai->port_number = -1; + ai->mixer_dev = (d->mixer_dev != NULL) ? PCMUNIT(d->mixer_dev) : -1; + /** + * @note + * @c real_device - OSSv4 docs: "Obsolete." + */ + ai->real_device = -1; + strlcpy(ai->devnode, devname, sizeof(ai->devnode)); + ai->enabled = device_is_attached(d->dev) ? 1 : 0; + /** + * @note + * @c flags - OSSv4 docs: "Reserved for future use." + * + * @note + * @c binding - OSSv4 docs: "Reserved for future use." + * + * @todo @c handle - haven't decided how to generate + * this yet; bus, vendor, device IDs? + */ + ai->min_rate = caps->minspeed; + ai->max_rate = caps->maxspeed; + + ai->min_channels = minch; + ai->max_channels = maxch; + + ai->nrates = chn_getrates(ch, &rates); + if (ai->nrates > OSS_MAX_SAMPLE_RATES) + ai->nrates = OSS_MAX_SAMPLE_RATES; + + for (i = 0; i < ai->nrates; i++) + ai->rates[i] = rates[i]; + + CHN_UNLOCK(ch); + } + + pcm_unlock(d); + + if (devname != NULL) + return (0); + } + + /* Exhausted the search -- nothing is locked, so return. */ + return (EINVAL); +} + +/** + * @brief Assigns a PCM channel to a sync group. + * + * Sync groups are used to enable audio operations on multiple devices + * simultaneously. They may be used with any number of devices and may + * span across applications. Devices are added to groups with + * the SNDCTL_DSP_SYNCGROUP ioctl, and operations are triggered with the + * SNDCTL_DSP_SYNCSTART ioctl. + * + * If the @c id field of the @c group parameter is set to zero, then a new + * sync group is created. Otherwise, wrch and rdch (if set) are added to + * the group specified. + * + * @todo As far as memory allocation, should we assume that things are + * okay and allocate with M_WAITOK before acquiring channel locks, + * freeing later if not? + * + * @param wrch output channel associated w/ device (if any) + * @param rdch input channel associated w/ device (if any) + * @param group Sync group parameters + * + * @retval 0 success + * @retval non-zero error to be propagated upstream + */ +static int +dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group) +{ + struct pcmchan_syncmember *smrd, *smwr; + struct pcmchan_syncgroup *sg; + int ret, sg_ids[3]; + + smrd = NULL; + smwr = NULL; + sg = NULL; + ret = 0; + + /* + * Free_unr() may sleep, so store released syncgroup IDs until after + * all locks are released. + */ + sg_ids[0] = sg_ids[1] = sg_ids[2] = 0; + + PCM_SG_LOCK(); + + /* + * - Insert channel(s) into group's member list. + * - Set CHN_F_NOTRIGGER on channel(s). + * - Stop channel(s). + */ + + /* + * If device's channels are already mapped to a group, unmap them. + */ + if (wrch) { + CHN_LOCK(wrch); + sg_ids[0] = chn_syncdestroy(wrch); + } + + if (rdch) { + CHN_LOCK(rdch); + sg_ids[1] = chn_syncdestroy(rdch); + } + + /* + * Verify that mode matches character device properites. + * - Bail if PCM_ENABLE_OUTPUT && wrch == NULL. + * - Bail if PCM_ENABLE_INPUT && rdch == NULL. + */ + if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) || + ((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) { + ret = EINVAL; + goto out; + } + /* + * An id of zero indicates the user wants to create a new + * syncgroup. + */ + if (group->id == 0) { + sg = (struct pcmchan_syncgroup *)malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); + if (sg != NULL) { + SLIST_INIT(&sg->members); + sg->id = alloc_unr(pcmsg_unrhdr); + + group->id = sg->id; + SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link); + } else + ret = ENOMEM; + } else { + SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { + if (sg->id == group->id) + break; + } + if (sg == NULL) + ret = EINVAL; + } + + /* Couldn't create or find a syncgroup. Fail. */ + if (sg == NULL) + goto out; + + /* + * Allocate a syncmember, assign it and a channel together, and + * insert into syncgroup. + */ + if (group->mode & PCM_ENABLE_INPUT) { + smrd = (struct pcmchan_syncmember *)malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); + if (smrd == NULL) { + ret = ENOMEM; + goto out; + } + + SLIST_INSERT_HEAD(&sg->members, smrd, link); + smrd->parent = sg; + smrd->ch = rdch; + + chn_abort(rdch); + rdch->flags |= CHN_F_NOTRIGGER; + rdch->sm = smrd; + } + + if (group->mode & PCM_ENABLE_OUTPUT) { + smwr = (struct pcmchan_syncmember *)malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); + if (smwr == NULL) { + ret = ENOMEM; + goto out; + } + + SLIST_INSERT_HEAD(&sg->members, smwr, link); + smwr->parent = sg; + smwr->ch = wrch; + + chn_abort(wrch); + wrch->flags |= CHN_F_NOTRIGGER; + wrch->sm = smwr; + } + + +out: + if (ret != 0) { + if (smrd != NULL) + free(smrd, M_DEVBUF); + if ((sg != NULL) && SLIST_EMPTY(&sg->members)) { + sg_ids[2] = sg->id; + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + free(sg, M_DEVBUF); + } + + if (wrch) + wrch->sm = NULL; + if (rdch) + rdch->sm = NULL; + } + + if (wrch) + CHN_UNLOCK(wrch); + if (rdch) + CHN_UNLOCK(rdch); + + PCM_SG_UNLOCK(); + + if (sg_ids[0]) + free_unr(pcmsg_unrhdr, sg_ids[0]); + if (sg_ids[1]) + free_unr(pcmsg_unrhdr, sg_ids[1]); + if (sg_ids[2]) + free_unr(pcmsg_unrhdr, sg_ids[2]); + + return (ret); +} + +/** + * @brief Launch a sync group into action + * + * Sync groups are established via SNDCTL_DSP_SYNCGROUP. This function + * iterates over all members, triggering them along the way. + * + * @note Caller must not hold any channel locks. + * + * @param sg_id sync group identifier + * + * @retval 0 success + * @retval non-zero error worthy of propagating upstream to user + */ +static int +dsp_oss_syncstart(int sg_id) +{ + struct pcmchan_syncmember *sm, *sm_tmp; + struct pcmchan_syncgroup *sg; + struct pcm_channel *c; + int ret, needlocks; + + /* Get the synclists lock */ + PCM_SG_LOCK(); + + do { + ret = 0; + needlocks = 0; + + /* Search for syncgroup by ID */ + SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { + if (sg->id == sg_id) + break; + } + + /* Return EINVAL if not found */ + if (sg == NULL) { + ret = EINVAL; + break; + } + + /* Any removals resulting in an empty group should've handled this */ + KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup")); + + /* + * Attempt to lock all member channels - if any are already + * locked, unlock those acquired, sleep for a bit, and try + * again. + */ + SLIST_FOREACH(sm, &sg->members, link) { + if (CHN_TRYLOCK(sm->ch) == 0) { + int timo = hz * 5/1000; + if (timo < 1) + timo = 1; + + /* Release all locked channels so far, retry */ + SLIST_FOREACH(sm_tmp, &sg->members, link) { + /* sm is the member already locked */ + if (sm == sm_tmp) + break; + CHN_UNLOCK(sm_tmp->ch); + } + + /** @todo Is PRIBIO correct/ */ + ret = msleep(sm, &snd_pcm_syncgroups_mtx, + PRIBIO | PCATCH, "pcmsg", timo); + if (ret == EINTR || ret == ERESTART) + break; + + needlocks = 1; + ret = 0; /* Assumes ret == EAGAIN... */ + } + } + } while (needlocks && ret == 0); + + /* Proceed only if no errors encountered. */ + if (ret == 0) { + /* Launch channels */ + while((sm = SLIST_FIRST(&sg->members)) != NULL) { + SLIST_REMOVE_HEAD(&sg->members, link); + + c = sm->ch; + c->sm = NULL; + chn_start(c, 1); + c->flags &= ~CHN_F_NOTRIGGER; + CHN_UNLOCK(c); + + free(sm, M_DEVBUF); + } + + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + free(sg, M_DEVBUF); + } + + PCM_SG_UNLOCK(); + + /* + * Free_unr() may sleep, so be sure to give up the syncgroup lock + * first. + */ + if (ret == 0) + free_unr(pcmsg_unrhdr, sg_id); + + return (ret); +} + +/** + * @brief Handler for SNDCTL_DSP_POLICY + * + * The SNDCTL_DSP_POLICY ioctl is a simpler interface to control fragment + * size and count like with SNDCTL_DSP_SETFRAGMENT. Instead of the user + * specifying those two parameters, s/he simply selects a number from 0..10 + * which corresponds to a buffer size. Smaller numbers request smaller + * buffers with lower latencies (at greater overhead from more frequent + * interrupts), while greater numbers behave in the opposite manner. + * + * The 4Front spec states that a value of 5 should be the default. However, + * this implementation deviates slightly by using a linear scale without + * consulting drivers. I.e., even though drivers may have different default + * buffer sizes, a policy argument of 5 will have the same result across + * all drivers. + * + * See http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html for + * more information. + * + * @todo When SNDCTL_DSP_COOKEDMODE is supported, it'll be necessary to + * work with hardware drivers directly. + * + * @note PCM channel arguments must not be locked by caller. + * + * @param wrch Pointer to opened playback channel (optional; may be NULL) + * @param rdch " recording channel (optional; may be NULL) + * @param policy Integer from [0:10] + * + * @retval 0 constant (for now) + */ +static int +dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) +{ + int ret; + + if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) + return (EIO); + + /* Default: success */ + ret = 0; + + if (rdch) { + CHN_LOCK(rdch); + ret = chn_setlatency(rdch, policy); + CHN_UNLOCK(rdch); + } + + if (wrch && ret == 0) { + CHN_LOCK(wrch); + ret = chn_setlatency(wrch, policy); + CHN_UNLOCK(wrch); + } + + if (ret) + ret = EIO; + + return (ret); +} + +#ifdef OSSV4_EXPERIMENT +/** + * @brief Enable or disable "cooked" mode + * + * This is a handler for @c SNDCTL_DSP_COOKEDMODE. When in cooked mode, which + * is the default, the sound system handles rate and format conversions + * automatically (ex: user writing 11025Hz/8 bit/unsigned but card only + * operates with 44100Hz/16bit/signed samples). + * + * Disabling cooked mode is intended for applications wanting to mmap() + * a sound card's buffer space directly, bypassing the FreeBSD 2-stage + * feeder architecture, presumably to gain as much control over audio + * hardware as possible. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_COOKEDMODE.html + * for more details. + * + * @note Currently, this function is just a stub that always returns EINVAL. + * + * @todo Figure out how to and actually implement this. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param enabled 0 = raw mode, 1 = cooked mode + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled) +{ + return (EINVAL); +} + +/** + * @brief Retrieve channel interleaving order + * + * This is the handler for @c SNDCTL_DSP_GET_CHNORDER. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_GET_CHNORDER.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_DSP_GET_CHNORDER. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param map channel map (result will be stored there) + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) +{ + return (EINVAL); +} + +/** + * @brief Specify channel interleaving order + * + * This is the handler for @c SNDCTL_DSP_SET_CHNORDER. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support @c SNDCTL_DSP_SET_CHNORDER. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param map channel map + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) +{ + return (EINVAL); +} + +/** + * @brief Retrieve an audio device's label + * + * This is a handler for the @c SNDCTL_GETLABEL ioctl. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html + * for more details. + * + * From Hannu@4Front: "For example ossxmix (just like some HW mixer + * consoles) can show variable "labels" for certain controls. By default + * the application name (say quake) is shown as the label but + * applications may change the labels themselves." + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support @c SNDCTL_GETLABEL. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param label label gets copied here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) +{ + return (EINVAL); +} + +/** + * @brief Specify an audio device's label + * + * This is a handler for the @c SNDCTL_SETLABEL ioctl. Please see the + * comments for @c dsp_oss_getlabel immediately above. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETLABEL. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param label label gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) +{ + return (EINVAL); +} + +/** + * @brief Retrieve name of currently played song + * + * This is a handler for the @c SNDCTL_GETSONG ioctl. Audio players could + * tell the system the name of the currently playing song, which would be + * visible in @c /dev/sndstat. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETSONG.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_GETSONG. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param song song name gets copied here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) +{ + return (EINVAL); +} + +/** + * @brief Retrieve name of currently played song + * + * This is a handler for the @c SNDCTL_SETSONG ioctl. Audio players could + * tell the system the name of the currently playing song, which would be + * visible in @c /dev/sndstat. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_SETSONG.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETSONG. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param song song name gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) +{ + return (EINVAL); +} + +/** + * @brief Rename a device + * + * This is a handler for the @c SNDCTL_SETNAME ioctl. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_SETNAME.html for + * more details. + * + * From Hannu@4Front: "This call is used to change the device name + * reported in /dev/sndstat and ossinfo. So instead of using some generic + * 'OSS loopback audio (MIDI) driver' the device may be given a meaningfull + * name depending on the current context (for example 'OSS virtual wave table + * synth' or 'VoIP link to London')." + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETNAME. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param name new device name gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name) +{ + return (EINVAL); +} +#endif /* !OSSV4_EXPERIMENT */ --- sys/dev/sound/pcm/dsp.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/dsp.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,20 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/dsp.h,v 1.9 2005/01/06 01:43:20 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/dsp.h,v 1.13 2007/06/16 03:37:28 ariff Exp $ */ +#ifndef _PCMDSP_H_ +#define _PCMDSP_H_ + extern struct cdevsw dsp_cdevsw; + +struct dsp_cdevinfo; + +char *dsp_unit2name(char *, size_t, int); +int dsp_oss_audioinfo(struct cdev *, oss_audioinfo *); + +void dsp_cdevinfo_init(struct snddev_info *); +void dsp_cdevinfo_flush(struct snddev_info *); + +#endif /* !_PCMDSP_H_ */ --- sys/dev/sound/pcm/fake.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/fake.c Thu Jul 12 12:04:19 2007 @@ -26,7 +26,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/fake.c,v 1.14.2.1 2005/12/30 19:55:54 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/fake.c,v 1.18 2007/03/15 18:19:01 ariff Exp $"); static u_int32_t fk_fmt[] = { AFMT_MU_LAW, @@ -137,15 +137,16 @@ struct snddev_info *d = device_get_softc(dev); struct pcm_channel *c; - c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK); + c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); c->methods = kobj_create(&fkchan_class, M_DEVBUF, M_WAITOK); c->parentsnddev = d; /* * Fake channel is such a blessing in disguise. Using this, - * we can keep track prefered virtual channel speed without + * we can keep track prefered virtual channel speed / format without * querying kernel hint repetitively (see vchan_create / vchan.c). */ c->speed = 0; + c->format = 0; snprintf(c->name, CHN_NAMELEN, "%s:fake", device_get_nameunit(dev)); return c; --- sys/dev/sound/pcm/feeder.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/feeder.c Thu Jul 12 12:04:19 2007 @@ -28,13 +28,50 @@ #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder.c,v 1.33.2.3 2006/03/07 15:51:19 jhb Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder.c,v 1.44 2007/06/17 15:53:11 ariff Exp $"); MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); #define MAXFEEDERS 256 #undef FEEDER_DEBUG +int feeder_buffersize = FEEDBUFSZ; +TUNABLE_INT("hw.snd.feeder_buffersize", &feeder_buffersize); + +#ifdef SND_DEBUG +static int +sysctl_hw_snd_feeder_buffersize(SYSCTL_HANDLER_ARGS) +{ + int i, err, val; + + val = feeder_buffersize; + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err != 0 || req->newptr == NULL) + return err; + + if (val < FEEDBUFSZ_MIN || val > FEEDBUFSZ_MAX) + return EINVAL; + + i = 0; + while (val >> i) + i++; + i = 1 << i; + if (i > val && (i >> 1) > 0 && (i >> 1) >= ((val * 3) >> 2)) + i >>= 1; + + feeder_buffersize = i; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_buffersize, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_buffersize, "I", + "feeder buffer size"); +#else +SYSCTL_INT(_hw_snd, OID_AUTO, feeder_buffersize, CTLFLAG_RD, + &feeder_buffersize, FEEDBUFSZ, "feeder buffer size"); +#endif + struct feedertab_entry { SLIST_ENTRY(feedertab_entry) link; struct feeder_class *feederclass; @@ -72,6 +109,55 @@ SLIST_INSERT_HEAD(&feedertab, fte, link); feedercnt++; + /* initialize global variables */ + + if (snd_verbose < 0 || snd_verbose > 4) + snd_verbose = 1; + + /* initialize unit numbering */ + snd_unit_init(); + if (snd_unit < 0 || snd_unit > PCMMAXUNIT) + snd_unit = -1; + + if (snd_maxautovchans < 0 || + snd_maxautovchans > SND_MAXVCHANS) + snd_maxautovchans = 0; + + if (chn_latency < CHN_LATENCY_MIN || + chn_latency > CHN_LATENCY_MAX) + chn_latency = CHN_LATENCY_DEFAULT; + + if (chn_latency_profile < CHN_LATENCY_PROFILE_MIN || + chn_latency_profile > CHN_LATENCY_PROFILE_MAX) + chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; + + if (feeder_buffersize < FEEDBUFSZ_MIN || + feeder_buffersize > FEEDBUFSZ_MAX) + feeder_buffersize = FEEDBUFSZ; + + if (feeder_rate_min < FEEDRATE_MIN || + feeder_rate_max < FEEDRATE_MIN || + feeder_rate_min > FEEDRATE_MAX || + feeder_rate_max > FEEDRATE_MAX || + !(feeder_rate_min < feeder_rate_max)) { + feeder_rate_min = FEEDRATE_RATEMIN; + feeder_rate_max = FEEDRATE_RATEMAX; + } + + if (feeder_rate_round < FEEDRATE_ROUNDHZ_MIN || + feeder_rate_round > FEEDRATE_ROUNDHZ_MAX) + feeder_rate_round = FEEDRATE_ROUNDHZ; + + if (bootverbose) + printf("%s: snd_unit=%d snd_maxautovchans=%d " + "latency=%d feeder_buffersize=%d " + "feeder_rate_min=%d feeder_rate_max=%d " + "feeder_rate_round=%d\n", + __func__, snd_unit, snd_maxautovchans, + chn_latency, feeder_buffersize, + feeder_rate_min, feeder_rate_max, + feeder_rate_round); + /* we've got our root feeder so don't veto pcm loading anymore */ pcm_veto_load = 0; @@ -259,122 +345,152 @@ return 1; } -static struct pcm_feeder * -feeder_fmtchain(u_int32_t *to, struct pcm_feeder *source, struct pcm_feeder *stop, int maxdepth) -{ - struct feedertab_entry *fte; - struct pcm_feeder *try, *ret; - - DEB(printf("trying %s (0x%08x -> 0x%08x)...\n", source->class->name, source->desc->in, source->desc->out)); - if (fmtvalid(source->desc->out, to)) { - DEB(printf("got it\n")); - return source; - } - - if (maxdepth < 0) - return NULL; - - SLIST_FOREACH(fte, &feedertab, link) { - if (fte->desc == NULL) - continue; - if (fte->desc->type != FEEDER_FMT) - continue; - if (fte->desc->in == source->desc->out) { - try = feeder_create(fte->feederclass, fte->desc); - if (try) { - try->source = source; - ret = chainok(try, stop)? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; - if (ret != NULL) - return ret; - feeder_destroy(try); - } - } - } - /* printf("giving up %s...\n", source->class->name); */ +/* + * See feeder_fmtchain() for the mumbo-jumbo ridiculous explanation + * of what the heck is this FMT_Q_* + */ +#define FMT_Q_UP 1 +#define FMT_Q_DOWN 2 +#define FMT_Q_EQ 3 +#define FMT_Q_MULTI 4 - return NULL; -} +/* + * 14bit format scoring + * -------------------- + * + * 13 12 11 10 9 8 2 1 0 offset + * +---+---+---+---+---+---+-------------+---+---+ + * | X | X | X | X | X | X | X X X X X X | X | X | + * +---+---+---+---+---+---+-------------+---+---+ + * | | | | | | | | | + * | | | | | | | | +--> signed? + * | | | | | | | | + * | | | | | | | +------> bigendian? + * | | | | | | | + * | | | | | | +---------------> total channels + * | | | | | | + * | | | | | +------------------------> AFMT_A_LAW + * | | | | | + * | | | | +----------------------------> AFMT_MU_LAW + * | | | | + * | | | +--------------------------------> AFMT_8BIT + * | | | + * | | +------------------------------------> AFMT_16BIT + * | | + * | +----------------------------------------> AFMT_24BIT + * | + * +--------------------------------------------> AFMT_32BIT + */ +#define score_signeq(s1, s2) (((s1) & 0x1) == ((s2) & 0x1)) +#define score_endianeq(s1, s2) (((s1) & 0x2) == ((s2) & 0x2)) +#define score_cheq(s1, s2) (((s1) & 0xfc) == ((s2) & 0xfc)) +#define score_val(s1) ((s1) & 0x3f00) +#define score_cse(s1) ((s1) & 0x7f) -int +u_int32_t chn_fmtscore(u_int32_t fmt) { - if (fmt & AFMT_32BIT) - return 60; - if (fmt & AFMT_24BIT) - return 50; - if (fmt & AFMT_16BIT) - return 40; - if (fmt & (AFMT_U8|AFMT_S8)) - return 30; - if (fmt & AFMT_MU_LAW) - return 20; + u_int32_t ret; + + ret = 0; + if (fmt & AFMT_SIGNED) + ret |= 1 << 0; + if (fmt & AFMT_BIGENDIAN) + ret |= 1 << 1; + if (fmt & AFMT_STEREO) + ret |= (2 & 0x3f) << 2; + else + ret |= (1 & 0x3f) << 2; if (fmt & AFMT_A_LAW) - return 10; - return 0; + ret |= 1 << 8; + else if (fmt & AFMT_MU_LAW) + ret |= 1 << 9; + else if (fmt & AFMT_8BIT) + ret |= 1 << 10; + else if (fmt & AFMT_16BIT) + ret |= 1 << 11; + else if (fmt & AFMT_24BIT) + ret |= 1 << 12; + else if (fmt & AFMT_32BIT) + ret |= 1 << 13; + + return ret; } -u_int32_t -chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) +static u_int32_t +chn_fmtbestfunc(u_int32_t fmt, u_int32_t *fmts, int cheq) { - u_int32_t best; - int i, score, score2, oldscore; + u_int32_t best, score, score2, oldscore; + int i; + + if (fmt == 0 || fmts == NULL || fmts[0] == 0) + return 0; + + if (fmtvalid(fmt, fmts)) + return fmt; best = 0; score = chn_fmtscore(fmt); oldscore = 0; for (i = 0; fmts[i] != 0; i++) { score2 = chn_fmtscore(fmts[i]); - if (oldscore == 0 || (score2 == score) || - (score2 > oldscore && score2 < score) || - (score2 < oldscore && score2 > score) || - (oldscore < score && score2 > oldscore)) { - best = fmts[i]; - oldscore = score2; + if (cheq && !score_cheq(score, score2)) + continue; + if (oldscore == 0 || + (score_val(score2) == score_val(score)) || + (score_val(score2) == score_val(oldscore)) || + (score_val(score2) > score_val(oldscore) && + score_val(score2) < score_val(score)) || + (score_val(score2) < score_val(oldscore) && + score_val(score2) > score_val(score)) || + (score_val(oldscore) < score_val(score) && + score_val(score2) > score_val(oldscore))) { + if (score_val(oldscore) != score_val(score2) || + score_cse(score) == score_cse(score2) || + ((score_cse(oldscore) != score_cse(score) && + !score_endianeq(score, oldscore) && + (score_endianeq(score, score2) || + (!score_signeq(score, oldscore) && + score_signeq(score, score2)))))) { + best = fmts[i]; + oldscore = score2; + } } } return best; } u_int32_t -chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts) +chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) { - u_int32_t best; - int i, score, score2, oldscore; + return chn_fmtbestfunc(fmt, fmts, 0); +} - best = 0; - score = chn_fmtscore(fmt); - oldscore = 0; - for (i = 0; fmts[i] != 0; i++) { - if ((fmt & AFMT_STEREO) == (fmts[i] & AFMT_STEREO)) { - score2 = chn_fmtscore(fmts[i]); - if (oldscore == 0 || (score2 == score) || - (score2 > oldscore && score2 < score) || - (score2 < oldscore && score2 > score) || - (oldscore < score && score2 > oldscore)) { - best = fmts[i]; - oldscore = score2; - } - } - } - return best; +u_int32_t +chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts) +{ + return chn_fmtbestfunc(fmt, fmts, 1); } u_int32_t chn_fmtbest(u_int32_t fmt, u_int32_t *fmts) { u_int32_t best1, best2; - int score, score1, score2; + u_int32_t score, score1, score2; + + if (fmtvalid(fmt, fmts)) + return fmt; best1 = chn_fmtbeststereo(fmt, fmts); best2 = chn_fmtbestbit(fmt, fmts); - if (best1 != 0 && best2 != 0) { + if (best1 != 0 && best2 != 0 && best1 != best2) { if (fmt & AFMT_STEREO) return best1; else { - score = chn_fmtscore(fmt); - score1 = chn_fmtscore(best1); - score2 = chn_fmtscore(best2); + score = score_val(chn_fmtscore(fmt)); + score1 = score_val(chn_fmtscore(best1)); + score2 = score_val(chn_fmtscore(best2)); if (score1 == score2 || score1 == score) return best1; else if (score2 == score) @@ -389,6 +505,184 @@ return best2; } +static struct pcm_feeder * +feeder_fmtchain(u_int32_t *to, struct pcm_feeder *source, struct pcm_feeder *stop, int maxdepth) +{ + struct feedertab_entry *fte, *ftebest; + struct pcm_feeder *try, *ret; + uint32_t fl, qout, qsrc, qdst; + int qtype; + + if (to == NULL || to[0] == 0) + return NULL; + + DEB(printf("trying %s (0x%08x -> 0x%08x)...\n", source->class->name, source->desc->in, source->desc->out)); + if (fmtvalid(source->desc->out, to)) { + DEB(printf("got it\n")); + return source; + } + + if (maxdepth < 0) + return NULL; + + /* + * WARNING: THIS IS _NOT_ FOR THE FAINT HEART + * Disclaimer: I don't expect anybody could understand this + * without deep logical and mathematical analysis + * involving various unnamed probability theorem. + * + * This "Best Fit Random Chain Selection" (BLEHBLEHWHATEVER) algorithm + * is **extremely** difficult to digest especially when applied to + * large sets / numbers of random chains (feeders), each with + * unique characteristic providing different sets of in/out format. + * + * Basically, our FEEDER_FMT (see feeder_fmt.c) chains characteristic: + * 1) Format chains + * 1.1 "8bit to any, not to 8bit" + * 1.1.1 sign can remain consistent, e.g: u8 -> u16[le|be] + * 1.1.2 sign can be changed, e.g: u8 -> s16[le|be] + * 1.1.3 endian can be changed, e.g: u8 -> u16[le|be] + * 1.1.4 both can be changed, e.g: u8 -> [u|s]16[le|be] + * 1.2 "Any to 8bit, not from 8bit" + * 1.2.1 sign can remain consistent, e.g: s16le -> s8 + * 1.2.2 sign can be changed, e.g: s16le -> u8 + * 1.2.3 source endian can be anything e.g: s16[le|be] -> s8 + * 1.2.4 source endian / sign can be anything e.g: [u|s]16[le|be] -> u8 + * 1.3 "Any to any where BOTH input and output either 8bit or non-8bit" + * 1.3.1 endian MUST remain consistent + * 1.3.2 sign CAN be changed + * 1.4 "Long jump" is allowed, e.g: from 16bit to 32bit, excluding + * 16bit to 24bit . + * 2) Channel chains (mono <-> stereo) + * 2.1 Both endian and sign MUST remain consistent + * 3) Endian chains (big endian <-> little endian) + * 3.1 Channels and sign MUST remain consistent + * 4) Sign chains (signed <-> unsigned) + * 4.1 Channels and endian MUST remain consistent + * + * .. and the mother of all chaining rules: + * + * Rules 0: Source and destination MUST not contain multiple selections. + * (qtype != FMT_Q_MULTI) + * + * First of all, our caller ( chn_fmtchain() ) will reduce the possible + * multiple from/to formats to a single best format using chn_fmtbest(). + * Then, using chn_fmtscore(), we determine the chaining characteristic. + * Our main goal is to narrow it down until it reach FMT_Q_EQ chaining + * type while still adhering above chaining rules. + * + * The need for this complicated chaining procedures is inevitable, + * since currently we have more than 200 different types of FEEDER_FMT + * doing various unique format conversion. Without this (the old way), + * it is possible to generate broken chain since it doesn't do any + * sanity checking to ensure that the output format is "properly aligned" + * with the direction of conversion (quality up/down/equal). + * + * Conversion: s24le to s32le + * Possible chain: 1) s24le -> s32le (correct, optimized) + * 2) s24le -> s16le -> s32le + * (since we have feeder_24to16 and feeder_16to32) + * +-- obviously broken! + * + * Using scoring mechanisme, this will ensure that the chaining + * process do the right thing, or at least, give the best chain + * possible without causing quality (the 'Q') degradation. + */ + + qdst = chn_fmtscore(to[0]); + qsrc = chn_fmtscore(source->desc->out); + +#define score_q(s1) score_val(s1) +#define score_8bit(s1) ((s1) & 0x700) +#define score_non8bit(s1) (!score_8bit(s1)) +#define score_across8bit(s1, s2) ((score_8bit(s1) && score_non8bit(s2)) || \ + (score_8bit(s2) && score_non8bit(s1))) + +#define FMT_CHAIN_Q_UP(s1, s2) (score_q(s1) < score_q(s2)) +#define FMT_CHAIN_Q_DOWN(s1, s2) (score_q(s1) > score_q(s2)) +#define FMT_CHAIN_Q_EQ(s1, s2) (score_q(s1) == score_q(s2)) +#define FMT_Q_DOWN_FLAGS(s1, s2) (0x1 | (score_across8bit(s1, s2) ? \ + 0x2 : 0x0)) +#define FMT_Q_UP_FLAGS(s1, s2) FMT_Q_DOWN_FLAGS(s1, s2) +#define FMT_Q_EQ_FLAGS(s1, s2) (0x3ffc | \ + ((score_cheq(s1, s2) && \ + score_endianeq(s1, s2)) ? \ + 0x1 : 0x0) | \ + ((score_cheq(s1, s2) && \ + score_signeq(s1, s2)) ? \ + 0x2 : 0x0)) + + /* Determine chaining direction and set matching flag */ + fl = 0x3fff; + if (to[1] != 0) { + qtype = FMT_Q_MULTI; + printf("%s: WARNING: FMT_Q_MULTI chaining. Expect the unexpected.\n", __func__); + } else if (FMT_CHAIN_Q_DOWN(qsrc, qdst)) { + qtype = FMT_Q_DOWN; + fl = FMT_Q_DOWN_FLAGS(qsrc, qdst); + } else if (FMT_CHAIN_Q_UP(qsrc, qdst)) { + qtype = FMT_Q_UP; + fl = FMT_Q_UP_FLAGS(qsrc, qdst); + } else { + qtype = FMT_Q_EQ; + fl = FMT_Q_EQ_FLAGS(qsrc, qdst); + } + + ftebest = NULL; + + SLIST_FOREACH(fte, &feedertab, link) { + if (fte->desc == NULL) + continue; + if (fte->desc->type != FEEDER_FMT) + continue; + qout = chn_fmtscore(fte->desc->out); +#define FMT_Q_MULTI_VALIDATE(qt) ((qt) == FMT_Q_MULTI) +#define FMT_Q_FL_MATCH(qfl, s1, s2) (((s1) & (qfl)) == ((s2) & (qfl))) +#define FMT_Q_UP_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_UP && \ + score_q(s3) >= score_q(s1) && \ + score_q(s3) <= score_q(s2)) +#define FMT_Q_DOWN_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_DOWN && \ + score_q(s3) <= score_q(s1) && \ + score_q(s3) >= score_q(s2)) +#define FMT_Q_EQ_VALIDATE(qt, s1, s2) ((qt) == FMT_Q_EQ && \ + score_q(s1) == score_q(s2)) + if (fte->desc->in == source->desc->out && + (FMT_Q_MULTI_VALIDATE(qtype) || + (FMT_Q_FL_MATCH(fl, qout, qdst) && + (FMT_Q_UP_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_DOWN_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_EQ_VALIDATE(qtype, qdst, qout))))) { + try = feeder_create(fte->feederclass, fte->desc); + if (try) { + try->source = source; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + if (ret != NULL) + return ret; + feeder_destroy(try); + } + } else if (fte->desc->in == source->desc->out) { + /* XXX quality must be considered! */ + if (ftebest == NULL) + ftebest = fte; + } + } + + if (ftebest != NULL) { + try = feeder_create(ftebest->feederclass, ftebest->desc); + if (try) { + try->source = source; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + if (ret != NULL) + return ret; + feeder_destroy(try); + } + } + + /* printf("giving up %s...\n", source->class->name); */ + + return NULL; +} + u_int32_t chn_fmtchain(struct pcm_channel *c, u_int32_t *to) { @@ -401,13 +695,15 @@ KASSERT(to != NULL, ("to == NULL")); KASSERT(to[0] != 0, ("to[0] == 0")); + if (c == NULL || c->feeder == NULL || to == NULL || to[0] == 0) + return 0; + stop = c->feeder; + best = 0; if (c->direction == PCMDIR_REC && c->feeder->desc->type == FEEDER_ROOT) { from = chn_getcaps(c)->fmtlist; - if (fmtvalid(to[0], from)) - from = to; - else { + if (from[1] != 0) { best = chn_fmtbest(to[0], from); if (best != 0) { tmpfrom[0] = best; @@ -420,49 +716,60 @@ tmpfrom[1] = 0; from = tmpfrom; if (to[1] != 0) { - if (fmtvalid(tmpfrom[0], to)) { - tmpto[0] = tmpfrom[0]; + best = chn_fmtbest(from[0], to); + if (best != 0) { + tmpto[0] = best; tmpto[1] = 0; to = tmpto; - } else { - best = chn_fmtbest(tmpfrom[0], to); - if (best != 0) { - tmpto[0] = best; - tmpto[1] = 0; - to = tmpto; - } } } } - i = 0; - best = 0; - bestmax = 100; - while (from[i] != 0) { - c->feeder->desc->out = from[i]; - try = NULL; +#define FEEDER_FMTCHAIN_MAXDEPTH 8 + + try = NULL; + + if (to[0] != 0 && from[0] != 0 && + to[1] == 0 && from[1] == 0) { max = 0; - while (try == NULL && max < 8) { + best = from[0]; + c->feeder->desc->out = best; + do { try = feeder_fmtchain(to, c->feeder, stop, max); - if (try == NULL) - max++; - } - if (try != NULL && max < bestmax) { - bestmax = max; - best = from[i]; - } - while (try != NULL && try != stop) { - del = try; - try = try->source; - feeder_destroy(del); + DEB(if (try != NULL) { + printf("%s: 0x%08x -> 0x%08x (maxdepth: %d)\n", + __func__, from[0], to[0], max); + }); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + } else { + printf("%s: Using the old-way format chaining!\n", __func__); + i = 0; + best = 0; + bestmax = 100; + while (from[i] != 0) { + c->feeder->desc->out = from[i]; + try = NULL; + max = 0; + do { + try = feeder_fmtchain(to, c->feeder, stop, max); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + if (try != NULL && max < bestmax) { + bestmax = max; + best = from[i]; + } + while (try != NULL && try != stop) { + del = try; + try = try->source; + feeder_destroy(del); + } + i++; } - i++; - } - if (best == 0) - return 0; + if (best == 0) + return 0; - c->feeder->desc->out = best; - try = feeder_fmtchain(to, c->feeder, stop, bestmax); + c->feeder->desc->out = best; + try = feeder_fmtchain(to, c->feeder, stop, bestmax); + } if (try == NULL) return 0; @@ -521,28 +828,54 @@ feed_root(struct pcm_feeder *feeder, struct pcm_channel *ch, u_int8_t *buffer, u_int32_t count, void *source) { struct snd_dbuf *src = source; - int l; - u_int8_t x; + int l, offset; KASSERT(count > 0, ("feed_root: count == 0")); /* count &= ~((1 << ch->align) - 1); */ KASSERT(count > 0, ("feed_root: aligned count == 0 (align = %d)", ch->align)); + if (++ch->feedcount == 0) + ch->feedcount = 2; + l = min(count, sndbuf_getready(src)); - sndbuf_dispose(src, buffer, l); /* When recording only return as much data as available */ - if (ch->direction == PCMDIR_REC) + if (ch->direction == PCMDIR_REC) { + sndbuf_dispose(src, buffer, l); return l; + } -/* - if (l < count) - printf("appending %d bytes\n", count - l); -*/ - - x = (sndbuf_getfmt(src) & AFMT_SIGNED)? 0 : 0x80; - while (l < count) - buffer[l++] = x; + + offset = count - l; + + if (offset > 0) { + if (snd_verbose > 3) + printf("%s: (%s) %spending %d bytes " + "(count=%d l=%d feed=%d)\n", + __func__, + (ch->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + (ch->feedcount == 1) ? "pre" : "ap", + offset, count, l, ch->feedcount); + + if (ch->feedcount == 1) { + memset(buffer, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (l > 0) + sndbuf_dispose(src, buffer + offset, l); + else + ch->feedcount--; + } else { + if (l > 0) + sndbuf_dispose(src, buffer, l); + memset(buffer + l, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (!(ch->flags & CHN_F_CLOSING)) + ch->xruns++; + } + } else if (l > 0) + sndbuf_dispose(src, buffer, l); return count; } @@ -561,8 +894,3 @@ }; SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register, &feeder_root_class); SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); - - - - - --- sys/dev/sound/pcm/feeder.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/feeder.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/feeder.h,v 1.12.2.1 2006/01/29 02:27:28 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/feeder.h,v 1.15 2007/03/16 17:15:33 ariff Exp $ */ struct pcm_feederdesc { @@ -53,7 +53,7 @@ void feeder_register(void *p); struct feeder_class *feeder_getclass(struct pcm_feederdesc *desc); -int chn_fmtscore(u_int32_t fmt); +u_int32_t chn_fmtscore(u_int32_t fmt); u_int32_t chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts); u_int32_t chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts); u_int32_t chn_fmtbest(u_int32_t fmt, u_int32_t *fmts); @@ -72,17 +72,45 @@ .desc = feeder ## _desc, \ .data = pdata, \ }; \ -SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, feeder_register, &feeder ## _class); +SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_ANY, feeder_register, &feeder ## _class); -#define FEEDER_ROOT 1 -#define FEEDER_FMT 2 -#define FEEDER_MIXER 3 -#define FEEDER_RATE 4 -#define FEEDER_FILTER 5 -#define FEEDER_VOLUME 6 -#define FEEDER_LAST FEEDER_VOLUME +enum { + FEEDER_ROOT, + FEEDER_FMT, + FEEDER_MIXER, + FEEDER_RATE, + FEEDER_FILTER, + FEEDER_VOLUME, + FEEDER_SWAPLR, + FEEDER_LAST +}; #define FEEDRATE_SRC 1 #define FEEDRATE_DST 2 +#define FEEDVOL_CLASS 1 + +#define FEEDRATE_RATEMIN 1 +#define FEEDRATE_RATEMAX 2016000 /* 48000 * 42 */ +#define FEEDRATE_MIN 1 +#define FEEDRATE_MAX 0x7fffff /* sign 24bit ~ 8ghz ! */ + +#define FEEDRATE_ROUNDHZ 25 +#define FEEDRATE_ROUNDHZ_MIN 0 +#define FEEDRATE_ROUNDHZ_MAX 500 + +/* + * Default buffer size for feeder processing. + * + * Big = less sndbuf_feed(), more memory usage. + * Small = aggresive sndbuf_feed() (perhaps too much), less memory usage. + */ +#define FEEDBUFSZ 16384 +#define FEEDBUFSZ_MIN 2048 +#define FEEDBUFSZ_MAX 131072 + +extern int feeder_rate_min; +extern int feeder_rate_max; +extern int feeder_rate_round; +extern int feeder_buffersize; --- sys/dev/sound/pcm/feeder_fmt.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/feeder_fmt.c Thu Jul 12 12:04:19 2007 @@ -23,7 +23,9 @@ * 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. - * + */ + +/* * *New* and rewritten soft format converter, supporting 24/32bit pcm data, * simplified and optimized. * @@ -37,90 +39,89 @@ */ #include +#include +#include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_fmt.c,v 1.14.2.2 2006/01/29 02:27:28 ariff Exp $"); - -MALLOC_DEFINE(M_FMTFEEDER, "fmtfeed", "pcm format feeder"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_fmt.c,v 1.23 2007/06/02 13:07:44 joel Exp $"); -#define FEEDBUFSZ 8192 -#define FEEDBUF24SZ 8190 - -#define FMT_TRACE(x...) /* printf(x) */ -#define FMT_TEST(x, y...) /* if (x) FMT_TRACE(y) */ -#define FMT_ALIGNBYTE(x) /* x */ - -/* - * Sign inverted ulaw/alaw -> 8 table - */ -static uint8_t ulaw_to_s8_tbl[] = { - 131, 135, 139, 143, 147, 151, 155, 159, - 163, 167, 171, 175, 179, 183, 187, 191, - 194, 196, 198, 200, 202, 204, 206, 208, - 210, 212, 214, 216, 218, 220, 222, 224, - 226, 227, 228, 229, 230, 231, 232, 233, - 234, 235, 236, 237, 238, 239, 240, 241, - 241, 242, 242, 243, 243, 244, 244, 245, - 245, 246, 246, 247, 247, 248, 248, 249, - 249, 249, 250, 250, 250, 250, 251, 251, - 251, 251, 252, 252, 252, 252, 253, 253, - 253, 253, 253, 253, 254, 254, 254, 254, - 254, 254, 254, 254, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 125, 121, 117, 113, 109, 105, 101, 97, - 93, 89, 85, 81, 77, 73, 69, 65, - 62, 60, 58, 56, 54, 52, 50, 48, - 46, 44, 42, 40, 38, 36, 34, 32, - 30, 29, 28, 27, 26, 25, 24, 23, - 22, 21, 20, 19, 18, 17, 16, 15, - 15, 14, 14, 13, 13, 12, 12, 11, - 11, 10, 10, 9, 9, 8, 8, 7, - 7, 7, 6, 6, 6, 6, 5, 5, - 5, 5, 4, 4, 4, 4, 3, 3, - 3, 3, 3, 3, 2, 2, 2, 2, - 2, 2, 2, 2, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -}; - -static uint8_t alaw_to_s8_tbl[] = { - 236, 237, 234, 235, 240, 241, 238, 239, - 228, 229, 226, 227, 232, 233, 230, 231, - 246, 246, 245, 245, 248, 248, 247, 247, - 242, 242, 241, 241, 244, 244, 243, 243, - 171, 175, 163, 167, 187, 191, 179, 183, - 139, 143, 131, 135, 155, 159, 147, 151, - 214, 216, 210, 212, 222, 224, 218, 220, - 198, 200, 194, 196, 206, 208, 202, 204, - 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 251, 251, 251, 251, 252, 252, 252, 252, - 249, 249, 249, 249, 250, 250, 250, 250, - 254, 254, 254, 254, 254, 254, 254, 254, - 253, 253, 253, 253, 253, 253, 253, 253, - 20, 19, 22, 21, 16, 15, 18, 17, - 28, 27, 30, 29, 24, 23, 26, 25, - 10, 10, 11, 11, 8, 8, 9, 9, - 14, 14, 15, 15, 12, 12, 13, 13, - 85, 81, 93, 89, 69, 65, 77, 73, - 117, 113, 125, 121, 101, 97, 109, 105, - 42, 40, 46, 44, 34, 32, 38, 36, - 58, 56, 62, 60, 50, 48, 54, 52, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 5, 5, 5, 5, 4, 4, 4, 4, - 7, 7, 7, 7, 6, 6, 6, 6, - 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, +static int feeder_fmt_stereodownmix = 0; +TUNABLE_INT("hw.snd.feeder_fmt_stereodownmix", &feeder_fmt_stereodownmix); +#ifdef SND_DEBUG +SYSCTL_INT(_hw_snd, OID_AUTO, feeder_fmt_stereodownmix, CTLFLAG_RW, + &feeder_fmt_stereodownmix, 1, "averaging stereo downmix"); +#endif + +#define FEEDFMT_RESERVOIR (PCM_32_BPS * SND_CHN_MAX) + +static uint8_t ulaw_to_u8_tbl[] = { + 3, 7, 11, 15, 19, 23, 27, 31, + 35, 39, 43, 47, 51, 55, 59, 63, + 66, 68, 70, 72, 74, 76, 78, 80, + 82, 84, 86, 88, 90, 92, 94, 96, + 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, + 113, 114, 114, 115, 115, 116, 116, 117, + 117, 118, 118, 119, 119, 120, 120, 121, + 121, 121, 122, 122, 122, 122, 123, 123, + 123, 123, 124, 124, 124, 124, 125, 125, + 125, 125, 125, 125, 126, 126, 126, 126, + 126, 126, 126, 126, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 253, 249, 245, 241, 237, 233, 229, 225, + 221, 217, 213, 209, 205, 201, 197, 193, + 190, 188, 186, 184, 182, 180, 178, 176, + 174, 172, 170, 168, 166, 164, 162, 160, + 158, 157, 156, 155, 154, 153, 152, 151, + 150, 149, 148, 147, 146, 145, 144, 143, + 143, 142, 142, 141, 141, 140, 140, 139, + 139, 138, 138, 137, 137, 136, 136, 135, + 135, 135, 134, 134, 134, 134, 133, 133, + 133, 133, 132, 132, 132, 132, 131, 131, + 131, 131, 131, 131, 130, 130, 130, 130, + 130, 130, 130, 130, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, +}; + +static uint8_t alaw_to_u8_tbl[] = { + 108, 109, 106, 107, 112, 113, 110, 111, + 100, 101, 98, 99, 104, 105, 102, 103, + 118, 118, 117, 117, 120, 120, 119, 119, + 114, 114, 113, 113, 116, 116, 115, 115, + 43, 47, 35, 39, 59, 63, 51, 55, + 11, 15, 3, 7, 27, 31, 19, 23, + 86, 88, 82, 84, 94, 96, 90, 92, + 70, 72, 66, 68, 78, 80, 74, 76, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 123, 123, 123, 123, 124, 124, 124, 124, + 121, 121, 121, 121, 122, 122, 122, 122, + 126, 126, 126, 126, 126, 126, 126, 126, + 125, 125, 125, 125, 125, 125, 125, 125, + 148, 147, 150, 149, 144, 143, 146, 145, + 156, 155, 158, 157, 152, 151, 154, 153, + 138, 138, 139, 139, 136, 136, 137, 137, + 142, 142, 143, 143, 140, 140, 141, 141, + 213, 209, 221, 217, 197, 193, 205, 201, + 245, 241, 253, 249, 229, 225, 237, 233, + 170, 168, 174, 172, 162, 160, 166, 164, + 186, 184, 190, 188, 178, 176, 182, 180, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 133, 133, 133, 133, 132, 132, 132, 132, + 135, 135, 135, 135, 134, 134, 134, 134, + 130, 130, 130, 130, 130, 130, 130, 130, + 131, 131, 131, 131, 131, 131, 131, 131, }; static uint8_t u8_to_ulaw_tbl[] = { @@ -194,371 +195,660 @@ }; static int -feed_table_u8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_table_8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int j, k = FEEDER_FEED(f->source, c, b, count, source); + int j, sign, k; uint8_t *tbl = (uint8_t *)f->data; - + + if (count < PCM_8_BPS) + return (0); + + k = FEEDER_FEED(f->source, c, b, count, source); + if (k < PCM_8_BPS) + return (0); + j = k; - while (j > 0) { + sign = (f->desc->out & AFMT_SIGNED) ? 0x80 : 0x00; + + do { j--; - b[j] = tbl[b[j]] ^ 0x80; - } - return k; + b[j] = tbl[b[j]] ^ sign; + } while (j != 0); + + return (k); } static int -feed_table_s16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_table_16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int i, j, sign, k; uint8_t *tbl = (uint8_t *)f->data; - + + if (count < PCM_16_BPS) + return (0); + + k = FEEDER_FEED(f->source, c, b, count >> 1, source); + if (k < PCM_8_BPS) + return (0); + i = k; k <<= 1; j = k; - while (i > 0) { - b[--j] = tbl[b[--i]]; - b[--j] = 0; + sign = (f->desc->out & AFMT_SIGNED) ? 0x80 : 0x00; + + if (f->desc->out & AFMT_BIGENDIAN) { + do { + b[--j] = 0; + b[--j] = tbl[b[--i]] ^ sign; + } while (i != 0); + } else { + do { + b[--j] = tbl[b[--i]] ^ sign; + b[--j] = 0; + } while (i != 0); } - return k; + + return (k); } static int feed_table_xlaw(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int j, k = FEEDER_FEED(f->source, c, b, count, source); + int j, sign, k; uint8_t *tbl = (uint8_t *)f->data; - j = k; - while (j > 0) { + if (count < PCM_8_BPS) + return (0); + + k = FEEDER_FEED(f->source, c, b, count, source); + if (k < PCM_8_BPS) + return (0); + + j = k ; + sign = (f->desc->in & AFMT_SIGNED) ? 0x80 : 0x00; + + do { j--; - b[j] = tbl[b[j]]; - } - return k; + b[j] = tbl[b[j] ^ sign]; + } while (j != 0); + + return (k); } -static struct pcm_feederdesc feeder_ulawtou8_desc[] = { +static struct pcm_feederdesc feeder_ulawto8_desc[] = { {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_ulawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_u8), +static kobj_method_t feeder_ulawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), {0, 0} }; -FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_s8_tbl); +FEEDER_DECLARE(feeder_ulawto8, 0, ulaw_to_u8_tbl); -static struct pcm_feederdesc feeder_alawtou8_desc[] = { +static struct pcm_feederdesc feeder_alawto8_desc[] = { {FEEDER_FMT, AFMT_A_LAW, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_alawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_u8), +static kobj_method_t feeder_alawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), {0, 0} }; -FEEDER_DECLARE(feeder_alawtou8, 0, alaw_to_s8_tbl); +FEEDER_DECLARE(feeder_alawto8, 0, alaw_to_u8_tbl); -static struct pcm_feederdesc feeder_ulawtos16le_desc[] = { +static struct pcm_feederdesc feeder_ulawto16_desc[] = { {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_MU_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_ulawtos16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_s16le), +static kobj_method_t feeder_ulawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), {0, 0} }; -FEEDER_DECLARE(feeder_ulawtos16le, 0, ulaw_to_s8_tbl); +FEEDER_DECLARE(feeder_ulawto16, 0, ulaw_to_u8_tbl); -static struct pcm_feederdesc feeder_alawtos16le_desc[] = { +static struct pcm_feederdesc feeder_alawto16_desc[] = { {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_A_LAW|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_alawtos16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_s16le), +static kobj_method_t feeder_alawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), {0, 0} }; -FEEDER_DECLARE(feeder_alawtos16le, 0, alaw_to_s8_tbl); +FEEDER_DECLARE(feeder_alawto16, 0, alaw_to_u8_tbl); -static struct pcm_feederdesc feeder_u8toulaw_desc[] = { +static struct pcm_feederdesc feeder_8toulaw_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, - {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_MU_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_u8toulaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_xlaw), +static kobj_method_t feeder_8toulaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_xlaw), {0, 0} }; -FEEDER_DECLARE(feeder_u8toulaw, 0, u8_to_ulaw_tbl); +FEEDER_DECLARE(feeder_8toulaw, 0, u8_to_ulaw_tbl); -static struct pcm_feederdesc feeder_u8toalaw_desc[] = { +static struct pcm_feederdesc feeder_8toalaw_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_A_LAW, 0}, - {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_A_LAW|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_A_LAW, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_u8toalaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table_xlaw), +static kobj_method_t feeder_8toalaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_xlaw), {0, 0} }; -FEEDER_DECLARE(feeder_u8toalaw, 0, u8_to_alaw_tbl); +FEEDER_DECLARE(feeder_8toalaw, 0, u8_to_alaw_tbl); /* - * Conversion rules:- - * 1. BE -> LE - * 2. if fmt == u8 , u8 -> s8 (economical) - * 3. Xle -> 16le - * 4. if fmt != u8 && fmt == u16le , u16le -> s16le - * 4. s16le mono -> s16le stereo - * * All conversion done in byte level to preserve endianess. */ -static int -feed_common_init(struct pcm_feeder *f) -{ - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT|M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; -} - -static int -feed_common_free(struct pcm_feeder *f) -{ - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; -} +#define FEEDFMT_SWAP_SIGN(f) (((((f)->desc->in & AFMT_SIGNED) == 0) != \ + (((f)->desc->out & AFMT_SIGNED) == 0)) \ + ? 0x80 : 0x00) /* * Bit conversion */ + +#define FBIT_DATA(i, o, c) ((intptr_t)((((c) & 0x3f) << 8) | \ + (((i) & 0xf) << 4) | ((o) & 0xf))) +#define FBIT_OUTBPS(m) ((m) & 0xf) +#define FBIT_INBPS(m) FBIT_OUTBPS((m) >> 4) +#define FBIT_CHANNELS(m) (((m) >> 8) & 0x3f) + static int -feed_8to16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) +feed_updownbit_init(struct pcm_feeder *f) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); - - i = k; - k <<= 1; - j = k; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = 0; - } - return k; + int ibps, obps, channels; + + if (f->desc->in == f->desc->out || (f->desc->in & AFMT_STEREO) != + (f->desc->out & AFMT_STEREO)) + return (-1); + + channels = (f->desc->in & AFMT_STEREO) ? 2 : 1; + + if (f->desc->in & AFMT_32BIT) + ibps = PCM_32_BPS; + else if (f->desc->in & AFMT_24BIT) + ibps = PCM_24_BPS; + else if (f->desc->in & AFMT_16BIT) + ibps = PCM_16_BPS; + else + ibps = PCM_8_BPS; + + if (f->desc->out & AFMT_32BIT) + obps = PCM_32_BPS; + else if (f->desc->out & AFMT_24BIT) + obps = PCM_24_BPS; + else if (f->desc->out & AFMT_16BIT) + obps = PCM_16_BPS; + else + obps = PCM_8_BPS; + + f->data = (void *)FBIT_DATA(ibps, obps, channels); + + return (0); } -static struct pcm_feederdesc feeder_8to16le_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, - {0, 0, 0, 0}, -}; -static kobj_method_t feeder_8to16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_8to16le), - {0, 0} -}; -FEEDER_DECLARE(feeder_8to16le, 0, NULL); static int -feed_16leto8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_upbit(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; - - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); - if (k < 2) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); - i = k; - j = k >> 1; - while (i > 0) { - b[--j] = src[--i]; - i--; + int i, j, k, sign, ibps, ialign, obps, oalign, pad; + uint8_t *src, *dst; + + ibps = FBIT_INBPS((intptr_t)f->data); + obps = FBIT_OUTBPS((intptr_t)f->data); + + ialign = ibps * FBIT_CHANNELS((intptr_t)f->data); + oalign = obps * FBIT_CHANNELS((intptr_t)f->data); + + if (count < oalign) + return (0); + + k = FEEDER_FEED(f->source, c, b, (count / oalign) * ialign, source); + if (k < ialign) + return (0); + + k -= k % ialign; + j = (k / ibps) * obps; + pad = obps - ibps; + src = b + k; + dst = b + j; + sign = FEEDFMT_SWAP_SIGN(f); + + if (f->desc->out & AFMT_BIGENDIAN) { + do { + i = pad; + do { + *--dst = 0; + } while (--i != 0); + i = ibps; + while (--i != 0) + *--dst = *--src; + *--dst = *--src ^ sign; + } while (dst != b); + } else { + do { + *--dst = *--src ^ sign; + i = ibps; + while (--i != 0) + *--dst = *--src; + i = pad; + do { + *--dst = 0; + } while (--i != 0); + } while (dst != b); } - return k >> 1; + + return (j); } -static struct pcm_feederdesc feeder_16leto8_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S8, 0}, - {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, - {0, 0, 0, 0}, -}; -static kobj_method_t feeder_16leto8_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_16leto8), + +static struct pcm_feederdesc feeder_8to16_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to16_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_8to16, 0, NULL); + +static struct pcm_feederdesc feeder_8to24_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to24_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_8to24, 0, NULL); + +static struct pcm_feederdesc feeder_8to32_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to32_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), {0, 0} }; -FEEDER_DECLARE(feeder_16leto8, 0, NULL); - -static int -feed_16leto24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k; +FEEDER_DECLARE(feeder_8to32, 0, NULL); - k = (count / 3) << 1; - k = FEEDER_FEED(f->source, c, b, k, source); - if (k < 2) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); - i = k; - j = (k >> 1) * 3; - k = j; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = b[--i]; - b[--j] = 0; - } - return k; -} -static struct pcm_feederdesc feeder_16leto24le_desc[] = { +static struct pcm_feederdesc feeder_16to24_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U24_LE, 0}, - {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S24_LE, 0}, - {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, - {0, 0, 0, 0}, -}; -static kobj_method_t feeder_16leto24le_methods[] = { - KOBJMETHOD(feeder_feed, feed_16leto24le), + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to24_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), {0, 0} }; -FEEDER_DECLARE(feeder_16leto24le, 0, NULL); +FEEDER_DECLARE(feeder_16to24, 0, NULL); -static int -feed_24leto16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k; - uint8_t *src = (uint8_t *)f->data; +static struct pcm_feederdesc feeder_16to32_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to32_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_16to32, 0, NULL); + +static struct pcm_feederdesc feeder_24to32_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to32_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_24to32, 0, NULL); + +static int +feed_downbit(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, k, sign, be, ibps, ialign, obps, oalign,dump; + uint8_t *src, *dst, *end; + uint8_t reservoir[FEEDFMT_RESERVOIR]; + + ibps = FBIT_INBPS((intptr_t)f->data); + obps = FBIT_OUTBPS((intptr_t)f->data); + + ialign = ibps * FBIT_CHANNELS((intptr_t)f->data); + oalign = obps * FBIT_CHANNELS((intptr_t)f->data); + + if (count < oalign) + return (0); + + dst = b; + dump = ibps - obps; + sign = FEEDFMT_SWAP_SIGN(f); + be = (f->desc->in & AFMT_BIGENDIAN) ? 1 : 0; + k = count - (count % oalign); + + do { + if (k < oalign) + break; + + if (k < ialign) { + src = reservoir; + j = ialign; + } else { + src = dst; + j = k; + } + + j = FEEDER_FEED(f->source, c, src, j - (j % ialign), source); + if (j < ialign) + break; + + j -= j % ialign; + j *= obps; + j /= ibps; + end = dst + j; + + if (be != 0) { + do { + *dst++ = *src++ ^ sign; + i = obps; + while (--i != 0) + *dst++ = *src++; + src += dump; + } while (dst != end); + } else { + do { + src += dump; + i = obps; + while (--i != 0) + *dst++ = *src++; + *dst++ = *src++ ^ sign; + } while (dst != end); + } - k = (count * 3) >> 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); - if (k < 3) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(k -= k % 3); - i = (k / 3) << 1; - j = i; - while (i > 0) { - b[--i] = src[--k]; - b[--i] = src[--k]; - k--; - } - return j; + k -= j; + } while (k != 0); + + return (dst - b); } -static struct pcm_feederdesc feeder_24leto16le_desc[] = { - {FEEDER_FMT, AFMT_U24_LE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S24_LE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, - {0, 0, 0, 0}, -}; -static kobj_method_t feeder_24leto16le_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_24leto16le), + +static struct pcm_feederdesc feeder_16to8_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to8_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_16to8, 0, NULL); + +static struct pcm_feederdesc feeder_24to8_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to8_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), {0, 0} }; -FEEDER_DECLARE(feeder_24leto16le, 1, NULL); +FEEDER_DECLARE(feeder_24to8, 0, NULL); -static int -feed_16leto32le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); - if (k < 2) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); - i = k; - j = k << 1; - k = j; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = b[--i]; - b[--j] = 0; - b[--j] = 0; - } - return k; -} -static struct pcm_feederdesc feeder_16leto32le_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U32_LE, 0}, - {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S32_LE, 0}, - {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, - {0, 0, 0, 0}, -}; -static kobj_method_t feeder_16leto32le_methods[] = { - KOBJMETHOD(feeder_feed, feed_16leto32le), +static struct pcm_feederdesc feeder_24to16_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to16_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_24to16, 0, NULL); + +static struct pcm_feederdesc feeder_32to8_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to8_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), {0, 0} }; -FEEDER_DECLARE(feeder_16leto32le, 0, NULL); +FEEDER_DECLARE(feeder_32to8, 0, NULL); -static int -feed_32leto16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k; - uint8_t *src = (uint8_t *)f->data; - - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); - if (k < 4) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~3); - i = k; - k >>= 1; - j = k; - while (i > 0) { - b[--j] = src[--i]; - b[--j] = src[--i]; - i -= 2; - } - return k; -} -static struct pcm_feederdesc feeder_32leto16le_desc[] = { +static struct pcm_feederdesc feeder_32to16_desc[] = { {FEEDER_FMT, AFMT_U32_LE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S32_LE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, - {0, 0, 0, 0}, -}; -static kobj_method_t feeder_32leto16le_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_32leto16le), + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to16_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to16, 0, NULL); + +static struct pcm_feederdesc feeder_32to24_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to24_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), {0, 0} }; -FEEDER_DECLARE(feeder_32leto16le, 1, NULL); +FEEDER_DECLARE(feeder_32to24, 0, NULL); /* * Bit conversion end */ @@ -567,155 +857,88 @@ * Channel conversion (mono -> stereo) */ static int -feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_monotostereo(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); + int bps, i, j, k, l; + uint8_t v; - i = k; - j = k << 1; - while (i > 0) { - b[--j] = b[--i]; - b[--j] = b[i]; - } - return k << 1; + bps = (int)((intptr_t)f->data); + if (count < (bps << 1)) + return (0); + + j = FEEDER_FEED(f->source, c, b, (count - (count % bps)) >> 1, source); + if (j < bps) + return (0); + + j -= j % bps; + i = j << 1; + l = i; + + do { + k = bps; + do { + v = b[--j]; + b[--i] = v; + b[i - bps] = v; + } while (--k != 0); + i -= bps; + } while (i != 0); + + return (l); } + static struct pcm_feederdesc feeder_monotostereo8_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_U8|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S8, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_MU_LAW | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_A_LAW | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo8_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo8), + KOBJMETHOD(feeder_feed, feed_monotostereo), {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo8, 0, NULL); - -static int -feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); - uint8_t l, m; +FEEDER_DECLARE(feeder_monotostereo8, 0, (void *)PCM_8_BPS); - if (k < 2) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); - i = k; - j = k << 1; - while (i > 0) { - l = b[--i]; - m = b[--i]; - b[--j] = l; - b[--j] = m; - b[--j] = l; - b[--j] = m; - } - return k << 1; -} static struct pcm_feederdesc feeder_monotostereo16_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo16_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo16), + KOBJMETHOD(feeder_feed, feed_monotostereo), {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo16, 0, NULL); +FEEDER_DECLARE(feeder_monotostereo16, 0, (void *)PCM_16_BPS); -static int -feed_monotostereo24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); - uint8_t l, m, n; - - if (k < 3) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(k -= k % 3); - i = k; - j = k << 1; - while (i > 0) { - l = b[--i]; - m = b[--i]; - n = b[--i]; - b[--j] = l; - b[--j] = m; - b[--j] = n; - b[--j] = l; - b[--j] = m; - b[--j] = n; - } - return k << 1; -} static struct pcm_feederdesc feeder_monotostereo24_desc[] = { - {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_BE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo24_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo24), + KOBJMETHOD(feeder_feed, feed_monotostereo), {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo24, 0, NULL); +FEEDER_DECLARE(feeder_monotostereo24, 0, (void *)PCM_24_BPS); -static int -feed_monotostereo32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k = FEEDER_FEED(f->source, c, b, count >> 1, source); - uint8_t l, m, n, o; - - if (k < 4) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~3); - i = k; - j = k << 1; - while (i > 0) { - l = b[--i]; - m = b[--i]; - n = b[--i]; - o = b[--i]; - b[--j] = l; - b[--j] = m; - b[--j] = n; - b[--j] = o; - b[--j] = l; - b[--j] = m; - b[--j] = n; - b[--j] = o; - } - return k << 1; -} static struct pcm_feederdesc feeder_monotostereo32_desc[] = { - {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_LE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_BE|AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo32_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo32), + KOBJMETHOD(feeder_feed, feed_monotostereo), {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo32, 0, NULL); +FEEDER_DECLARE(feeder_monotostereo32, 0, (void *)PCM_32_BPS); /* * Channel conversion (mono -> stereo) end */ @@ -723,162 +946,218 @@ /* * Channel conversion (stereo -> mono) */ +#define FEEDER_FMT_STEREODOWNMIX(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static void \ +SIGNS##FMTBIT##ENDIANS##e_stereodownmix(uint8_t *dst, uint8_t *sx, uint8_t *sy) \ +{ \ + intpcm_t v; \ + \ + v = (INTPCM##FMTBIT##_T(PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sx)) + \ + PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sy)) >> 1; \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(dst, v); \ +} + +FEEDER_FMT_STEREODOWNMIX(8, S, s, N, n) +FEEDER_FMT_STEREODOWNMIX(16, S, s, L, l) +FEEDER_FMT_STEREODOWNMIX(24, S, s, L, l) +FEEDER_FMT_STEREODOWNMIX(32, S, s, L, l) +FEEDER_FMT_STEREODOWNMIX(16, S, s, B, b) +FEEDER_FMT_STEREODOWNMIX(24, S, s, B, b) +FEEDER_FMT_STEREODOWNMIX(32, S, s, B, b) +FEEDER_FMT_STEREODOWNMIX(8, U, u, N, n) +FEEDER_FMT_STEREODOWNMIX(16, U, u, L, l) +FEEDER_FMT_STEREODOWNMIX(24, U, u, L, l) +FEEDER_FMT_STEREODOWNMIX(32, U, u, L, l) +FEEDER_FMT_STEREODOWNMIX(16, U, u, B, b) +FEEDER_FMT_STEREODOWNMIX(24, U, u, B, b) +FEEDER_FMT_STEREODOWNMIX(32, U, u, B, b) + +static void +ulaw_stereodownmix(uint8_t *dst, uint8_t *sx, uint8_t *sy) +{ + uint8_t v; + + v = ((uint32_t)ulaw_to_u8_tbl[*sx] + ulaw_to_u8_tbl[*sy]) >> 1; + *dst = u8_to_ulaw_tbl[v]; +} + +static void +alaw_stereodownmix(uint8_t *dst, uint8_t *sx, uint8_t *sy) +{ + uint8_t v; + + v = ((uint32_t)alaw_to_u8_tbl[*sx] + alaw_to_u8_tbl[*sy]) >> 1; + *dst = u8_to_alaw_tbl[v]; +} + +typedef void (*feed_fmt_stereodownmix_filter)(uint8_t *, + uint8_t *, uint8_t *); + +struct feed_fmt_stereodownmix_info { + uint32_t format; + int bps; + feed_fmt_stereodownmix_filter func[2]; +}; + +static struct feed_fmt_stereodownmix_info feed_fmt_stereodownmix_tbl[] = { + { AFMT_S8, PCM_8_BPS, { NULL, s8ne_stereodownmix }}, + { AFMT_S16_LE, PCM_16_BPS, { NULL, s16le_stereodownmix }}, + { AFMT_S16_BE, PCM_16_BPS, { NULL, s16be_stereodownmix }}, + { AFMT_S24_LE, PCM_24_BPS, { NULL, s24le_stereodownmix }}, + { AFMT_S24_BE, PCM_24_BPS, { NULL, s24be_stereodownmix }}, + { AFMT_S32_LE, PCM_32_BPS, { NULL, s32le_stereodownmix }}, + { AFMT_S32_BE, PCM_32_BPS, { NULL, s32be_stereodownmix }}, + { AFMT_U8, PCM_8_BPS, { NULL, u8ne_stereodownmix }}, + { AFMT_A_LAW, PCM_8_BPS, { NULL, alaw_stereodownmix }}, + { AFMT_MU_LAW, PCM_8_BPS, { NULL, ulaw_stereodownmix }}, + { AFMT_U16_LE, PCM_16_BPS, { NULL, u16le_stereodownmix }}, + { AFMT_U16_BE, PCM_16_BPS, { NULL, u16be_stereodownmix }}, + { AFMT_U24_LE, PCM_24_BPS, { NULL, u24le_stereodownmix }}, + { AFMT_U24_BE, PCM_24_BPS, { NULL, u24be_stereodownmix }}, + { AFMT_U32_LE, PCM_32_BPS, { NULL, u32le_stereodownmix }}, + { AFMT_U32_BE, PCM_32_BPS, { NULL, u32be_stereodownmix }}, +}; + +#define FSM_DATA(i, j) ((intptr_t)((((i) & 0x3f) << 1) | ((j) & 0x1))) +#define FSM_INFOIDX(m) (((m) >> 1) & 0x3f) +#define FSM_FUNCIDX(m) ((m) & 0x1) + static int -feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) +feed_stereotomono_init(struct pcm_feeder *f) { - int i, j, k; - uint8_t *src = (uint8_t *)f->data; + int i, funcidx; - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); - if (k < 2) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 1, "%s: Bytes not 8bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~1); - i = k >> 1; - j = i; - while (i > 0) { - k--; - b[--i] = src[--k]; + if (!(f->desc->in & AFMT_STEREO) || (f->desc->out & AFMT_STEREO)) + return (-1); + + funcidx = (feeder_fmt_stereodownmix != 0) ? 1 : 0; + + for (i = 0; i < sizeof(feed_fmt_stereodownmix_tbl) / + sizeof(feed_fmt_stereodownmix_tbl[0]); i++) { + if (f->desc->out == feed_fmt_stereodownmix_tbl[i].format) { + f->data = (void *)FSM_DATA(i, funcidx); + return (0); + } } - return j; + + return (-1); } + +static int +feed_stereotomono(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_fmt_stereodownmix_info *info; + feed_fmt_stereodownmix_filter stereodownmix; + int i, j, k, ibps, obps; + uint8_t *src, *dst, *end; + uint8_t reservoir[FEEDFMT_RESERVOIR]; + + info = &feed_fmt_stereodownmix_tbl[FSM_INFOIDX((intptr_t)f->data)]; + obps = info->bps; + + if (count < obps) + return (0); + + stereodownmix = info->func[FSM_FUNCIDX((intptr_t)f->data)]; + ibps = obps << 1; + dst = b; + k = count - (count % obps); + + do { + if (k < obps) + break; + + if (k < ibps) { + src = reservoir; + j = ibps; + } else { + src = dst; + j = k; + } + + j = FEEDER_FEED(f->source, c, src, j - (j % ibps), source); + if (j < ibps) + break; + + j -= j % ibps; + j >>= 1; + end = dst + j; + + if (stereodownmix != NULL) { + do { + stereodownmix(dst, src, src + obps); + dst += obps; + src += ibps; + } while (dst != end); + } else { + do { + i = obps; + do { + *dst++ = *src++; + } while (--i != 0); + src += obps; + } while (dst != end); + } + + k -= j; + } while (k != 0); + + return (dst - b); +} + static struct pcm_feederdesc feeder_stereotomono8_desc[] = { - {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_A_LAW, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono8_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_stereotomono8), + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), {0, 0} }; FEEDER_DECLARE(feeder_stereotomono8, 0, NULL); -static int -feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k; - uint8_t *src = (uint8_t *)f->data; - - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); - if (k < 4) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 3, "%s: Bytes not 16bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~3); - i = k >> 1; - j = i; - while (i > 0) { - k -= 2; - b[--i] = src[--k]; - b[--i] = src[--k]; - } - return j; -} static struct pcm_feederdesc feeder_stereotomono16_desc[] = { - {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U16_BE, 0}, - {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono16_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_stereotomono16), + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), {0, 0} }; FEEDER_DECLARE(feeder_stereotomono16, 0, NULL); -static int -feed_stereotomono24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k; - uint8_t *src = (uint8_t *)f->data; - - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUF24SZ), source); - if (k < 6) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k % 6, "%s: Bytes not 24bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k -= k % 6); - i = k >> 1; - j = i; - while (i > 0) { - k -= 3; - b[--i] = src[--k]; - b[--i] = src[--k]; - b[--i] = src[--k]; - } - return j; -} static struct pcm_feederdesc feeder_stereotomono24_desc[] = { - {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U24_LE, 0}, - {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S24_LE, 0}, - {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U24_BE, 0}, - {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono24_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_stereotomono24), + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), {0, 0} }; FEEDER_DECLARE(feeder_stereotomono24, 0, NULL); -static int -feed_stereotomono32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j, k; - uint8_t *src = (uint8_t *)f->data; - - k = count << 1; - k = FEEDER_FEED(f->source, c, src, min(k, FEEDBUFSZ), source); - if (k < 8) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, k); - return 0; - } - FMT_TEST(k & 7, "%s: Bytes not 32bit (stereo) aligned.\n", __func__); - FMT_ALIGNBYTE(k &= ~7); - i = k >> 1; - j = i; - while (i > 0) { - k -= 4; - b[--i] = src[--k]; - b[--i] = src[--k]; - b[--i] = src[--k]; - b[--i] = src[--k]; - } - return j; -} static struct pcm_feederdesc feeder_stereotomono32_desc[] = { - {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U32_LE, 0}, - {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S32_LE, 0}, - {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U32_BE, 0}, - {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono32_methods[] = { - KOBJMETHOD(feeder_init, feed_common_init), - KOBJMETHOD(feeder_free, feed_common_free), - KOBJMETHOD(feeder_feed, feed_stereotomono32), + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), {0, 0} }; FEEDER_DECLARE(feeder_stereotomono32, 0, NULL); @@ -890,253 +1169,268 @@ * Sign conversion */ static int -feed_sign8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_sign(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); + int i, j, bps, ofs; + + bps = (int)((intptr_t)f->data); + if (count < bps) + return (0); + + i = FEEDER_FEED(f->source, c, b, count - (count % bps), source); + if (i < bps) + return (0); + + i -= i % bps; + j = i; + ofs = (f->desc->in & AFMT_BIGENDIAN) ? bps : 1; - i = j; - while (i > 0) - b[--i] ^= 0x80; - return j; + do { + b[i - ofs] ^= 0x80; + i -= bps; + } while (i != 0); + + return (j); } static struct pcm_feederdesc feeder_sign8_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_S8, 0}, - {FEEDER_FMT, AFMT_U8|AFMT_STEREO, AFMT_S8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S8, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_S8|AFMT_STEREO, AFMT_U8|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_sign8_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign8), + KOBJMETHOD(feeder_feed, feed_sign), {0, 0} }; -FEEDER_DECLARE(feeder_sign8, 0, NULL); +FEEDER_DECLARE(feeder_sign8, 0, (void *)PCM_8_BPS); -static int -feed_sign16le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j = FEEDER_FEED(f->source, c, b, count, source); - - if (j < 2) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, j); - return 0; - } - FMT_TEST(j & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~1); - i = j; - while (i > 0) { - b[--i] ^= 0x80; - i--; - } - return j; -} -static struct pcm_feederdesc feeder_sign16le_desc[] = { +static struct pcm_feederdesc feeder_sign16_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_sign16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign16le), +static kobj_method_t feeder_sign16_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), {0, 0} }; -FEEDER_DECLARE(feeder_sign16le, 0, NULL); +FEEDER_DECLARE(feeder_sign16, 0, (void *)PCM_16_BPS); -static int -feed_sign24le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j = FEEDER_FEED(f->source, c, b, count, source); - - if (j < 3) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, j); - return 0; - } - FMT_TEST(j % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(j -= j % 3); - i = j; - while (i > 0) { - b[--i] ^= 0x80; - i -= 2; - } - return j; -} -static struct pcm_feederdesc feeder_sign24le_desc[] = { +static struct pcm_feederdesc feeder_sign24_desc[] = { {FEEDER_FMT, AFMT_U24_LE, AFMT_S24_LE, 0}, - {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S24_LE, AFMT_U24_LE, 0}, - {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_sign24le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign24le), +static kobj_method_t feeder_sign24_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), {0, 0} }; -FEEDER_DECLARE(feeder_sign24le, 0, NULL); +FEEDER_DECLARE(feeder_sign24, 0, (void *)PCM_24_BPS); -static int -feed_sign32le(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j = FEEDER_FEED(f->source, c, b, count, source); - - if (j < 4) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, j); - return 0; - } - FMT_TEST(j & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~3); - i = j; - while (i > 0) { - b[--i] ^= 0x80; - i -= 3; - } - return j; -} -static struct pcm_feederdesc feeder_sign32le_desc[] = { +static struct pcm_feederdesc feeder_sign32_desc[] = { {FEEDER_FMT, AFMT_U32_LE, AFMT_S32_LE, 0}, - {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S32_LE, AFMT_U32_LE, 0}, - {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_sign32le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign32le), +static kobj_method_t feeder_sign32_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), {0, 0} }; -FEEDER_DECLARE(feeder_sign32le, 0, NULL); -/* - * Sign conversion end. - */ +FEEDER_DECLARE(feeder_sign32, 0, (void *)PCM_32_BPS); /* - * Endian conversion. + * Endian conversion */ static int -feed_endian16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, +feed_endian(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { - int i, j = FEEDER_FEED(f->source, c, b, count, source); - uint8_t v; + int i, j, k, bps; + uint8_t *buf, v; - if (j < 2) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, j); - return 0; - } - FMT_TEST(j & 1, "%s: Bytes not 16bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~1); - i = j; - while (i > 0) { - v = b[--i]; - b[i] = b[i - 1]; - b[--i] = v; - } - return j; + bps = (int)((intptr_t)f->data); + if (count < bps) + return (0); + + k = FEEDER_FEED(f->source, c, b, count - (count % bps), source); + if (k < bps) + return (0); + + k -= k % bps; + j = bps >> 1; + buf = b + k; + + do { + buf -= bps; + i = j; + do { + v = buf[--i]; + buf[i] = buf[bps - i - 1]; + buf[bps - i - 1] = v; + } while (i != 0); + } while (buf != b); + + return (k); } static struct pcm_feederdesc feeder_endian16_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, - {FEEDER_FMT, AFMT_U16_LE|AFMT_STEREO, AFMT_U16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, - {FEEDER_FMT, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U16_BE|AFMT_STEREO, AFMT_U16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S16_BE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_endian16_methods[] = { - KOBJMETHOD(feeder_feed, feed_endian16), + KOBJMETHOD(feeder_feed, feed_endian), {0, 0} }; -FEEDER_DECLARE(feeder_endian16, 0, NULL); - -static int -feed_endian24(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j = FEEDER_FEED(f->source, c, b, count, source); - uint8_t v; +FEEDER_DECLARE(feeder_endian16, 0, (void *)PCM_16_BPS); - if (j < 3) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, j); - return 0; - } - FMT_TEST(j % 3, "%s: Bytes not 24bit aligned.\n", __func__); - FMT_ALIGNBYTE(j -= j % 3); - i = j; - while (i > 0) { - v = b[--i]; - b[i] = b[i - 2]; - b[i -= 2] = v; - } - return j; -} static struct pcm_feederdesc feeder_endian24_desc[] = { {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_BE, 0}, - {FEEDER_FMT, AFMT_U24_LE|AFMT_STEREO, AFMT_U24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_BE, 0}, - {FEEDER_FMT, AFMT_S24_LE|AFMT_STEREO, AFMT_S24_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_LE, 0}, - {FEEDER_FMT, AFMT_U24_BE|AFMT_STEREO, AFMT_U24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_LE, 0}, - {FEEDER_FMT, AFMT_S24_BE|AFMT_STEREO, AFMT_S24_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_endian24_methods[] = { - KOBJMETHOD(feeder_feed, feed_endian24), + KOBJMETHOD(feeder_feed, feed_endian), {0, 0} }; -FEEDER_DECLARE(feeder_endian24, 0, NULL); - -static int -feed_endian32(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) -{ - int i, j = FEEDER_FEED(f->source, c, b, count, source); - uint8_t l, m; +FEEDER_DECLARE(feeder_endian24, 0, (void *)PCM_24_BPS); - if (j < 4) { - FMT_TRACE("%s: Not enough data (Got: %d bytes)\n", - __func__, j); - return 0; - } - FMT_TEST(j & 3, "%s: Bytes not 32bit aligned.\n", __func__); - FMT_ALIGNBYTE(j &= ~3); - i = j; - while (i > 0) { - l = b[--i]; - m = b[--i]; - b[i] = b[i - 1]; - b[i + 1] = b[i - 2]; - b[--i] = m; - b[--i] = l; - } - return j; -} static struct pcm_feederdesc feeder_endian32_desc[] = { {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_BE, 0}, - {FEEDER_FMT, AFMT_U32_LE|AFMT_STEREO, AFMT_U32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_BE, 0}, - {FEEDER_FMT, AFMT_S32_LE|AFMT_STEREO, AFMT_S32_BE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_LE, 0}, - {FEEDER_FMT, AFMT_U32_BE|AFMT_STEREO, AFMT_U32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_LE, 0}, - {FEEDER_FMT, AFMT_S32_BE|AFMT_STEREO, AFMT_S32_LE|AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; static kobj_method_t feeder_endian32_methods[] = { - KOBJMETHOD(feeder_feed, feed_endian32), + KOBJMETHOD(feeder_feed, feed_endian), {0, 0} }; -FEEDER_DECLARE(feeder_endian32, 0, NULL); +FEEDER_DECLARE(feeder_endian32, 0, (void *)PCM_32_BPS); /* * Endian conversion end + */ + +/* + * L/R swap conversion + */ +static int +feed_swaplr(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, bps, smpsz; + uint8_t *buf, v; + + bps = (int)((intptr_t)f->data); + smpsz = bps << 1; + if (count < smpsz) + return (0); + + j = FEEDER_FEED(f->source, c, b, count - (count % smpsz), source); + if (j < smpsz) + return (0); + + j -= j % smpsz; + buf = b + j; + + do { + buf -= smpsz; + i = bps; + do { + v = buf[--i]; + buf[i] = buf[bps + i]; + buf[bps + i] = v; + } while (i != 0); + } while (buf != b); + + return (j); +} + +static struct pcm_feederdesc feeder_swaplr8_desc[] = { + {FEEDER_SWAPLR, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_A_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_MU_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr8_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr8, -1, (void *)PCM_8_BPS); + +static struct pcm_feederdesc feeder_swaplr16_desc[] = { + {FEEDER_SWAPLR, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr16_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr16, -1, (void *)PCM_16_BPS); + +static struct pcm_feederdesc feeder_swaplr24_desc[] = { + {FEEDER_SWAPLR, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr24_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr24, -1, (void *)PCM_24_BPS); + +static struct pcm_feederdesc feeder_swaplr32_desc[] = { + {FEEDER_SWAPLR, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr32_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr32, -1, (void *)PCM_32_BPS); +/* + * L/R swap conversion end */ --- sys/dev/sound/pcm/feeder_rate.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/feeder_rate.c Thu Jul 12 12:04:19 2007 @@ -24,6 +24,17 @@ * 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. + */ + +/* + * 2006-02-21: + * ========== + * + * Major cleanup and overhaul to remove much redundant codes. + * Highlights: + * 1) Support for signed / unsigned 16, 24 and 32 bit, + * big / little endian, + * 2) Unlimited channels. * * 2005-06-11: * ========== @@ -64,34 +75,30 @@ */ #include +#include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_rate.c,v 1.11.2.2 2006/01/29 02:27:28 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_rate.c,v 1.23 2007/06/16 03:37:28 ariff Exp $"); -#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ -#define RATE_TEST(x, y) /* if (!(x)) printf y */ -#define RATE_TRACE(x...) /* printf(x) */ +#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ +#define RATE_TEST(x, y) /* if (!(x)) printf y */ +#define RATE_TRACE(x...) /* printf(x) */ MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder"); -#define FEEDBUFSZ 8192 -#define ROUNDHZ 25 -#define RATEMIN 4000 -/* 8000 * 138 or 11025 * 100 . This is insane, indeed! */ -#define RATEMAX 1102500 -#define MINGAIN 92 -#define MAXGAIN 96 - -#define FEEDRATE_CONVERT_64 0 -#define FEEDRATE_CONVERT_SCALE64 1 -#define FEEDRATE_CONVERT_SCALE32 2 -#define FEEDRATE_CONVERT_PLAIN 3 -#define FEEDRATE_CONVERT_FIXED 4 -#define FEEDRATE_CONVERT_OPTIMAL 5 -#define FEEDRATE_CONVERT_WORST 6 +/* + * Don't overflow 32bit integer, since everything is done + * within 32bit arithmetic. + */ +#define RATE_FACTOR_MIN 1 +#define RATE_FACTOR_MAX PCM_S24_MAX +#define RATE_FACTOR_SAFE(val) (!((val) < RATE_FACTOR_MIN || \ + (val) > RATE_FACTOR_MAX)) -#define FEEDRATE_64_MAXROLL 32 -#define FEEDRATE_32_MAXROLL 16 +struct feed_rate_info; + +typedef uint32_t (*feed_rate_converter)(struct feed_rate_info *, uint8_t *, + uint32_t); struct feed_rate_info { uint32_t src, dst; /* rounded source / destination rates */ @@ -99,138 +106,155 @@ uint32_t gx, gy; /* interpolation / decimation ratio */ uint32_t alpha; /* interpolation distance */ uint32_t pos, bpos; /* current sample / buffer positions */ - uint32_t bufsz; /* total buffer size */ + uint32_t bufsz; /* total buffer size limit */ + uint32_t bufsz_init; /* allocated buffer size */ + uint32_t channels; /* total channels */ + uint32_t bps; /* bytes-per-sample */ +#ifdef FEEDRATE_STRAY uint32_t stray; /* stray bytes */ - int32_t scale, roll; /* scale / roll factor */ - int16_t *buffer; - uint32_t (*convert)(struct feed_rate_info *, int16_t *, uint32_t); +#endif + uint8_t *buffer; + feed_rate_converter convert; }; -static uint32_t -feed_convert_64(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_scale64(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_scale32(struct feed_rate_info *, int16_t *, uint32_t); -static uint32_t -feed_convert_plain(struct feed_rate_info *, int16_t *, uint32_t); - -int feeder_rate_ratemin = RATEMIN; -int feeder_rate_ratemax = RATEMAX; -/* - * See 'Feeder Scaling Type' below.. - */ -static int feeder_rate_scaling = FEEDRATE_CONVERT_OPTIMAL; -static int feeder_rate_buffersize = FEEDBUFSZ & ~1; - -#if 0 -/* - * sysctls.. I love sysctls.. - */ -TUNABLE_INT("hw.snd.feeder_rate_ratemin", &feeder_rate_ratemin); -TUNABLE_INT("hw.snd.feeder_rate_ratemax", &feeder_rate_ratemin); -TUNABLE_INT("hw.snd.feeder_rate_scaling", &feeder_rate_scaling); -TUNABLE_INT("hw.snd.feeder_rate_buffersize", &feeder_rate_buffersize); +int feeder_rate_min = FEEDRATE_RATEMIN; +int feeder_rate_max = FEEDRATE_RATEMAX; +int feeder_rate_round = FEEDRATE_ROUNDHZ; + +TUNABLE_INT("hw.snd.feeder_rate_min", &feeder_rate_min); +TUNABLE_INT("hw.snd.feeder_rate_max", &feeder_rate_max); +TUNABLE_INT("hw.snd.feeder_rate_round", &feeder_rate_round); static int -sysctl_hw_snd_feeder_rate_ratemin(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_min(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_ratemin; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val < 1 || val >= feeder_rate_ratemax) - err = EINVAL; + val = feeder_rate_min; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (RATE_FACTOR_SAFE(val) && val < feeder_rate_max) + feeder_rate_min = val; else - feeder_rate_ratemin = val; - return err; -} -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemin, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemin, "I", ""); - -static int -sysctl_hw_snd_feeder_rate_ratemax(SYSCTL_HANDLER_ARGS) -{ - int err, val; - - val = feeder_rate_ratemax; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val <= feeder_rate_ratemin || val > 0x7fffff) err = EINVAL; - else - feeder_rate_ratemax = val; - return err; + return (err); } -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_ratemax, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_ratemax, "I", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_min, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_min, "I", + "minimum allowable rate"); static int -sysctl_hw_snd_feeder_rate_scaling(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_max(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_scaling; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - /* - * Feeder Scaling Type - * =================== - * - * 1. Plain 64bit (high precision) - * 2. 64bit scaling (high precision, CPU friendly, but can - * cause gain up/down). - * 3. 32bit scaling (somehow can cause hz roundup, gain - * up/down). - * 4. Plain copy (default if src == dst. Except if src == dst, - * this is the worst / silly conversion method!). - * - * Sysctl options:- - * - * 0 - Plain 64bit - no fallback. - * 1 - 64bit scaling - no fallback. - * 2 - 32bit scaling - no fallback. - * 3 - Plain copy - no fallback. - * 4 - Fixed rate. Means that, choose optimal conversion method - * without causing hz roundup. - * 32bit scaling (as long as hz roundup does not occur), - * 64bit scaling, Plain 64bit. - * 5 - Optimal / CPU friendly (DEFAULT). - * 32bit scaling, 64bit scaling, Plain 64bit - * 6 - Optimal to worst, no 64bit arithmetic involved. - * 32bit scaling, Plain copy. - */ - if (val < FEEDRATE_CONVERT_64 || val > FEEDRATE_CONVERT_WORST) - err = EINVAL; + val = feeder_rate_max; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (RATE_FACTOR_SAFE(val) && val > feeder_rate_min) + feeder_rate_max = val; else - feeder_rate_scaling = val; - return err; + err = EINVAL; + return (err); } -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_scaling, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_scaling, "I", ""); +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_max, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_max, "I", + "maximum allowable rate"); static int -sysctl_hw_snd_feeder_rate_buffersize(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_feeder_rate_round(SYSCTL_HANDLER_ARGS) { int err, val; - val = feeder_rate_buffersize; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - /* - * Don't waste too much kernel space - */ - if (val < 2 || val > 65536) + val = feeder_rate_round; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (val < FEEDRATE_ROUNDHZ_MIN || val > FEEDRATE_ROUNDHZ_MAX) err = EINVAL; else - feeder_rate_buffersize = val & ~1; - return err; + feeder_rate_round = val - (val % FEEDRATE_ROUNDHZ); + return (err); } -SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_buffersize, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_feeder_rate_buffersize, "I", ""); -#endif +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_round, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_round, "I", + "sample rate converter rounding threshold"); + +#define FEEDER_RATE_CONVERT(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_convert_##SIGNS##FMTBIT##ENDIANS##e(struct feed_rate_info *info, \ + uint8_t *dst, uint32_t max) \ +{ \ + uint32_t ret, smpsz, ch, pos, bpos, gx, gy, alpha, d1, d2; \ + intpcm_t x, y; \ + int i; \ + uint8_t *src, *sx, *sy; \ + \ + ret = 0; \ + alpha = info->alpha; \ + gx = info->gx; \ + gy = info->gy; \ + pos = info->pos; \ + bpos = info->bpos; \ + src = info->buffer + pos; \ + ch = info->channels; \ + smpsz = PCM_##FMTBIT##_BPS * ch; \ + for (;;) { \ + if (alpha < gx) { \ + alpha += gy; \ + pos += smpsz; \ + if (pos == bpos) \ + break; \ + src += smpsz; \ + } else { \ + alpha -= gx; \ + d1 = (alpha << PCM_FXSHIFT) / gy; \ + d2 = (1U << PCM_FXSHIFT) - d1; \ + sx = src - smpsz; \ + sy = src; \ + i = ch; \ + do { \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sx); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sy); \ + x = ((INTPCM##FMTBIT##_T(x) * d1) + \ + (INTPCM##FMTBIT##_T(y) * d2)) >> \ + PCM_FXSHIFT; \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(dst, x); \ + dst += PCM_##FMTBIT##_BPS; \ + sx += PCM_##FMTBIT##_BPS; \ + sy += PCM_##FMTBIT##_BPS; \ + ret += PCM_##FMTBIT##_BPS; \ + } while (--i != 0); \ + if (ret == max) \ + break; \ + } \ + } \ + info->alpha = alpha; \ + info->pos = pos; \ + return (ret); \ +} + +FEEDER_RATE_CONVERT(8, S, s, N, n) +FEEDER_RATE_CONVERT(16, S, s, L, l) +FEEDER_RATE_CONVERT(24, S, s, L, l) +FEEDER_RATE_CONVERT(32, S, s, L, l) +FEEDER_RATE_CONVERT(16, S, s, B, b) +FEEDER_RATE_CONVERT(24, S, s, B, b) +FEEDER_RATE_CONVERT(32, S, s, B, b) +FEEDER_RATE_CONVERT(8, U, u, N, n) +FEEDER_RATE_CONVERT(16, U, u, L, l) +FEEDER_RATE_CONVERT(24, U, u, L, l) +FEEDER_RATE_CONVERT(32, U, u, L, l) +FEEDER_RATE_CONVERT(16, U, u, B, b) +FEEDER_RATE_CONVERT(24, U, u, B, b) +FEEDER_RATE_CONVERT(32, U, u, B, b) static void -feed_speed_ratio(uint32_t x, uint32_t y, uint32_t *gx, uint32_t *gy) +feed_speed_ratio(uint32_t src, uint32_t dst, uint32_t *gx, uint32_t *gy) { - uint32_t w, src = x, dst = y; + uint32_t w, x = src, y = dst; while (y != 0) { w = x % y; @@ -242,244 +266,113 @@ } static void -feed_scale_roll(uint32_t dst, int32_t *scale, int32_t *roll, int32_t max) -{ - int64_t k, tscale; - int32_t j, troll; - - *scale = *roll = -1; - for (j = MAXGAIN; j >= MINGAIN; j -= 3) { - for (troll = 0; troll < max; troll++) { - tscale = (1 << troll) / dst; - k = (tscale * dst * 100) >> troll; - if (k > j && k <= 100) { - *scale = tscale; - *roll = troll; - return; - } - } - } -} - -static int -feed_get_best_coef(uint32_t *src, uint32_t *dst, uint32_t *gx, uint32_t *gy, - int32_t *scale, int32_t *roll) -{ - uint32_t tsrc, tdst, sscale, dscale; - int32_t tscale, troll; - int i, j, hzmin, hzmax; - - *scale = *roll = -1; - for (i = 0; i < 2; i++) { - hzmin = (ROUNDHZ * i) + 1; - hzmax = hzmin + ROUNDHZ; - for (j = hzmin; j < hzmax; j++) { - tsrc = *src - (*src % j); - tdst = *dst; - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - for (j = hzmin; j < hzmax; j++) { - tsrc = *src - (*src % j); - tdst = *dst - (*dst % j); - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *dst = tdst; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - for (j = hzmin; j < hzmax; j++) { - tsrc = *src; - tdst = *dst - (*dst % j); - if (tsrc < 1 || tdst < 1) - goto coef_failed; - feed_speed_ratio(tsrc, tdst, &sscale, &dscale); - feed_scale_roll(dscale, &tscale, &troll, - FEEDRATE_32_MAXROLL); - if (tscale != -1 && troll != -1) { - *src = tsrc; - *dst = tdst; - *gx = sscale; - *gy = dscale; - *scale = tscale; - *roll = troll; - return j; - } - } - } -coef_failed: - feed_speed_ratio(*src, *dst, gx, gy); - feed_scale_roll(*gy, scale, roll, FEEDRATE_32_MAXROLL); - return 0; -} - -static void feed_rate_reset(struct feed_rate_info *info) { - info->scale = -1; - info->roll = -1; - info->src = info->rsrc; - info->dst = info->rdst; - info->gx = 0; - info->gy = 0; + info->src = info->rsrc - (info->rsrc % + ((feeder_rate_round > 0) ? feeder_rate_round : 1)); + info->dst = info->rdst - (info->rdst % + ((feeder_rate_round > 0) ? feeder_rate_round : 1)); + info->gx = 1; + info->gy = 1; + info->alpha = 0; + info->channels = 1; + info->bps = PCM_8_BPS; + info->convert = NULL; + info->bufsz = info->bufsz_init; + info->pos = 1; + info->bpos = 2; +#ifdef FEEDRATE_STRAY + info->stray = 0; +#endif } static int feed_rate_setup(struct pcm_feeder *f) { struct feed_rate_info *info = f->data; - int r = 0; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_rate_converter convert; + } convtbl[] = { + { AFMT_S8, PCM_8_BPS, feed_convert_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_convert_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_convert_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_convert_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_convert_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_convert_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_convert_s32be }, + { AFMT_U8, PCM_8_BPS, feed_convert_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_convert_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_convert_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_convert_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_convert_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_convert_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_convert_u32be }, + { 0, 0, NULL }, + }; + uint32_t i; - info->pos = 2; - info->bpos = 4; - info->alpha = 0; - info->stray = 0; feed_rate_reset(info); - if (info->src == info->dst) { - /* - * No conversion ever needed. Just do plain copy. - */ - info->convert = feed_convert_plain; - info->gx = 1; - info->gy = 1; - } else { - switch (feeder_rate_scaling) { - case FEEDRATE_CONVERT_64: - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - info->convert = feed_convert_64; - break; - case FEEDRATE_CONVERT_SCALE64: - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale == -1 || info->roll == -1) - return -1; - info->convert = feed_convert_scale64; - break; - case FEEDRATE_CONVERT_SCALE32: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r == 0) - return -1; - info->convert = feed_convert_scale32; - break; - case FEEDRATE_CONVERT_PLAIN: - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - info->convert = feed_convert_plain; - break; - case FEEDRATE_CONVERT_FIXED: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0 && info->src == info->rsrc && - info->dst == info->rdst) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale != -1 && info->roll != -1) - info->convert = feed_convert_scale64; - else - info->convert = feed_convert_64; - } - break; - case FEEDRATE_CONVERT_OPTIMAL: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - feed_scale_roll(info->gy, &info->scale, - &info->roll, FEEDRATE_64_MAXROLL); - if (info->scale != -1 && info->roll != -1) - info->convert = feed_convert_scale64; - else - info->convert = feed_convert_64; - } - break; - case FEEDRATE_CONVERT_WORST: - r = feed_get_best_coef(&info->src, &info->dst, - &info->gx, &info->gy, &info->scale, - &info->roll); - if (r != 0) - info->convert = feed_convert_scale32; - else { - /* Fallback */ - feed_rate_reset(info); - feed_speed_ratio(info->src, info->dst, - &info->gx, &info->gy); - info->convert = feed_convert_plain; - } - break; - default: - return -1; - break; + + if (info->src != info->dst) + feed_speed_ratio(info->src, info->dst, &info->gx, &info->gy); + + if (!(RATE_FACTOR_SAFE(info->gx) && RATE_FACTOR_SAFE(info->gy))) + return (-1); + + for (i = 0; i < sizeof(convtbl) / sizeof(convtbl[0]); i++) { + if (convtbl[i].format == 0) + return (-1); + if ((f->desc->out & ~AFMT_STEREO) == convtbl[i].format) { + info->bps = convtbl[i].bps; + info->convert = convtbl[i].convert; + break; } - /* No way! */ - if (info->gx == 0 || info->gy == 0) - return -1; - /* - * No need to interpolate/decimate, just do plain copy. - * This probably caused by Hz roundup. - */ - if (info->gx == info->gy) - info->convert = feed_convert_plain; } - return 0; + + /* + * No need to interpolate/decimate, just do plain copy. + */ + if (info->gx == info->gy) + info->convert = NULL; + + info->channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + info->pos = info->bps * info->channels; + info->bpos = info->pos << 1; + info->bufsz -= info->bufsz % info->pos; + + memset(info->buffer, sndbuf_zerodata(f->desc->out), info->bpos); + + RATE_TRACE("%s: %u (%u) -> %u (%u) [%u/%u] , " + "format=0x%08x, channels=%u, bufsz=%u\n", + __func__, info->src, info->rsrc, info->dst, info->rdst, + info->gx, info->gy, f->desc->out, info->channels, + info->bufsz - info->pos); + + return (0); } static int -feed_rate_set(struct pcm_feeder *f, int what, int value) +feed_rate_set(struct pcm_feeder *f, int what, int32_t value) { struct feed_rate_info *info = f->data; - if (value < feeder_rate_ratemin || value > feeder_rate_ratemax) - return -1; - + if (value < feeder_rate_min || value > feeder_rate_max) + return (-1); + switch (what) { - case FEEDRATE_SRC: - info->rsrc = value; - break; - case FEEDRATE_DST: - info->rdst = value; - break; - default: - return -1; + case FEEDRATE_SRC: + info->rsrc = value; + break; + case FEEDRATE_DST: + info->rdst = value; + break; + default: + return (-1); } - return feed_rate_setup(f); + return (feed_rate_setup(f)); } static int @@ -487,18 +380,15 @@ { struct feed_rate_info *info = f->data; - /* - * Return *real* src/dst rate. - */ switch (what) { - case FEEDRATE_SRC: - return info->rsrc; - case FEEDRATE_DST: - return info->rdst; - default: - return -1; + case FEEDRATE_SRC: + return (info->rsrc); + case FEEDRATE_DST: + return (info->rdst); + default: + return (-1); } - return -1; + return (-1); } static int @@ -506,23 +396,26 @@ { struct feed_rate_info *info; + if (f->desc->out != f->desc->in) + return (EINVAL); + info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO); if (info == NULL) - return ENOMEM; + return (ENOMEM); /* * bufsz = sample from last cycle + conversion space */ - info->bufsz = 2 + feeder_rate_buffersize; - info->buffer = malloc(sizeof(*info->buffer) * info->bufsz, - M_RATEFEEDER, M_NOWAIT | M_ZERO); + info->bufsz_init = 8 + feeder_buffersize; + info->buffer = malloc(info->bufsz_init, M_RATEFEEDER, + M_NOWAIT | M_ZERO); if (info->buffer == NULL) { free(info, M_RATEFEEDER); - return ENOMEM; + return (ENOMEM); } info->rsrc = DSP_DEFAULT_SPEED; info->rdst = DSP_DEFAULT_SPEED; f->data = info; - return feed_rate_setup(f); + return (feed_rate_setup(f)); } static int @@ -530,303 +423,220 @@ { struct feed_rate_info *info = f->data; - if (info) { - if (info->buffer) + if (info != NULL) { + if (info->buffer != NULL) free(info->buffer, M_RATEFEEDER); free(info, M_RATEFEEDER); } f->data = NULL; - return 0; + return (0); } -static uint32_t -feed_convert_64(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - int64_t x, alpha, distance; - uint32_t ret; - int32_t pos, bpos, gx, gy; - int16_t *src; - /* - * Plain, straight forward 64bit arith. No bit-magic applied here. - */ - ret = 0; - alpha = info->alpha; - gx = info->gx; - gy = info->gy; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x / gy; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x / gy; - if (ret == max) - break; - } - } - info->alpha = alpha; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_scale64(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - int64_t x, alpha, distance; - uint32_t ret; - int32_t pos, bpos, gx, gy, roll; - int16_t *src; - /* - * 64bit scaling. - */ - ret = 0; - roll = info->roll; - alpha = info->alpha * info->scale; - gx = info->gx * info->scale; - gy = info->gy * info->scale; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x >> roll; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x >> roll; - if (ret == max) - break; - } - } - info->alpha = alpha / info->scale; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_scale32(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - uint32_t ret; - int32_t x, pos, bpos, gx, gy, alpha, roll, distance; - int16_t *src; - /* - * 32bit scaling. - */ - ret = 0; - roll = info->roll; - alpha = info->alpha * info->scale; - gx = info->gx * info->scale; - gy = info->gy * info->scale; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - distance = gy - alpha; - x = (alpha * src[pos - 2]) + (distance * src[pos]); - dst[ret++] = x >> roll; - x = (alpha * src[pos - 1]) + (distance * src[pos + 1]); - dst[ret++] = x >> roll; - if (ret == max) - break; - } - } - info->alpha = alpha / info->scale; - info->pos = pos; - return ret; -} - -static uint32_t -feed_convert_plain(struct feed_rate_info *info, int16_t *dst, uint32_t max) -{ - uint32_t ret; - int32_t pos, bpos, gx, gy, alpha; - int16_t *src; - /* - * Plain copy. - */ - ret = 0; - gx = info->gx; - gy = info->gy; - alpha = info->alpha; - pos = info->pos; - bpos = info->bpos; - src = info->buffer; - for (;;) { - if (alpha < gx) { - alpha += gy; - pos += 2; - if (pos == bpos) - break; - } else { - alpha -= gx; - dst[ret++] = src[pos]; - dst[ret++] = src[pos + 1]; - if (ret == max) - break; - } - } - info->pos = pos; - info->alpha = alpha; - return ret; -} - -static int32_t +static int feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) + uint32_t count, void *source) { struct feed_rate_info *info = f->data; - uint32_t i; + uint32_t i, smpsz; int32_t fetch, slot; - int16_t *dst = (int16_t *)b; + + if (info->convert == NULL) + return (FEEDER_FEED(f->source, c, b, count, source)); + /* * This loop has been optimized to generalize both up / down * sampling without causing missing samples or excessive buffer - * feeding. + * feeding. The tricky part is to calculate *precise* (slot) value + * needed for the entire conversion space since we are bound to + * return and fill up the buffer according to the requested 'count'. + * Too much feeding will cause the extra buffer stay within temporary + * circular buffer forever and always manifest itself as a truncated + * sound during end of playback / recording. Too few, and we end up + * with possible underruns and waste of cpu cycles. + * + * 'Stray' management exist to combat with possible unaligned + * buffering by the caller. */ - RATE_TEST(count >= 4 && (count & 3) == 0, - ("%s: Count size not byte integral (%d)\n", __func__, count)); - if (count < 4) - return 0; - count >>= 1; - count &= ~1; - slot = (((info->gx * (count >> 1)) + info->gy - info->alpha - 1) / info->gy) << 1; - RATE_TEST((slot & 1) == 0, ("%s: Slot count not sample integral (%d)\n", - __func__, slot)); + smpsz = info->bps * info->channels; + RATE_TEST(count >= smpsz && (count % smpsz) == 0, + ("%s: Count size not sample integral (%d)\n", __func__, count)); + if (count < smpsz) + return (0); + count -= count % smpsz; /* - * Optimize buffer feeding aggressively to ensure calculated slot - * can be fitted nicely into available buffer free space, hence - * avoiding multiple feeding. + * This slot count formula will stay here for the next million years + * to come. This is the key of our circular buffering precision. */ - RATE_TEST(info->stray == 0, ("%s: [1] Stray bytes: %u\n", - __func__,info->stray)); - if (info->pos != 2 && info->bpos - info->pos == 2 && - info->bpos + slot > info->bufsz) { + slot = (((info->gx * (count / smpsz)) + info->gy - info->alpha - 1) / + info->gy) * smpsz; + RATE_TEST((slot % smpsz) == 0, + ("%s: Slot count not sample integral (%d)\n", __func__, slot)); +#ifdef FEEDRATE_STRAY + RATE_TEST(info->stray == 0, ("%s: [1] Stray bytes: %u\n", __func__, + info->stray)); +#endif + if (info->pos != smpsz && info->bpos - info->pos == smpsz && + info->bpos + slot > info->bufsz) { /* * Copy last unit sample and its previous to * beginning of buffer. */ - info->buffer[0] = info->buffer[info->pos - 2]; - info->buffer[1] = info->buffer[info->pos - 1]; - info->buffer[2] = info->buffer[info->pos]; - info->buffer[3] = info->buffer[info->pos + 1]; - info->pos = 2; - info->bpos = 4; + bcopy(info->buffer + info->pos - smpsz, info->buffer, + smpsz << 1); + info->pos = smpsz; + info->bpos = smpsz << 1; } - RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", - __func__, slot)); + RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", __func__, slot)); i = 0; for (;;) { for (;;) { - fetch = (info->bufsz - info->bpos) << 1; + fetch = info->bufsz - info->bpos; +#ifdef FEEDRATE_STRAY fetch -= info->stray; +#endif RATE_ASSERT(fetch >= 0, - ("%s: [1] Buffer overrun: %d > %d\n", - __func__, info->bpos, info->bufsz)); - if ((slot << 1) < fetch) - fetch = slot << 1; - if (fetch > 0) { - RATE_ASSERT(((info->bpos << 1) - info->stray) >= 0 && - ((info->bpos << 1) - info->stray) < (info->bufsz << 1), - ("%s: DANGER - BUFFER OVERRUN! bufsz=%d, pos=%d\n", __func__, - info->bufsz << 1, (info->bpos << 1) - info->stray)); - fetch = FEEDER_FEED(f->source, c, - (uint8_t *)(info->buffer) + (info->bpos << 1) - info->stray, - fetch, source); - info->stray = 0; - if (fetch == 0) - break; - RATE_TEST((fetch & 3) == 0, - ("%s: Fetch size not byte integral (%d)\n", - __func__, fetch)); - info->stray += fetch & 3; - RATE_TEST(info->stray == 0, - ("%s: Stray bytes detected (%d)\n", - __func__, info->stray)); - fetch >>= 1; - fetch &= ~1; - info->bpos += fetch; - slot -= fetch; - RATE_ASSERT(slot >= 0, - ("%s: Negative Slot: %d\n", __func__, - slot)); - if (slot == 0) - break; - if (info->bpos == info->bufsz) - break; - } else + ("%s: [1] Buffer overrun: %d > %d\n", __func__, + info->bpos, info->bufsz)); + if (slot < fetch) + fetch = slot; +#ifdef FEEDRATE_STRAY + if (fetch < 1) +#else + if (fetch < smpsz) +#endif + break; + RATE_ASSERT((int)(info->bpos +#ifdef FEEDRATE_STRAY + - info->stray +#endif + ) >= 0 && + (info->bpos - info->stray) < info->bufsz, + ("%s: DANGER - BUFFER OVERRUN! bufsz=%d, pos=%d\n", + __func__, info->bufsz, info->bpos +#ifdef FEEDRATE_STRAY + - info->stray +#endif + )); + fetch = FEEDER_FEED(f->source, c, + info->buffer + info->bpos +#ifdef FEEDRATE_STRAY + - info->stray +#endif + , fetch, source); +#ifdef FEEDRATE_STRAY + info->stray = 0; + if (fetch == 0) +#else + if (fetch < smpsz) +#endif + break; + RATE_TEST((fetch % smpsz) == 0, + ("%s: Fetch size not sample integral (%d)\n", + __func__, fetch)); +#ifdef FEEDRATE_STRAY + info->stray += fetch % smpsz; + RATE_TEST(info->stray == 0, + ("%s: Stray bytes detected (%d)\n", __func__, + info->stray)); +#endif + fetch -= fetch % smpsz; + info->bpos += fetch; + slot -= fetch; + RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", + __func__, slot)); + if (slot == 0 || info->bpos == info->bufsz) break; } if (info->pos == info->bpos) { - RATE_TEST(info->pos == 2, - ("%s: EOF while in progress\n", __func__)); + RATE_TEST(info->pos == smpsz, + ("%s: EOF while in progress\n", __func__)); break; } RATE_ASSERT(info->pos <= info->bpos, - ("%s: [2] Buffer overrun: %d > %d\n", __func__, - info->pos, info->bpos)); + ("%s: [2] Buffer overrun: %d > %d\n", __func__, info->pos, + info->bpos)); RATE_ASSERT(info->pos < info->bpos, - ("%s: Zero buffer!\n", __func__)); - RATE_ASSERT(((info->bpos - info->pos) & 1) == 0, - ("%s: Buffer not sample integral (%d)\n", - __func__, info->bpos - info->pos)); - i += info->convert(info, dst + i, count - i); + ("%s: Zero buffer!\n", __func__)); + RATE_ASSERT(((info->bpos - info->pos) % smpsz) == 0, + ("%s: Buffer not sample integral (%d)\n", __func__, + info->bpos - info->pos)); + i += info->convert(info, b + i, count - i); RATE_ASSERT(info->pos <= info->bpos, - ("%s: [3] Buffer overrun: %d > %d\n", - __func__, info->pos, info->bpos)); + ("%s: [3] Buffer overrun: %d > %d\n", __func__, info->pos, + info->bpos)); if (info->pos == info->bpos) { /* * End of buffer cycle. Copy last unit sample * to beginning of buffer so next cycle can * interpolate using it. */ - RATE_TEST(info->stray == 0, ("%s: [2] Stray bytes: %u\n", __func__, info->stray)); - info->buffer[0] = info->buffer[info->pos - 2]; - info->buffer[1] = info->buffer[info->pos - 1]; - info->bpos = 2; - info->pos = 2; +#ifdef FEEDRATE_STRAY + RATE_TEST(info->stray == 0, + ("%s: [2] Stray bytes: %u\n", __func__, + info->stray)); +#endif + bcopy(info->buffer + info->pos - smpsz, info->buffer, + smpsz); + info->bpos = smpsz; + info->pos = smpsz; } if (i == count) break; } -#if 0 - RATE_TEST(count == i, ("Expect: %u , Got: %u\n", count << 1, i << 1)); + + RATE_TEST((slot == 0 && count == i) || (slot > 0 && count > i && + info->pos == info->bpos && info->pos == smpsz), + ("%s: Inconsistent slot/count! " + "Count Expect: %u , Got: %u, Slot Left: %d\n", __func__, count, i, + slot)); + +#ifdef FEEDRATE_STRAY + RATE_TEST(info->stray == 0, ("%s: [3] Stray bytes: %u\n", __func__, + info->stray)); #endif - RATE_TEST(info->stray == 0, ("%s: [3] Stray bytes: %u\n", __func__, info->stray)); - return i << 1; + + return (i); } static struct pcm_feederdesc feeder_rate_desc[] = { + {FEEDER_RATE, AFMT_S8, AFMT_S8, 0}, + {FEEDER_RATE, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_RATE, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_RATE, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_RATE, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_RATE, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_RATE, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_RATE, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_RATE, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U8, AFMT_U8, 0}, + {FEEDER_RATE, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_RATE, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_RATE, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_RATE, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_RATE, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_RATE, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_RATE, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; + static kobj_method_t feeder_rate_methods[] = { - KOBJMETHOD(feeder_init, feed_rate_init), - KOBJMETHOD(feeder_free, feed_rate_free), - KOBJMETHOD(feeder_set, feed_rate_set), - KOBJMETHOD(feeder_get, feed_rate_get), - KOBJMETHOD(feeder_feed, feed_rate), + KOBJMETHOD(feeder_init, feed_rate_init), + KOBJMETHOD(feeder_free, feed_rate_free), + KOBJMETHOD(feeder_set, feed_rate_set), + KOBJMETHOD(feeder_get, feed_rate_get), + KOBJMETHOD(feeder_feed, feed_rate), {0, 0} }; + FEEDER_DECLARE(feeder_rate, 2, NULL); --- sys/dev/sound/pcm/feeder_volume.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/feeder_volume.c Thu Jul 12 12:04:19 2007 @@ -22,57 +22,219 @@ * 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. - * - * feeder_volume, a long 'Lost Technology' rather than a new feature. */ +/* feeder_volume, a long 'Lost Technology' rather than a new feature. */ + #include +#include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_volume.c,v 1.2.2.1 2005/12/30 19:55:54 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_volume.c,v 1.6 2007/06/16 20:36:39 ariff Exp $"); + +typedef int (*feed_volume_filter)(uint8_t *, int *, int, int); + +#define FVOL_CALC_8(s, v) SND_VOL_CALC_SAMPLE((int32_t)(s), v) +#define FVOL_CALC_16(s, v) SND_VOL_CALC_SAMPLE((int32_t)(s), v) +#define FVOL_CALC_24(s, v) SND_VOL_CALC_SAMPLE((int64_t)(s), v) +#define FVOL_CALC_32(s, v) SND_VOL_CALC_SAMPLE((int64_t)(s), v) + +#define FEEDER_VOLUME_FILTER(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static int \ +feed_volume_filter_##SIGNS##FMTBIT##ENDIANS##e(uint8_t *b, int *vol, \ + int channels, int count) \ +{ \ + intpcm##FMTBIT##_t k; \ + intpcm_t j; \ + int i; \ + \ + i = count; \ + b += i; \ + \ + do { \ + b -= PCM_##FMTBIT##_BPS; \ + i -= PCM_##FMTBIT##_BPS; \ + j = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(b); \ + k = FVOL_CALC_##FMTBIT(j, \ + vol[(i / PCM_##FMTBIT##_BPS) % channels]); \ + j = PCM_CLAMP_##SIGN##FMTBIT(k); \ + _PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(b, j); \ + } while (i != 0); \ + \ + return (count); \ +} + +FEEDER_VOLUME_FILTER(8, S, s, N, n) +FEEDER_VOLUME_FILTER(16, S, s, L, l) +FEEDER_VOLUME_FILTER(24, S, s, L, l) +FEEDER_VOLUME_FILTER(32, S, s, L, l) +FEEDER_VOLUME_FILTER(16, S, s, B, b) +FEEDER_VOLUME_FILTER(24, S, s, B, b) +FEEDER_VOLUME_FILTER(32, S, s, B, b) +FEEDER_VOLUME_FILTER(8, U, u, N, n) +FEEDER_VOLUME_FILTER(16, U, u, L, l) +FEEDER_VOLUME_FILTER(24, U, u, L, l) +FEEDER_VOLUME_FILTER(32, U, u, L, l) +FEEDER_VOLUME_FILTER(16, U, u, B, b) +FEEDER_VOLUME_FILTER(24, U, u, B, b) +FEEDER_VOLUME_FILTER(32, U, u, B, b) + +struct feed_volume_info { + uint32_t format; + int bps; + feed_volume_filter filter; +}; + +static struct feed_volume_info feed_volume_tbl[] = { + { AFMT_S8, PCM_8_BPS, feed_volume_filter_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_volume_filter_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_volume_filter_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_volume_filter_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_volume_filter_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_volume_filter_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_volume_filter_s32be }, + { AFMT_U8, PCM_8_BPS, feed_volume_filter_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_volume_filter_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_volume_filter_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_volume_filter_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_volume_filter_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_volume_filter_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_volume_filter_u32be }, +}; + +#define FVOL_DATA(vc, i, c) ((intptr_t)((((vc) & 0x3f) << 12) | \ + (((i) & 0x3f) << 6) | ((c) & 0x3f))) +#define FVOL_CLASS(m) (((m) >> 12) & 0x3f) +#define FVOL_INFOIDX(m) (((m) >> 6) & 0x3f) +#define FVOL_CHANNELS(m) ((m) & 0x3f) + +static int +feed_volume_setup(struct pcm_feeder *f, int vc) +{ + int i, channels; + + if (f->desc->in != f->desc->out || vc < SND_VOL_C_BEGIN || + vc > SND_VOL_C_END) + return (EINVAL); + + /* For now, this is mandatory! */ + if (!(f->desc->out & AFMT_STEREO)) + return (EINVAL); + + channels = 2; + + /* XXX Future interleave multichannel check yada yada.. */ + if (channels < SND_CHN_MIN || channels > SND_CHN_MAX) + return (EINVAL); + + for (i = 0; i < (sizeof(feed_volume_tbl) / sizeof(feed_volume_tbl[0])); + i++) { + if ((f->desc->out & ~AFMT_STEREO) == + feed_volume_tbl[i].format) { + f->data = (void *)FVOL_DATA(vc, i, channels); + return (0); + } + } + + return (EINVAL); +} static int -feed_volume_s16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, - uint32_t count, void *source) +feed_volume_init(struct pcm_feeder *f) { - int i, j, k, vol[2]; - int16_t *buf; + return (feed_volume_setup(f, SND_VOL_C_PCM)); +} - k = FEEDER_FEED(f->source, c, b, count & ~1, source); - if (k < 2) { -#if 0 - device_printf(c->dev, "%s: Not enough data (Got: %d bytes)\n", - __func__, k); -#endif - return 0; +static int +feed_volume_set(struct pcm_feeder *f, int what, int value) +{ + switch (what) { + case FEEDVOL_CLASS: + return (feed_volume_setup(f, value)); + break; + default: + break; } -#if 0 - if (k & 1) - device_printf(c->dev, "%s: Bytes not 16bit aligned.\n", __func__); -#endif - k &= ~1; - i = k >> 1; - buf = (int16_t *)b; - vol[0] = c->volume & 0x7f; - vol[1] = (c->volume >> 8) & 0x7f; - while (i > 0) { - i--; - j = (vol[i & 1] * buf[i]) / 100; - if (j > 32767) - j = 32767; - if (j < -32768) - j = -32768; - buf[i] = j; + + return (EINVAL); +} + +static int +feed_volume_get(struct pcm_feeder *f, int what) +{ + switch (what) { + case FEEDVOL_CLASS: + return (FVOL_CLASS((intptr_t)f->data)); + break; + default: + break; } - return k; + + return (-1); +} + +static int +feed_volume(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_volume_info *info; + int vol[SND_CHN_MAX]; + int j, k, v, smpsz, channels; + snd_volume_class_t vcv; + + channels = FVOL_CHANNELS((intptr_t)f->data); + vcv = SND_VOL_C_VAL(FVOL_CLASS((intptr_t)f->data)); + j = channels - 1; + k = 0; + + do { + KASSERT(c->matrix[j] != SND_CHN_T_MAX, + ("%s(): invalid matrix j=%d", __func__, j)); + v = c->volume[vcv][c->matrix[j]]; + if (v != SND_VOL_FLAT) + k = 1; + vol[j] = v; + } while (j-- != 0); + + if (k == 0) + return (FEEDER_FEED(f->source, c, b, count, source)); + + info = &feed_volume_tbl[FVOL_INFOIDX((intptr_t)f->data)]; + smpsz = info->bps * channels; + if (count < smpsz) + return (0); + + k = FEEDER_FEED(f->source, c, b, count - (count % smpsz), source); + if (k < smpsz) + return (0); + + k -= k % smpsz; + + return (info->filter(b, vol, channels, k)); } -static struct pcm_feederdesc feeder_volume_s16_desc[] = { - {FEEDER_VOLUME, AFMT_S16_LE|AFMT_STEREO, AFMT_S16_LE|AFMT_STEREO, 0}, +static struct pcm_feederdesc feeder_volume_desc[] = { + {FEEDER_VOLUME, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; -static kobj_method_t feeder_volume_s16_methods[] = { - KOBJMETHOD(feeder_feed, feed_volume_s16), +static kobj_method_t feeder_volume_methods[] = { + KOBJMETHOD(feeder_init, feed_volume_init), + KOBJMETHOD(feeder_set, feed_volume_set), + KOBJMETHOD(feeder_get, feed_volume_get), + KOBJMETHOD(feeder_feed, feed_volume), {0, 0} }; -FEEDER_DECLARE(feeder_volume_s16, 2, NULL); +FEEDER_DECLARE(feeder_volume, 2, NULL); --- sys/dev/sound/pcm/matrix.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/matrix.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,99 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#ifndef _SND_MATRIX_H_ +#define _SND_MATRIX_H_ + +typedef enum { + SND_CHN_T_FL, /* Front Left */ + SND_CHN_T_FR, /* Front Right */ + SND_CHN_T_FC, /* Front Center */ + SND_CHN_T_LF, /* Low Frequency */ + SND_CHN_T_BL, /* Back Left */ + SND_CHN_T_BR, /* Back Right */ + SND_CHN_T_FLC, /* Front Left Center */ + SND_CHN_T_FRC, /* Front Right Center */ + SND_CHN_T_BC, /* Back Center */ + SND_CHN_T_SL, /* Side Left */ + SND_CHN_T_SR, /* Side Right */ + SND_CHN_T_TC, /* Top Center */ + SND_CHN_T_TFL, /* Top Front Left */ + SND_CHN_T_TFC, /* Top Front Center */ + SND_CHN_T_TFR, /* Top Front Right */ + SND_CHN_T_TBL, /* Top Back Left */ + SND_CHN_T_TBC, /* Top Back Center */ + SND_CHN_T_TBR, /* Top Back Right */ + SND_CHN_T_MAX, + SND_CHN_T_VOL_0DB = SND_CHN_T_MAX, + SND_CHN_T_VOL_MAX +} snd_channel_t; + +#define SND_CHN_T_BEGIN SND_CHN_T_FL +#define SND_CHN_T_END SND_CHN_T_FR +#define SND_CHN_T_STEP 1 + +#define SND_CHN_MIN 1 +#define SND_CHN_MAX 2 + +/* + * Multichannel interleaved volume matrix. Each calculated value relative + * to master and 0db will be stored in each CLASS + 1 as long as + * chn_setvolume_matrix() or the equivalent CHN_SETVOLUME() macros is + * used (see channel.c). + */ +typedef enum { + SND_VOL_C_MASTER, + SND_VOL_C_PCM, + SND_VOL_C_PCM_VAL, + SND_VOL_C_MAX +} snd_volume_class_t; + +#define SND_VOL_C_BEGIN SND_VOL_C_PCM +#define SND_VOL_C_END SND_VOL_C_PCM +#define SND_VOL_C_STEP 2 + +#define SND_VOL_C_VAL(x) ((x) + 1) + +#define SND_VOL_0DB_MIN 1 +#define SND_VOL_0DB_MAX 100 + +#define SND_VOL_0DB_MASTER 100 +#define SND_VOL_0DB_PCM 45 + +#define SND_VOL_RESOLUTION 8 +#define SND_VOL_FLAT (1 << SND_VOL_RESOLUTION) + +#define SND_VOL_CALC_SAMPLE(x, y) (((x) * (y)) >> SND_VOL_RESOLUTION) + +#define SND_VOL_CALC_VAL(x, y, z) \ + (((((x)[y][z] << SND_VOL_RESOLUTION) / \ + (x)[y][SND_CHN_T_VOL_0DB]) * \ + (x)[SND_VOL_C_MASTER][z]) / \ + (x)[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB]) \ + +#endif /* !_SND_MATRIX_H_ */ --- sys/dev/sound/pcm/mixer.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/mixer.c Thu Jul 12 12:04:19 2007 @@ -28,19 +28,25 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/mixer.c,v 1.43.2.5 2007/05/13 20:53:39 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/mixer.c,v 1.61 2007/06/16 03:37:28 ariff Exp $"); MALLOC_DEFINE(M_MIXER, "mixer", "mixer"); +static int mixer_bypass = 1; +TUNABLE_INT("hw.snd.vpc_mixer_bypass", &mixer_bypass); +SYSCTL_INT(_hw_snd, OID_AUTO, vpc_mixer_bypass, CTLFLAG_RW, + &mixer_bypass, 0, + "control channel pcm/rec volume, bypassing real mixer device"); + #define MIXER_NAMELEN 16 struct snd_mixer { KOBJ_FIELDS; - const char *type; void *devinfo; int busy; int hwvol_muted; int hwvol_mixer; int hwvol_step; + int type; device_t dev; u_int32_t hwvol_mute_level; u_int32_t devs; @@ -52,6 +58,13 @@ u_int8_t realdev[32]; char name[MIXER_NAMELEN]; struct mtx *lock; + oss_mixer_enuminfo enuminfo; + /** + * Counter is incremented when applications change any of this + * mixer's controls. A change in value indicates that persistent + * mixer applications should update their displays. + */ + int modify_counter; }; static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = { @@ -76,18 +89,23 @@ static d_open_t mixer_open; static d_close_t mixer_close; +static d_ioctl_t mixer_ioctl; static struct cdevsw mixer_cdevsw = { .d_version = D_VERSION, - .d_flags = D_TRACKCLOSE | D_NEEDGIANT, .d_open = mixer_open, .d_close = mixer_close, .d_ioctl = mixer_ioctl, .d_name = "mixer", }; +/** + * Keeps a count of mixer devices; used only by OSSv4 SNDCTL_SYSINFO ioctl. + */ +int mixer_count = 0; + #ifdef USING_DEVFS -static eventhandler_tag mixer_ehtag; +static eventhandler_tag mixer_ehtag = NULL; #endif static struct cdev * @@ -114,31 +132,76 @@ } #endif +#define MIXER_SET_UNLOCK(x, y) do { \ + if ((y) != 0) \ + snd_mtxunlock((x)->lock); \ +} while(0) + +#define MIXER_SET_LOCK(x, y) do { \ + if ((y) != 0) \ + snd_mtxlock((x)->lock); \ +} while(0) + static int -mixer_set_softpcmvol(struct snd_mixer *mixer, struct snddev_info *d, - unsigned left, unsigned right) +mixer_set_softpcmvol(struct snd_mixer *m, struct snddev_info *d, + unsigned left, unsigned right) { - struct snddev_channel *sce; - struct pcm_channel *ch; -#ifdef USING_MUTEX - int locked = (mixer->lock && mtx_owned((struct mtx *)(mixer->lock))) ? 1 : 0; + struct pcm_channel *c; + int dropmtx, acquiremtx; - if (locked) - snd_mtxunlock(mixer->lock); -#endif - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; - CHN_LOCK(ch); - if (ch->direction == PCMDIR_PLAY && - (ch->feederflags & (1 << FEEDER_VOLUME))) - chn_setvolume(ch, left, right); - CHN_UNLOCK(ch); - } -#ifdef USING_MUTEX - if (locked) - snd_mtxlock(mixer->lock); -#endif - return 0; + if (!PCM_REGISTERED(d)) + return (EINVAL); + + if (mtx_owned(m->lock)) + dropmtx = 1; + else + dropmtx = 0; + + if (!(d->flags & SD_F_MPSAFE) || mtx_owned(d->lock) != 0) + acquiremtx = 0; + else + acquiremtx = 1; + + /* + * Be careful here. If we're coming from cdev ioctl, it is OK to + * not doing locking AT ALL (except on individual channel) since + * we've been heavily guarded by pcm cv, or if we're still + * under Giant influence. Since we also have mix_* calls, we cannot + * assume such protection and just do the lock as usuall. + */ + MIXER_SET_UNLOCK(m, dropmtx); + MIXER_SET_LOCK(d, acquiremtx); + + if (CHN_EMPTY(d, channels.pcm.busy)) { + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + (c->feederflags & (1 << FEEDER_VOLUME))) { + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FL, left); + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FR, right); + } + CHN_UNLOCK(c); + } + } else { + CHN_FOREACH(c, d, channels.pcm.busy) { + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + (c->feederflags & (1 << FEEDER_VOLUME))) { + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FL, left); + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FR, right); + } + CHN_UNLOCK(c); + } + } + + MIXER_SET_UNLOCK(d, acquiremtx); + MIXER_SET_LOCK(m, dropmtx); + + return (0); } static int @@ -148,7 +211,7 @@ unsigned l, r, tl, tr; u_int32_t parent = SOUND_MIXER_NONE, child = 0; u_int32_t realdev; - int i; + int i, dropmtx; if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || (0 == (m->devs & (1 << dev)))) @@ -162,6 +225,14 @@ if (d == NULL) return -1; + /* It is safe to drop this mutex due to Giant. */ + if (!(d->flags & SD_F_MPSAFE) && mtx_owned(m->lock) != 0) + dropmtx = 1; + else + dropmtx = 0; + + MIXER_SET_UNLOCK(m, dropmtx); + /* TODO: recursive handling */ parent = m->parent[dev]; if (parent >= SOUND_MIXER_NRDEVICES) @@ -173,10 +244,12 @@ tl = (l * (m->level[parent] & 0x00ff)) / 100; tr = (r * ((m->level[parent] & 0xff00) >> 8)) / 100; if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) - mixer_set_softpcmvol(m, d, tl, tr); + (void)mixer_set_softpcmvol(m, d, tl, tr); else if (realdev != SOUND_MIXER_NONE && - MIXER_SET(m, realdev, tl, tr) < 0) + MIXER_SET(m, realdev, tl, tr) < 0) { + MIXER_SET_LOCK(m, dropmtx); return -1; + } } else if (child != 0) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (!(child & (1 << i)) || m->parent[i] != dev) @@ -184,25 +257,32 @@ realdev = m->realdev[i]; tl = (l * (m->level[i] & 0x00ff)) / 100; tr = (r * ((m->level[i] & 0xff00) >> 8)) / 100; - if (i == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) - mixer_set_softpcmvol(m, d, tl, tr); + if (i == SOUND_MIXER_PCM && + (d->flags & SD_F_SOFTPCMVOL)) + (void)mixer_set_softpcmvol(m, d, tl, tr); else if (realdev != SOUND_MIXER_NONE) MIXER_SET(m, realdev, tl, tr); } realdev = m->realdev[dev]; if (realdev != SOUND_MIXER_NONE && - MIXER_SET(m, realdev, l, r) < 0) + MIXER_SET(m, realdev, l, r) < 0) { + MIXER_SET_LOCK(m, dropmtx); return -1; + } } else { if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) - mixer_set_softpcmvol(m, d, l, r); + (void)mixer_set_softpcmvol(m, d, l, r); else if (realdev != SOUND_MIXER_NONE && - MIXER_SET(m, realdev, l, r) < 0) + MIXER_SET(m, realdev, l, r) < 0) { + MIXER_SET_LOCK(m, dropmtx); return -1; + } } m->level[dev] = l | (r << 8); + MIXER_SET_LOCK(m, dropmtx); + return 0; } @@ -211,16 +291,30 @@ { if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) return mixer->level[dev]; - else return -1; + else + return -1; } static int mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src) { + struct snddev_info *d; + int dropmtx; + + d = device_get_softc(mixer->dev); + if (d == NULL) + return -1; + if (!(d->flags & SD_F_MPSAFE) && mtx_owned(mixer->lock) != 0) + dropmtx = 1; + else + dropmtx = 0; src &= mixer->recdevs; if (src == 0) src = SOUND_MASK_MIC; + /* It is safe to drop this mutex due to Giant. */ + MIXER_SET_UNLOCK(mixer, dropmtx); mixer->recsrc = MIXER_SETRECSRC(mixer, src); + MIXER_SET_LOCK(mixer, dropmtx); return 0; } @@ -230,6 +324,83 @@ return mixer->recsrc; } +/** + * @brief Retrieve the route number of the current recording device + * + * OSSv4 assigns routing numbers to recording devices, unlike the previous + * API which relied on a fixed table of device numbers and names. This + * function returns the routing number of the device currently selected + * for recording. + * + * For now, this function is kind of a goofy compatibility stub atop the + * existing sound system. (For example, in theory, the old sound system + * allows multiple recording devices to be specified via a bitmask.) + * + * @param m mixer context container thing + * + * @retval 0 success + * @retval EIDRM no recording device found (generally not possible) + * @todo Ask about error code + */ +static int +mixer_get_recroute(struct snd_mixer *m, int *route) +{ + int i, cnt; + + cnt = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + /** @todo can user set a multi-device mask? (== or &?) */ + if ((1 << i) == m->recsrc) + break; + if ((1 << i) & m->recdevs) + ++cnt; + } + + if (i == SOUND_MIXER_NRDEVICES) + return EIDRM; + + *route = cnt; + return 0; +} + +/** + * @brief Select a device for recording + * + * This function sets a recording source based on a recording device's + * routing number. Said number is translated to an old school recdev + * mask and passed over mixer_setrecsrc. + * + * @param m mixer context container thing + * + * @retval 0 success(?) + * @retval EINVAL User specified an invalid device number + * @retval otherwise error from mixer_setrecsrc + */ +static int +mixer_set_recroute(struct snd_mixer *m, int route) +{ + int i, cnt, ret; + + ret = 0; + cnt = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((1 << i) & m->recdevs) { + if (route == cnt) + break; + ++cnt; + } + } + + if (i == SOUND_MIXER_NRDEVICES) + ret = EINVAL; + else + ret = mixer_setrecsrc(m, (1 << i)); + + return ret; +} + void mix_setdevs(struct snd_mixer *m, u_int32_t v) { @@ -250,9 +421,71 @@ m->devs = v; } +/** + * @brief Record mask of available recording devices + * + * Calling functions are responsible for defining the mask of available + * recording devices. This function records that value in a structure + * used by the rest of the mixer code. + * + * This function also populates a structure used by the SNDCTL_DSP_*RECSRC* + * family of ioctls that are part of OSSV4. All recording device labels + * are concatenated in ascending order corresponding to their routing + * numbers. (Ex: a system might have 0 => 'vol', 1 => 'cd', 2 => 'line', + * etc.) For now, these labels are just the standard recording device + * names (cd, line1, etc.), but will eventually be fully dynamic and user + * controlled. + * + * @param m mixer device context container thing + * @param v mask of recording devices + */ void mix_setrecdevs(struct snd_mixer *m, u_int32_t v) { + oss_mixer_enuminfo *ei; + char *loc; + int i, nvalues, nwrote, nleft, ncopied; + + ei = &m->enuminfo; + + nvalues = 0; + nwrote = 0; + nleft = sizeof(ei->strings); + loc = ei->strings; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((1 << i) & v) { + ei->strindex[nvalues] = nwrote; + ncopied = strlcpy(loc, snd_mixernames[i], nleft) + 1; + /* strlcpy retval doesn't include terminator */ + + nwrote += ncopied; + nleft -= ncopied; + nvalues++; + + /* + * XXX I don't think this should ever be possible. + * Even with a move to dynamic device/channel names, + * each label is limited to ~16 characters, so that'd + * take a LOT to fill this buffer. + */ + if ((nleft <= 0) || (nvalues >= OSS_ENUM_MAXVALUE)) { + device_printf(m->dev, + "mix_setrecdevs: Not enough room to store device names--please file a bug report.\n"); + device_printf(m->dev, + "mix_setrecdevs: Please include details about your sound hardware, OS version, etc.\n"); + break; + } + + loc = &ei->strings[nwrote]; + } + } + + /* + * NB: The SNDCTL_DSP_GET_RECSRC_NAMES ioctl ignores the dev + * and ctrl fields. + */ + ei->nvalues = nvalues; m->recdevs = v; } @@ -322,30 +555,92 @@ return m->devinfo; } -int -mixer_init(device_t dev, kobj_class_t cls, void *devinfo) +static struct snd_mixer * +mixer_obj_create(device_t dev, kobj_class_t cls, void *devinfo, + int type, const char *desc) { - struct snddev_info *snddev; struct snd_mixer *m; - u_int16_t v; - struct cdev *pdev; - int i, unit, val; + int i; + + KASSERT(dev != NULL && cls != NULL && devinfo != NULL, + ("%s(): NULL data dev=%p cls=%p devinfo=%p", + __func__, dev, cls, devinfo)); + KASSERT(type == MIXER_TYPE_PRIMARY || type == MIXER_TYPE_SECONDARY, + ("invalid mixer type=%d", type)); m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); - snprintf(m->name, MIXER_NAMELEN, "%s:mixer", device_get_nameunit(dev)); - m->lock = snd_mtxcreate(m->name, "pcm mixer"); - m->type = cls->name; + snprintf(m->name, sizeof(m->name), "%s:mixer", + device_get_nameunit(dev)); + if (desc != NULL) { + strlcat(m->name, ":", sizeof(m->name)); + strlcat(m->name, desc, sizeof(m->name)); + } + m->lock = snd_mtxcreate(m->name, (type == MIXER_TYPE_PRIMARY) ? + "primary pcm mixer" : "secondary pcm mixer"); + m->type = type; m->devinfo = devinfo; m->busy = 0; m->dev = dev; - for (i = 0; i < 32; i++) { + for (i = 0; i < (sizeof(m->parent) / sizeof(m->parent[0])); i++) { m->parent[i] = SOUND_MIXER_NONE; m->child[i] = 0; m->realdev[i] = i; } - if (MIXER_INIT(m)) - goto bad; + if (MIXER_INIT(m)) { + snd_mtxlock(m->lock); + snd_mtxfree(m->lock); + kobj_delete((kobj_t)m, M_MIXER); + return (NULL); + } + + return (m); +} + +int +mixer_delete(struct snd_mixer *m) +{ + KASSERT(m != NULL, ("NULL snd_mixer")); + KASSERT(m->type == MIXER_TYPE_SECONDARY, + ("%s(): illegal mixer type=%d", __func__, m->type)); + + snd_mtxlock(m->lock); + + MIXER_UNINIT(m); + + snd_mtxfree(m->lock); + kobj_delete((kobj_t)m, M_MIXER); + + --mixer_count; + + return (0); +} + +struct snd_mixer * +mixer_create(device_t dev, kobj_class_t cls, void *devinfo, const char *desc) +{ + struct snd_mixer *m; + + m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_SECONDARY, desc); + + if (m != NULL) + ++mixer_count; + + return (m); +} + +int +mixer_init(device_t dev, kobj_class_t cls, void *devinfo) +{ + struct snddev_info *snddev; + struct snd_mixer *m; + u_int16_t v; + struct cdev *pdev; + int i, unit, devunit, val; + + m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_PRIMARY, NULL); + if (m == NULL) + return (-1); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { v = snd_mixerdefaults[i]; @@ -363,12 +658,15 @@ mixer_setrecsrc(m, SOUND_MASK_MIC); unit = device_get_unit(dev); - pdev = make_dev(&mixer_cdevsw, PCMMKMINOR(unit, SND_DEV_CTL, 0), + devunit = snd_mkunit(unit, SND_DEV_CTL, 0); + pdev = make_dev(&mixer_cdevsw, unit2minor(devunit), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m; snddev = device_get_softc(dev); snddev->mixer_dev = pdev; + ++mixer_count; + if (bootverbose) { for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if (!(m->devs & (1 << i))) @@ -393,13 +691,7 @@ device_printf(dev, "Soft PCM mixer ENABLED\n"); } - return 0; - -bad: - snd_mtxlock(m->lock); - snd_mtxfree(m->lock); - kobj_delete((kobj_t)m, M_MIXER); - return -1; + return (0); } int @@ -414,7 +706,12 @@ pdev = mixer_get_devt(dev); if (d == NULL || pdev == NULL || pdev->si_drv1 == NULL) return EBADF; + m = pdev->si_drv1; + KASSERT(m != NULL, ("NULL snd_mixer")); + KASSERT(m->type == MIXER_TYPE_PRIMARY, + ("%s(): illegal mixer type=%d", __func__, m->type)); + snd_mtxlock(m->lock); if (m->busy) { @@ -437,6 +734,8 @@ d->mixer_dev = NULL; + --mixer_count; + return 0; } @@ -476,7 +775,7 @@ m = oidp->oid_arg1; snd_mtxlock(m->lock); - strncpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); + strlcpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); snd_mtxunlock(m->lock); error = sysctl_handle_string(oidp, &devname[0], sizeof(devname), req); snd_mtxlock(m->lock); @@ -508,9 +807,11 @@ m->hwvol_mixer = SOUND_MIXER_VOLUME; m->hwvol_step = 5; #ifdef SND_DYNSYSCTL - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, ""); - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0, sysctl_hw_snd_hwvol_mixer, "A", ""); #endif @@ -566,42 +867,275 @@ snd_mtxunlock(m->lock); } +int +mixer_busy(struct snd_mixer *m) +{ + KASSERT(m != NULL, ("NULL snd_mixer")); + + return (m->busy); +} + +int +mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right) +{ + int ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_set(m, dev, left | (right << 8)); + snd_mtxunlock(m->lock); + + return ((ret != 0) ? ENXIO : 0); +} + +int +mix_get(struct snd_mixer *m, u_int dev) +{ + int ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_get(m, dev); + snd_mtxunlock(m->lock); + + return (ret); +} + +int +mix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + int ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_setrecsrc(m, src); + snd_mtxunlock(m->lock); + + return ((ret != 0) ? ENXIO : 0); +} + +u_int32_t +mix_getrecsrc(struct snd_mixer *m) +{ + u_int32_t ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_getrecsrc(m); + snd_mtxunlock(m->lock); + + return (ret); +} + +int +mix_get_type(struct snd_mixer *m) +{ + KASSERT(m != NULL, ("NULL snd_mixer")); + + return (m->type); +} + /* ----------------------------------------------------------------------- */ static int mixer_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { + struct snddev_info *d; struct snd_mixer *m; + + if (i_dev == NULL || i_dev->si_drv1 == NULL) + return (EBADF); + m = i_dev->si_drv1; - snd_mtxlock(m->lock); + d = device_get_softc(m->dev); + if (!PCM_REGISTERED(d)) + return (EBADF); - m->busy++; + /* XXX Need Giant magic entry ??? */ + snd_mtxlock(m->lock); + m->busy = 1; snd_mtxunlock(m->lock); - return 0; + + return (0); } static int mixer_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { + struct snddev_info *d; struct snd_mixer *m; + int ret; + + if (i_dev == NULL || i_dev->si_drv1 == NULL) + return (EBADF); m = i_dev->si_drv1; + d = device_get_softc(m->dev); + if (!PCM_REGISTERED(d)) + return (EBADF); + + /* XXX Need Giant magic entry ??? */ + snd_mtxlock(m->lock); + ret = (m->busy == 0) ? EBADF : 0; + m->busy = 0; + snd_mtxunlock(m->lock); + + return (ret); +} + +static int +mixer_ioctl_channel(struct cdev *dev, u_long cmd, caddr_t arg, int mode, + struct thread *td, int from) +{ + struct snddev_info *d; + struct snd_mixer *m; + struct pcm_channel *c, *rdch, *wrch; + pid_t pid; + int j, ret; + + if (td == NULL || td->td_proc == NULL) + return (-1); + + m = dev->si_drv1; + d = device_get_softc(m->dev); + j = cmd & 0xff; + + switch (j) { + case SOUND_MIXER_PCM: + case SOUND_MIXER_RECLEV: + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_CAPS: + case SOUND_MIXER_STEREODEVS: + break; + default: + return (-1); + break; + } - if (!m->busy) { + pid = td->td_proc->p_pid; + rdch = NULL; + wrch = NULL; + c = NULL; + ret = -1; + + /* + * This is unfair. Imagine single proc opening multiple + * instances of same direction. What we do right now + * is looking for the first matching proc/pid, and just + * that. Nothing more. Consider it done. + * + * The better approach of controlling specific channel + * pcm or rec volume is by doing mixer ioctl + * (SNDCTL_DSP_[SET|GET][PLAY|REC]VOL / SOUND_MIXER_[PCM|RECLEV] + * on its open fd, rather than cracky mixer bypassing here. + */ + CHN_FOREACH(c, d, channels.pcm.opened) { + CHN_LOCK(c); + if (c->pid != pid || + !(c->feederflags & (1 << FEEDER_VOLUME))) { + CHN_UNLOCK(c); + continue; + } + if (rdch == NULL && c->direction == PCMDIR_REC) { + rdch = c; + if (j == SOUND_MIXER_RECLEV) + goto mixer_ioctl_channel_proc; + } else if (wrch == NULL && c->direction == PCMDIR_PLAY) { + wrch = c; + if (j == SOUND_MIXER_PCM) + goto mixer_ioctl_channel_proc; + } + CHN_UNLOCK(c); + if (rdch != NULL && wrch != NULL) + break; + } + + if (rdch == NULL && wrch == NULL) + return (-1); + + if ((j == SOUND_MIXER_DEVMASK || j == SOUND_MIXER_CAPS || + j == SOUND_MIXER_STEREODEVS) && + (cmd & MIXER_READ(0)) == MIXER_READ(0)) { + snd_mtxlock(m->lock); + *(int *)arg = mix_getdevs(m); snd_mtxunlock(m->lock); - return EBADF; + if (rdch != NULL) + *(int *)arg |= SOUND_MASK_RECLEV; + if (wrch != NULL) + *(int *)arg |= SOUND_MASK_PCM; + ret = 0; } - m->busy--; - snd_mtxunlock(m->lock); - return 0; + return (ret); + +mixer_ioctl_channel_proc: + + KASSERT(c != NULL, ("%s(): NULL channel", __func__)); + CHN_LOCKASSERT(c); + + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { + CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL, + *(int *)arg & 0x7f); + CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR, + (*(int *)arg >> 8) & 0x7f); + } else if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { + *(int *)arg = CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL); + *(int *)arg |= + CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; + } + + CHN_UNLOCK(c); + + return (0); +} + +static int +mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + struct snddev_info *d; + int ret; + + if (i_dev == NULL || i_dev->si_drv1 == NULL) + return (EBADF); + + d = device_get_softc(((struct snd_mixer *)i_dev->si_drv1)->dev); + if (!PCM_REGISTERED(d)) + return (EBADF); + + PCM_GIANT_ENTER(d); + PCM_ACQUIRE_QUICK(d); + + ret = -1; + + if (mixer_bypass != 0 && (d->flags & SD_F_VPC)) + ret = mixer_ioctl_channel(i_dev, cmd, arg, mode, td, + MIXER_CMD_CDEV); + + if (ret == -1) + ret = mixer_ioctl_cmd(i_dev, cmd, arg, mode, td, + MIXER_CMD_CDEV); + + PCM_RELEASE_QUICK(d); + PCM_GIANT_LEAVE(d); + + return (ret); } +/* + * XXX Make sure you can guarantee concurrency safety before calling this + * function, be it through Giant, PCM_*, etc ! + */ int -mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td, int from) { struct snd_mixer *m; int ret, *arg_i = (int *)arg; @@ -610,12 +1144,17 @@ m = i_dev->si_drv1; if (m == NULL) - return EBADF; + return (EBADF); snd_mtxlock(m->lock); - if (mode != -1 && !m->busy) { + if (from == MIXER_CMD_CDEV && !m->busy) { snd_mtxunlock(m->lock); - return EBADF; + return (EBADF); + } + + if (cmd == SNDCTL_MIXERINFO) { + snd_mtxunlock(m->lock); + return (mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg)); } if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { @@ -624,7 +1163,7 @@ else ret = mixer_set(m, j, *arg_i); snd_mtxunlock(m->lock); - return (ret == 0)? 0 : ENXIO; + return ((ret == 0) ? 0 : ENXIO); } if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { @@ -648,25 +1187,60 @@ } *arg_i = v; snd_mtxunlock(m->lock); - return (v != -1)? 0 : ENXIO; + return ((v != -1) ? 0 : ENXIO); } + + ret = 0; + + switch (cmd) { + /** @todo Double check return values, error codes. */ + case SNDCTL_SYSINFO: + snd_mtxunlock(m->lock); + sound_oss_sysinfo((oss_sysinfo *)arg); + return (ret); + break; + case SNDCTL_AUDIOINFO: + snd_mtxunlock(m->lock); + return (dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg)); + break; + case SNDCTL_DSP_GET_RECSRC_NAMES: + bcopy((void *)&m->enuminfo, arg, sizeof(oss_mixer_enuminfo)); + break; + case SNDCTL_DSP_GET_RECSRC: + ret = mixer_get_recroute(m, arg_i); + break; + case SNDCTL_DSP_SET_RECSRC: + ret = mixer_set_recroute(m, *arg_i); + break; + case OSS_GETVERSION: + *arg_i = SOUND_VERSION; + break; + default: + ret = ENXIO; + break; + } + snd_mtxunlock(m->lock); - return ENXIO; + + return (ret); } #ifdef USING_DEVFS static void -mixer_clone(void *arg, struct ucred *cred, char *name, int namelen, - struct cdev **dev) +mixer_clone(void *arg, +#if __FreeBSD_version >= 600034 + struct ucred *cred, +#endif + char *name, int namelen, struct cdev **dev) { - struct snddev_info *sd; + struct snddev_info *d; if (*dev != NULL) return; if (strcmp(name, "mixer") == 0) { - sd = devclass_get_softc(pcm_devclass, snd_unit); - if (sd != NULL && sd->mixer_dev != NULL) { - *dev = sd->mixer_dev; + d = devclass_get_softc(pcm_devclass, snd_unit); + if (PCM_REGISTERED(d) && d->mixer_dev != NULL) { + *dev = d->mixer_dev; dev_ref(*dev); } } @@ -675,18 +1249,163 @@ static void mixer_sysinit(void *p) { + if (mixer_ehtag != NULL) + return; mixer_ehtag = EVENTHANDLER_REGISTER(dev_clone, mixer_clone, 0, 1000); } static void mixer_sysuninit(void *p) { - if (mixer_ehtag != NULL) - EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); + if (mixer_ehtag == NULL) + return; + EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); + mixer_ehtag = NULL; } SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL); #endif +/** + * @brief Handler for SNDCTL_MIXERINFO + * + * This function searches for a mixer based on the numeric ID stored + * in oss_miserinfo::dev. If set to -1, then information about the + * current mixer handling the request is provided. Note, however, that + * this ioctl may be made with any sound device (audio, mixer, midi). + * + * @note Caller must not hold any PCM device, channel, or mixer locks. + * + * See http://manuals.opensound.com/developer/SNDCTL_MIXERINFO.html for + * more information. + * + * @param i_dev character device on which the ioctl arrived + * @param arg user argument (oss_mixerinfo *) + * + * @retval EINVAL oss_mixerinfo::dev specified a bad value + * @retval 0 success + */ +int +mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi) +{ + struct snddev_info *d; + struct snd_mixer *m; + int nmix, i; + /* + * If probing the device handling the ioctl, make sure it's a mixer + * device. (This ioctl is valid on audio, mixer, and midi devices.) + */ + if (mi->dev == -1 && i_dev->si_devsw != &mixer_cdevsw) + return (EINVAL); + + d = NULL; + m = NULL; + nmix = 0; + + /* + * There's a 1:1 relationship between mixers and PCM devices, so + * begin by iterating over PCM devices and search for our mixer. + */ + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + + /* XXX Need Giant magic entry */ + + /* See the note in function docblock. */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_lock(d); + + if (d->mixer_dev != NULL && d->mixer_dev->si_drv1 != NULL && + ((mi->dev == -1 && d->mixer_dev == i_dev) || + mi->dev == nmix)) { + m = d->mixer_dev->si_drv1; + mtx_lock(m->lock); + + /* + * At this point, the following synchronization stuff + * has happened: + * - a specific PCM device is locked. + * - a specific mixer device has been locked, so be + * sure to unlock when existing. + */ + bzero((void *)mi, sizeof(*mi)); + mi->dev = nmix; + snprintf(mi->id, sizeof(mi->id), "mixer%d", i); + strlcpy(mi->name, m->name, sizeof(mi->name)); + mi->modify_counter = m->modify_counter; + mi->card_number = i; + /* + * Currently, FreeBSD assumes 1:1 relationship between + * a pcm and mixer devices, so this is hardcoded to 0. + */ + mi->port_number = 0; + + /** + * @todo Fill in @sa oss_mixerinfo::mixerhandle. + * @note From 4Front: "mixerhandle is an arbitrary + * string that identifies the mixer better than + * the device number (mixerinfo.dev). Device + * numbers may change depending on the order the + * drivers are loaded. However the handle should + * remain the same provided that the sound card + * is not moved to another PCI slot." + */ + + /** + * @note + * @sa oss_mixerinfo::magic is a reserved field. + * + * @par + * From 4Front: "magic is usually 0. However some + * devices may have dedicated setup utilities and the + * magic field may contain an unique driver specific + * value (managed by [4Front])." + */ + + mi->enabled = device_is_attached(m->dev) ? 1 : 0; + /** + * The only flag for @sa oss_mixerinfo::caps is + * currently MIXER_CAP_VIRTUAL, which I'm not sure we + * really worry about. + */ + /** + * Mixer extensions currently aren't supported, so + * leave @sa oss_mixerinfo::nrext blank for now. + */ + /** + * @todo Fill in @sa oss_mixerinfo::priority (requires + * touching drivers?) + * @note The priority field is for mixer applets to + * determine which mixer should be the default, with 0 + * being least preferred and 10 being most preferred. + * From 4Front: "OSS drivers like ICH use higher + * values (10) because such chips are known to be used + * only on motherboards. Drivers for high end pro + * devices use 0 because they will never be the + * default mixer. Other devices use values 1 to 9 + * depending on the estimated probability of being the + * default device. + * + * XXX Described by Hannu@4Front, but not found in + * soundcard.h. + strlcpy(mi->devnode, d->mixer_dev->si_name, + sizeof(mi->devnode)); + mi->legacy_device = i; + */ + mtx_unlock(m->lock); + } else + ++nmix; + + pcm_unlock(d); + + if (m != NULL) + return (0); + } + + return (EINVAL); +} --- sys/dev/sound/pcm/mixer.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/mixer.h Thu Jul 12 12:04:19 2007 @@ -23,18 +23,30 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/mixer.h,v 1.14.2.1 2007/05/13 20:53:39 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/mixer.h,v 1.19 2007/06/16 03:37:28 ariff Exp $ */ +struct snd_mixer *mixer_create(device_t dev, kobj_class_t cls, void *devinfo, + const char *desc); +int mixer_delete(struct snd_mixer *m); int mixer_init(device_t dev, kobj_class_t cls, void *devinfo); int mixer_uninit(device_t dev); int mixer_reinit(device_t dev); -int mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td); +int mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td, int from); +int mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi); int mixer_hwvol_init(device_t dev); void mixer_hwvol_mute(device_t dev); void mixer_hwvol_step(device_t dev, int left_step, int right_step); +int mixer_busy(struct snd_mixer *m); + +int mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right); +int mix_get(struct snd_mixer *m, u_int dev); +int mix_setrecsrc(struct snd_mixer *m, u_int32_t src); +u_int32_t mix_getrecsrc(struct snd_mixer *m); +int mix_get_type(struct snd_mixer *m); + void mix_setdevs(struct snd_mixer *m, u_int32_t v); void mix_setrecdevs(struct snd_mixer *m, u_int32_t v); u_int32_t mix_getdevs(struct snd_mixer *m); @@ -45,10 +57,19 @@ u_int32_t mix_getchild(struct snd_mixer *m, u_int32_t dev); void *mix_getdevinfo(struct snd_mixer *m); +extern int mixer_count; + +#define MIXER_CMD_DIRECT 0 /* send command within driver */ +#define MIXER_CMD_CDEV 1 /* send command from cdev/ioctl */ + +#define MIXER_TYPE_PRIMARY 0 /* mixer_init() */ +#define MIXER_TYPE_SECONDARY 1 /* mixer_create() */ + /* * this is a kludge to allow hiding of the struct snd_mixer definition * 512 should be enough for all architectures */ -#define MIXER_SIZE (512 + sizeof(struct kobj)) +#define MIXER_SIZE (512 + sizeof(struct kobj) + \ + sizeof(oss_mixer_enuminfo)) -#define MIXER_DECLARE(name) DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) +#define MIXER_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) --- sys/dev/sound/pcm/pcm.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/pcm.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,374 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#ifndef _SND_PCM_H_ +#define _SND_PCM_H_ + +#include + +/* + * Macros for reading/writing PCM sample / int values from bytes array. + * Since every process is done using signed integer (and to make our life + * less miserable), unsigned sample will be converted to its signed + * counterpart and restored during writing back. To avoid overflow, + * we truncate 32bit (and only 32bit) samples down to 24bit (see below + * for the reason), unless PCM_USE_64BIT_ARITH is defined. + */ + +/* + * Automatically turn on 64bit arithmetic on suitable archs + * (amd64 64bit, ia64, etc..) for wider 32bit samples / integer processing. + */ +#if LONG_BIT >= 64 +#undef PCM_USE_64BIT_ARITH +#define PCM_USE_64BIT_ARITH 1 +#endif + +typedef int32_t intpcm_t; + +typedef int32_t intpcm8_t; +typedef int32_t intpcm16_t; +typedef int32_t intpcm24_t; +#ifdef PCM_USE_64BIT_ARITH +typedef int64_t intpcm32_t; +#else +typedef int32_t intpcm32_t; +#endif + +/* 32bit fixed point shift */ +#define PCM_FXSHIFT 8 + +#define PCM_S8_MAX 0x7f +#define PCM_S8_MIN -0x80 +#define PCM_S16_MAX 0x7fff +#define PCM_S16_MIN -0x8000 +#define PCM_S24_MAX 0x7fffff +#define PCM_S24_MIN -0x800000 +#ifdef PCM_USE_64BIT_ARITH +#if LONG_BIT >= 64 +#define PCM_S32_MAX 0x7fffffffL +#define PCM_S32_MIN -0x80000000L +#else +#define PCM_S32_MAX 0x7fffffffLL +#define PCM_S32_MIN -0x80000000LL +#endif +#else +#define PCM_S32_MAX 0x7fffffff +#define PCM_S32_MIN (-0x7fffffff - 1) +#endif + +/* Bytes-per-sample definition */ +#define PCM_8_BPS 1 +#define PCM_16_BPS 2 +#define PCM_24_BPS 3 +#define PCM_32_BPS 4 + +#define INTPCM_T(v) ((intpcm_t)(v)) +#define INTPCM8_T(v) ((intpcm8_t)(v)) +#define INTPCM16_T(v) ((intpcm16_t)(v)) +#define INTPCM24_T(v) ((intpcm24_t)(v)) +#define INTPCM32_T(v) ((intpcm32_t)(v)) + +#if BYTE_ORDER == LITTLE_ENDIAN +#define _PCM_READ_S16_LE(b8) INTPCM_T(*((int16_t *)(b8))) +#define _PCM_READ_S32_LE(b8) INTPCM_T(*((int32_t *)(b8))) +#define _PCM_READ_S16_BE(b8) \ + INTPCM_T((b8)[1] | (((int8_t)((b8)[0])) << 8)) +#define _PCM_READ_S32_BE(b8) \ + INTPCM_T((b8)[3] | ((b8)[2] << 8) | ((b8)[1] << 16) | \ + (((int8_t)((b8)[0])) << 24)) + +#define _PCM_WRITE_S16_LE(b8, val) do { \ + *((int16_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_S32_LE(b8, val) do { \ + *((int32_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_S16_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = val >> 8; \ +} while(0) +#define _PCM_WRITE_S32_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = val >> 24; \ +} while(0) + +#define _PCM_READ_U16_LE(b8) \ + INTPCM_T((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_LE(b8) \ + INTPCM_T((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) +#define _PCM_READ_U16_BE(b8) \ + INTPCM_T((b8)[1] | (((int8_t)((b8)[0] ^ 0x80)) << 8)) +#define _PCM_READ_U32_BE(b8) \ + INTPCM_T((b8)[3] | ((b8)[2] << 8) | ((b8)[1] << 16) | \ + (((int8_t)((b8)[0] ^ 0x80)) << 24)) + +#define _PCM_WRITE_U16_LE(b8, val) do { \ + *((uint16_t *)(b8)) = (val) ^ 0x8000; \ +} while(0) +#define _PCM_WRITE_U32_LE(b8, val) do { \ + *((uint32_t *)(b8)) = (val) ^ 0x80000000; \ +} while(0) +#define _PCM_WRITE_U16_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = (val >> 8) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U32_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = (val >> 24) ^ 0x80; \ +} while(0) +#else /* !LITTLE_ENDIAN */ +#define _PCM_READ_S16_LE(b8) \ + INTPCM_T((b8)[0] | (((int8_t)((b8)[1])) << 8)) +#define _PCM_READ_S32_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | ((b8)[2] << 16) | \ + (((int8_t)((b8)[3])) << 24)) +#define _PCM_READ_S16_BE(b8) INTPCM_T(*((int16_t *)(b8))) +#define _PCM_READ_S32_BE(b8) INTPCM_T(*((int32_t *)(b8))) + +#define _PCM_WRITE_S16_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ +} while(0) +#define _PCM_WRITE_S32_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = val >> 24; \ +} while(0) +#define _PCM_WRITE_S16_BE(b8, val) do { \ + *((int16_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_S32_BE(b8, val) do { \ + *((int32_t *)(b8)) = (val); \ +} while(0) + +#define _PCM_READ_U16_LE(b8) \ + INTPCM_T((b8)[0] | (((int8_t)((b8)[1] ^ 0x80)) << 8)) +#define _PCM_READ_U32_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | ((b8)[2] << 16) | \ + (((int8_t)((b8)[3] ^ 0x80)) << 24)) +#define _PCM_READ_U16_BE(b8) \ + INTPCM_T((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_BE(b8) \ + INTPCM_T((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) + +#define _PCM_WRITE_U16_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = (val >> 8) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U32_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = (val >> 24) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U16_BE(b8, val) do { \ + *((uint16_t *)(b8)) = (val) ^ 0x8000; \ +} while(0) +#define _PCM_WRITE_U32_BE(b8, val) do { \ + *((uint32_t *)(b8)) = (val) ^ 0x80000000; \ +} while(0) +#endif + +#define _PCM_READ_S24_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | (((int8_t)((b8)[2])) << 16)) +#define _PCM_READ_S24_BE(b8) \ + INTPCM_T((b8)[2] | ((b8)[1] << 8) | (((int8_t)((b8)[0])) << 16)) + +#define _PCM_WRITE_S24_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ +} while(0) +#define _PCM_WRITE_S24_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = val >> 16; \ +} while(0) + +#define _PCM_READ_U24_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | \ + (((int8_t)((b8)[2] ^ 0x80)) << 16)) +#define _PCM_READ_U24_BE(b8) \ + INTPCM_T((b8)[2] | ((b8)[1] << 8) | \ + (((int8_t)((b8)[0] ^ 0x80)) << 16)) + +#define _PCM_WRITE_U24_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = (val >> 16) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U24_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = (val >> 16) ^ 0x80; \ +} while(0) + +/* + * 8bit sample is pretty much useless since it doesn't provide + * sufficient dynamic range throughout our filtering process. + * For the sake of completeness, declare it anyway. + */ +#define _PCM_READ_S8_NE(b8) INTPCM_T(*((int8_t *)(b8))) +#define _PCM_READ_U8_NE(b8) \ + INTPCM_T((int8_t)(*((uint8_t *)(b8)) ^ 0x80)) + +#define _PCM_WRITE_S8_NE(b8, val) do { \ + *((int8_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_U8_NE(b8, val) do { \ + *((uint8_t *)(b8)) = (val) ^ 0x80; \ +} while(0) + +/* + * Common macross. Use this instead of "_", unless we want + * the real sample value. + */ + +/* 8bit */ +#define PCM_READ_S8_NE(b8) _PCM_READ_S8_NE(b8) +#define PCM_READ_U8_NE(b8) _PCM_READ_U8_NE(b8) +#define PCM_WRITE_S8_NE(b8, val) _PCM_WRITE_S8_NE(b8, val) +#define PCM_WRITE_U8_NE(b8, val) _PCM_WRITE_U8_NE(b8, val) + +/* 16bit */ +#define PCM_READ_S16_LE(b8) _PCM_READ_S16_LE(b8) +#define PCM_READ_S16_BE(b8) _PCM_READ_S16_BE(b8) +#define PCM_READ_U16_LE(b8) _PCM_READ_U16_LE(b8) +#define PCM_READ_U16_BE(b8) _PCM_READ_U16_BE(b8) + +#define PCM_WRITE_S16_LE(b8, val) _PCM_WRITE_S16_LE(b8, val) +#define PCM_WRITE_S16_BE(b8, val) _PCM_WRITE_S16_BE(b8, val) +#define PCM_WRITE_U16_LE(b8, val) _PCM_WRITE_U16_LE(b8, val) +#define PCM_WRITE_U16_BE(b8, val) _PCM_WRITE_U16_BE(b8, val) + +/* 24bit */ +#define PCM_READ_S24_LE(b8) _PCM_READ_S24_LE(b8) +#define PCM_READ_S24_BE(b8) _PCM_READ_S24_BE(b8) +#define PCM_READ_U24_LE(b8) _PCM_READ_U24_LE(b8) +#define PCM_READ_U24_BE(b8) _PCM_READ_U24_BE(b8) + +#define PCM_WRITE_S24_LE(b8, val) _PCM_WRITE_S24_LE(b8, val) +#define PCM_WRITE_S24_BE(b8, val) _PCM_WRITE_S24_BE(b8, val) +#define PCM_WRITE_U24_LE(b8, val) _PCM_WRITE_U24_LE(b8, val) +#define PCM_WRITE_U24_BE(b8, val) _PCM_WRITE_U24_BE(b8, val) + +/* 32bit */ +#ifdef PCM_USE_64BIT_ARITH +#define PCM_READ_S32_LE(b8) _PCM_READ_S32_LE(b8) +#define PCM_READ_S32_BE(b8) _PCM_READ_S32_BE(b8) +#define PCM_READ_U32_LE(b8) _PCM_READ_U32_LE(b8) +#define PCM_READ_U32_BE(b8) _PCM_READ_U32_BE(b8) + +#define PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) +#define PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) +#define PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) +#define PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) +#else /* !PCM_USE_64BIT_ARITH */ +/* + * 24bit integer ?!? This is quite unfortunate, eh? Get the fact straight: + * Dynamic range for: + * 1) Human =~ 140db + * 2) 16bit = 96db (close enough) + * 3) 24bit = 144db (perfect) + * 4) 32bit = 196db (way too much) + * 5) Bugs Bunny = Gazillion!@%$Erbzzztt-EINVAL db + * Since we're not Bugs Bunny ..uh..err.. avoiding 64bit arithmetic, 24bit + * is pretty much sufficient for our signed integer processing. + */ +#define PCM_READ_S32_LE(b8) (_PCM_READ_S32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_S32_BE(b8) (_PCM_READ_S32_BE(b8) >> PCM_FXSHIFT) +#define PCM_READ_U32_LE(b8) (_PCM_READ_U32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_U32_BE(b8) (_PCM_READ_U32_BE(b8) >> PCM_FXSHIFT) + +#define PCM_WRITE_S32_LE(b8, val) \ + _PCM_WRITE_S32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_S32_BE(b8, val) \ + _PCM_WRITE_S32_BE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_U32_LE(b8, val) \ + _PCM_WRITE_U32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_U32_BE(b8, val) \ + _PCM_WRITE_U32_BE(b8, (val) << PCM_FXSHIFT) +#endif + +#define PCM_CLAMP_S8(val) \ + (((val) > PCM_S8_MAX) ? PCM_S8_MAX : \ + (((val) < PCM_S8_MIN) ? PCM_S8_MIN : (val))) +#define PCM_CLAMP_S16(val) \ + (((val) > PCM_S16_MAX) ? PCM_S16_MAX : \ + (((val) < PCM_S16_MIN) ? PCM_S16_MIN : (val))) +#define PCM_CLAMP_S24(val) \ + (((val) > PCM_S24_MAX) ? PCM_S24_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S24_MIN : (val))) + +#ifdef PCM_USE_64BIT_ARITH +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S32_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S32_MIN) ? PCM_S32_MIN : (val))) +#else +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S24_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S32_MIN : \ + ((val) << PCM_FXSHIFT))) +#endif + +#define PCM_CLAMP_U8(val) PCM_CLAMP_S8(val) +#define PCM_CLAMP_U16(val) PCM_CLAMP_S16(val) +#define PCM_CLAMP_U24(val) PCM_CLAMP_S24(val) +#define PCM_CLAMP_U32(val) PCM_CLAMP_S32(val) + +#endif /* !_SND_PCM_H_ */ --- sys/dev/sound/pcm/sndstat.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/sndstat.c Thu Jul 12 12:04:19 2007 @@ -25,12 +25,13 @@ */ #include -#include +#include +#include #ifdef USING_MUTEX #include #endif -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sndstat.c,v 1.20.2.2 2005/12/30 19:55:54 netchild Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sndstat.c,v 1.28 2007/06/16 03:37:28 ariff Exp $"); #define SS_TYPE_MODULE 0 #define SS_TYPE_FIRST 1 @@ -45,7 +46,6 @@ static struct cdevsw sndstat_cdevsw = { .d_version = D_VERSION, - .d_flags = D_NEEDGIANT, .d_open = sndstat_open, .d_close = sndstat_close, .d_read = sndstat_read, @@ -61,22 +61,55 @@ }; #ifdef USING_MUTEX -static struct sx sndstat_lock; +static struct mtx sndstat_lock; #endif static struct sbuf sndstat_sbuf; -static struct cdev *sndstat_dev = 0; -static int sndstat_isopen = 0; -static int sndstat_bufptr; +static struct cdev *sndstat_dev = NULL; +static int sndstat_bufptr = -1; static int sndstat_maxunit = -1; static int sndstat_files = 0; +#define SNDSTAT_PID(x) ((pid_t)((intptr_t)((x)->si_drv1))) +#define SNDSTAT_PID_SET(x, y) (x)->si_drv1 = (void *)((intptr_t)(y)) +#define SNDSTAT_FLUSH() do { \ + if (sndstat_bufptr != -1) { \ + sbuf_delete(&sndstat_sbuf); \ + sndstat_bufptr = -1; \ + } \ +} while(0) + static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none); -static int sndstat_verbose = 1; +int snd_verbose = 1; #ifdef USING_MUTEX -TUNABLE_INT("hw.snd.verbose", &sndstat_verbose); +TUNABLE_INT("hw.snd.verbose", &snd_verbose); #else -TUNABLE_INT_DECL("hw.snd.verbose", 1, sndstat_verbose); +TUNABLE_INT_DECL("hw.snd.verbose", 1, snd_verbose); +#endif + +#ifdef SND_DEBUG +static int +sysctl_hw_snd_sndstat_pid(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + if (sndstat_dev == NULL) + return (EINVAL); + + mtx_lock(&sndstat_lock); + val = (int)SNDSTAT_PID(sndstat_dev); + mtx_unlock(&sndstat_lock); + err = sysctl_handle_int(oidp, &val, 0, req); + if (err == 0 && req->newptr != NULL && val == 0) { + mtx_lock(&sndstat_lock); + SNDSTAT_FLUSH(); + SNDSTAT_PID_SET(sndstat_dev, 0); + mtx_unlock(&sndstat_lock); + } + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, sndstat_pid, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_sndstat_pid, "I", "sndstat busy pid"); #endif static int sndstat_prepare(struct sbuf *s); @@ -86,60 +119,61 @@ { int error, verbose; - verbose = sndstat_verbose; - error = sysctl_handle_int(oidp, &verbose, sizeof(verbose), req); + verbose = snd_verbose; + error = sysctl_handle_int(oidp, &verbose, 0, req); if (error == 0 && req->newptr != NULL) { - sx_xlock(&sndstat_lock); - if (verbose < 0 || verbose > 3) + mtx_lock(&sndstat_lock); + if (verbose < 0 || verbose > 4) error = EINVAL; else - sndstat_verbose = verbose; - sx_xunlock(&sndstat_lock); + snd_verbose = verbose; + mtx_unlock(&sndstat_lock); } return error; } SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_sndverbose, "I", ""); + 0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level"); static int sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { - int error; + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; - sx_xlock(&sndstat_lock); - if (sndstat_isopen) { - sx_xunlock(&sndstat_lock); + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(i_dev) != 0) { + mtx_unlock(&sndstat_lock); return EBUSY; } - sndstat_isopen = 1; - sx_xunlock(&sndstat_lock); - if (sbuf_new(&sndstat_sbuf, NULL, 4096, 0) == NULL) { - error = ENXIO; - goto out; + SNDSTAT_PID_SET(i_dev, td->td_proc->p_pid); + mtx_unlock(&sndstat_lock); + if (sbuf_new(&sndstat_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { + mtx_lock(&sndstat_lock); + SNDSTAT_PID_SET(i_dev, 0); + mtx_unlock(&sndstat_lock); + return ENXIO; } sndstat_bufptr = 0; - error = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM; -out: - if (error) { - sx_xlock(&sndstat_lock); - sndstat_isopen = 0; - sx_xunlock(&sndstat_lock); - } - return (error); + return 0; } static int sndstat_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { - sx_xlock(&sndstat_lock); - if (!sndstat_isopen) { - sx_xunlock(&sndstat_lock); + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(i_dev) == 0) { + mtx_unlock(&sndstat_lock); return EBADF; } - sbuf_delete(&sndstat_sbuf); - sndstat_isopen = 0; - sx_xunlock(&sndstat_lock); + SNDSTAT_FLUSH(); + SNDSTAT_PID_SET(i_dev, 0); + + mtx_unlock(&sndstat_lock); + return 0; } @@ -148,16 +182,31 @@ { int l, err; - sx_xlock(&sndstat_lock); - if (!sndstat_isopen) { - sx_xunlock(&sndstat_lock); + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(i_dev) != buf->uio_td->td_proc->p_pid || + sndstat_bufptr == -1) { + mtx_unlock(&sndstat_lock); return EBADF; } + mtx_unlock(&sndstat_lock); + + if (sndstat_bufptr == 0) { + err = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM; + if (err) { + mtx_lock(&sndstat_lock); + SNDSTAT_FLUSH(); + mtx_unlock(&sndstat_lock); + return err; + } + } + l = min(buf->uio_resid, sbuf_len(&sndstat_sbuf) - sndstat_bufptr); err = (l > 0)? uiomove(sbuf_data(&sndstat_sbuf) + sndstat_bufptr, l, buf) : 0; sndstat_bufptr += l; - sx_xunlock(&sndstat_lock); return err; } @@ -177,28 +226,34 @@ } int -sndstat_acquire(void) +sndstat_acquire(struct thread *td) { - sx_xlock(&sndstat_lock); - if (sndstat_isopen) { - sx_xunlock(&sndstat_lock); + if (sndstat_dev == NULL) + return EBADF; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(sndstat_dev) != 0) { + mtx_unlock(&sndstat_lock); return EBUSY; } - sndstat_isopen = 1; - sx_xunlock(&sndstat_lock); + SNDSTAT_PID_SET(sndstat_dev, td->td_proc->p_pid); + mtx_unlock(&sndstat_lock); return 0; } int -sndstat_release(void) +sndstat_release(struct thread *td) { - sx_xlock(&sndstat_lock); - if (!sndstat_isopen) { - sx_xunlock(&sndstat_lock); + if (sndstat_dev == NULL) + return EBADF; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(sndstat_dev) != td->td_proc->p_pid) { + mtx_unlock(&sndstat_lock); return EBADF; } - sndstat_isopen = 0; - sx_xunlock(&sndstat_lock); + SNDSTAT_PID_SET(sndstat_dev, 0); + mtx_unlock(&sndstat_lock); return 0; } @@ -225,22 +280,19 @@ unit = -1; } - ent = malloc(sizeof *ent, M_DEVBUF, M_ZERO | M_WAITOK); - if (!ent) - return ENOSPC; - + ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO); ent->dev = dev; ent->str = str; ent->type = type; ent->unit = unit; ent->handler = handler; - sx_xlock(&sndstat_lock); + mtx_lock(&sndstat_lock); SLIST_INSERT_HEAD(&sndstat_devlist, ent, link); if (type == SS_TYPE_MODULE) sndstat_files++; sndstat_maxunit = (unit > sndstat_maxunit)? unit : sndstat_maxunit; - sx_xunlock(&sndstat_lock); + mtx_unlock(&sndstat_lock); return 0; } @@ -256,17 +308,17 @@ { struct sndstat_entry *ent; - sx_xlock(&sndstat_lock); + mtx_lock(&sndstat_lock); SLIST_FOREACH(ent, &sndstat_devlist, link) { if (ent->dev == dev) { SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); - sx_xunlock(&sndstat_lock); + mtx_unlock(&sndstat_lock); free(ent, M_DEVBUF); return 0; } } - sx_xunlock(&sndstat_lock); + mtx_unlock(&sndstat_lock); return ENXIO; } @@ -276,18 +328,18 @@ { struct sndstat_entry *ent; - sx_xlock(&sndstat_lock); + mtx_lock(&sndstat_lock); SLIST_FOREACH(ent, &sndstat_devlist, link) { if (ent->dev == NULL && ent->str == str) { SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); sndstat_files--; - sx_xunlock(&sndstat_lock); + mtx_unlock(&sndstat_lock); free(ent, M_DEVBUF); return 0; } } - sx_xunlock(&sndstat_lock); + mtx_unlock(&sndstat_lock); return ENXIO; } @@ -298,9 +350,11 @@ sndstat_prepare(struct sbuf *s) { struct sndstat_entry *ent; + struct snddev_info *d; int i, j; - sbuf_printf(s, "FreeBSD Audio Driver (newpcm)\n"); + sbuf_printf(s, "FreeBSD Audio Driver (newpcm: %ubit %d/%s)\n", + (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION, MACHINE_ARCH); if (SLIST_EMPTY(&sndstat_devlist)) { sbuf_printf(s, "No devices installed.\n"); sbuf_finish(s); @@ -314,18 +368,25 @@ ent = sndstat_find(j, i); if (!ent) continue; + d = device_get_softc(ent->dev); + if (!PCM_REGISTERED(d)) + continue; + /* XXX Need Giant magic entry ??? */ + PCM_ACQUIRE_QUICK(d); sbuf_printf(s, "%s:", device_get_nameunit(ent->dev)); sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); - sbuf_printf(s, " %s", ent->str); + sbuf_printf(s, " %s [%s]", ent->str, + (d->flags & SD_F_MPSAFE) ? "MPSAFE" : "GIANT"); if (ent->handler) - ent->handler(s, ent->dev, sndstat_verbose); + ent->handler(s, ent->dev, snd_verbose); else sbuf_printf(s, " [no handler]"); sbuf_printf(s, "\n"); + PCM_RELEASE_QUICK(d); } } - if (sndstat_verbose >= 3 && sndstat_files > 0) { + if (snd_verbose >= 3 && sndstat_files > 0) { sbuf_printf(s, "\nFile Versions:\n"); SLIST_FOREACH(ent, &sndstat_devlist, link) { @@ -341,27 +402,34 @@ static int sndstat_init(void) { - sx_init(&sndstat_lock, "sndstat"); - sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, UID_ROOT, GID_WHEEL, 0444, "sndstat"); - - return (sndstat_dev != 0)? 0 : ENXIO; + if (sndstat_dev != NULL) + return EINVAL; + mtx_init(&sndstat_lock, "sndstat", "sndstat lock", MTX_DEF); + sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, + UID_ROOT, GID_WHEEL, 0444, "sndstat"); + return 0; } static int sndstat_uninit(void) { - sx_xlock(&sndstat_lock); - if (sndstat_isopen) { - sx_xunlock(&sndstat_lock); + if (sndstat_dev == NULL) + return EINVAL; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(sndstat_dev) != curthread->td_proc->p_pid) { + mtx_unlock(&sndstat_lock); return EBUSY; } - if (sndstat_dev) - destroy_dev(sndstat_dev); - sndstat_dev = 0; + SNDSTAT_FLUSH(); - sx_xunlock(&sndstat_lock); - sx_destroy(&sndstat_lock); + mtx_unlock(&sndstat_lock); + + destroy_dev(sndstat_dev); + sndstat_dev = NULL; + + mtx_destroy(&sndstat_lock); return 0; } @@ -382,5 +450,3 @@ SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); - - --- sys/dev/sound/pcm/sndstat.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/sndstat.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,147 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#ifndef _SND_SNDSTAT_H_ +#define _SND_SNDSTAT_H_ + +#define SNDSTAT_PREPARE_PCM_ARGS \ + struct sbuf *s, device_t dev, int verbose + +#define SNDSTAT_PREPARE_PCM_BEGIN() do { \ + struct snddev_info *d; \ + struct pcm_channel *c; \ + struct pcm_feeder *f; \ + \ + if (verbose < 1) \ + return (0); \ + \ + d = device_get_softc(dev); \ + PCM_BUSYASSERT(d); \ + \ + if (CHN_EMPTY(d, channels.pcm)) { \ + sbuf_printf(s, " (mixer only)"); \ + return (0); \ + } \ + \ + sbuf_printf(s, " (%dp:%dv/%dr:%dv channels %splex%s)", \ + d->playcount, d->pvchancount, d->reccount, d->rvchancount, \ + (d->flags & SD_F_SIMPLEX) ? "sim" : "du", \ + (device_get_unit(dev) == snd_unit) ? " default" : "") + + +#define SNDSTAT_PREPARE_PCM_END() \ + if (verbose <= 1) \ + return (0); \ + \ + CHN_FOREACH(c, d, channels.pcm) { \ + \ + KASSERT(c->bufhard != NULL && c->bufsoft != NULL, \ + ("hosed pcm channel setup")); \ + \ + sbuf_printf(s, "\n\t"); \ + \ + sbuf_printf(s, "%s[%s]: ", \ + (c->parentchannel != NULL) ? \ + c->parentchannel->name : "", c->name); \ + sbuf_printf(s, "spd %d", c->speed); \ + if (c->speed != sndbuf_getspd(c->bufhard)) \ + sbuf_printf(s, "/%d", \ + sndbuf_getspd(c->bufhard)); \ + sbuf_printf(s, ", fmt 0x%08x", c->format); \ + if (c->format != sndbuf_getfmt(c->bufhard)) \ + sbuf_printf(s, "/0x%08x", \ + sndbuf_getfmt(c->bufhard)); \ + sbuf_printf(s, ", flags 0x%08x, 0x%08x", \ + c->flags, c->feederflags); \ + if (c->pid != -1) \ + sbuf_printf(s, ", pid %d (%s)", \ + c->pid, c->comm); \ + sbuf_printf(s, "\n\t"); \ + \ + sbuf_printf(s, "interrupts %d, ", c->interrupts); \ + \ + if (c->direction == PCMDIR_REC) \ + sbuf_printf(s, \ + "overruns %d, feed %u, hfree %d, " \ + "sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", \ + c->xruns, c->feedcount, \ + sndbuf_getfree(c->bufhard), \ + sndbuf_getfree(c->bufsoft), \ + sndbuf_getsize(c->bufhard), \ + sndbuf_getblksz(c->bufhard), \ + sndbuf_getblkcnt(c->bufhard), \ + sndbuf_getsize(c->bufsoft), \ + sndbuf_getblksz(c->bufsoft), \ + sndbuf_getblkcnt(c->bufsoft)); \ + else \ + sbuf_printf(s, \ + "underruns %d, feed %u, ready %d " \ + "[b:%d/%d/%d|bs:%d/%d/%d]", \ + c->xruns, c->feedcount, \ + sndbuf_getready(c->bufsoft), \ + sndbuf_getsize(c->bufhard), \ + sndbuf_getblksz(c->bufhard), \ + sndbuf_getblkcnt(c->bufhard), \ + sndbuf_getsize(c->bufsoft), \ + sndbuf_getblksz(c->bufsoft), \ + sndbuf_getblkcnt(c->bufsoft)); \ + sbuf_printf(s, "\n\t"); \ + \ + sbuf_printf(s, "{%s}", \ + (c->direction == PCMDIR_REC) ? "hardware" : \ + "userland"); \ + sbuf_printf(s, " -> "); \ + f = c->feeder; \ + while (f->source != NULL) \ + f = f->source; \ + while (f != NULL) { \ + sbuf_printf(s, "%s", f->class->name); \ + if (f->desc->type == FEEDER_FMT) \ + sbuf_printf(s, "(0x%08x -> 0x%08x)", \ + f->desc->in, f->desc->out); \ + if (f->desc->type == FEEDER_RATE) \ + sbuf_printf(s, "(%d -> %d)", \ + FEEDER_GET(f, FEEDRATE_SRC), \ + FEEDER_GET(f, FEEDRATE_DST)); \ + if (f->desc->type == FEEDER_ROOT || \ + f->desc->type == FEEDER_MIXER || \ + f->desc->type == FEEDER_VOLUME) \ + sbuf_printf(s, "(0x%08x)", \ + f->desc->out); \ + sbuf_printf(s, " -> "); \ + f = f->parent; \ + } \ + sbuf_printf(s, "{%s}", \ + (c->direction == PCMDIR_REC) ? "userland" : \ + "hardware"); \ + } \ + \ + return (0); \ +} while(0) + +#endif /* !_SND_SNDSTAT_H_ */ --- sys/dev/sound/pcm/sound.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/sound.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * (C) 1997 Luigi Rizzo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,44 +26,59 @@ */ #include +#include #include #include +#include +#include +#include #include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sound.c,v 1.93.2.5 2007/06/04 09:06:05 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sound.c,v 1.119 2007/06/17 19:02:05 ariff Exp $"); devclass_t pcm_devclass; int pcm_veto_load = 1; #ifdef USING_DEVFS -int snd_unit = 0; -TUNABLE_INT("hw.snd.unit", &snd_unit); +int snd_unit = -1; +TUNABLE_INT("hw.snd.default_unit", &snd_unit); #endif -int snd_maxautovchans = 4; +static int snd_unit_auto = 0; +TUNABLE_INT("hw.snd.default_auto", &snd_unit_auto); +SYSCTL_INT(_hw_snd, OID_AUTO, default_auto, CTLFLAG_RW, + &snd_unit_auto, 0, "assign default unit to a newly attached device"); + +int snd_maxautovchans = 16; +/* XXX: a tunable implies that we may need more than one sound channel before + the system can change a sysctl (/etc/sysctl.conf), do we really need + this? */ TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); -static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); - -struct sysctl_ctx_list * -snd_sysctl_tree(device_t dev) -{ - struct snddev_info *d = device_get_softc(dev); +/* + * XXX I've had enough with people not telling proper version/arch + * while reporting problems, not after 387397913213th questions/requests. + */ +static const char snd_driver_version[] = + __XSTRING(SND_DRV_VERSION)"/"MACHINE_ARCH; +SYSCTL_STRING(_hw_snd, OID_AUTO, version, CTLFLAG_RD, &snd_driver_version, + 0, "driver version/arch"); - return &d->sysctl_tree; -} +/** + * @brief Unit number allocator for syncgroup IDs + */ +struct unrhdr *pcmsg_unrhdr = NULL; -struct sysctl_oid * -snd_sysctl_tree_top(device_t dev) +static int +sndstat_prepare_pcm(SNDSTAT_PREPARE_PCM_ARGS) { - struct snddev_info *d = device_get_softc(dev); - - return d->sysctl_tree_top; + SNDSTAT_PREPARE_PCM_BEGIN(); + SNDSTAT_PREPARE_PCM_END(); } void * @@ -73,8 +88,6 @@ struct mtx *m; m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); - if (m == NULL) - return NULL; mtx_init(m, desc, type, MTX_DEF); return m; #else @@ -129,13 +142,22 @@ int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) { + struct snddev_info *d; #ifdef USING_MUTEX flags &= INTR_MPSAFE; flags |= INTR_TYPE_AV; #else flags = INTR_TYPE_AV; #endif - return bus_setup_intr(dev, res, flags, hand, param, cookiep); + d = device_get_softc(dev); + if (d != NULL && (flags & INTR_MPSAFE)) + d->flags |= SD_F_MPSAFE; + + return bus_setup_intr(dev, res, flags, +#if __FreeBSD_version >= 700031 + NULL, +#endif + hand, param, cookiep); } #ifndef PCM_DEBUG_MTX @@ -158,136 +180,186 @@ return d->fakechan; } +static void +pcm_clonereset(struct snddev_info *d) +{ + int cmax; + + PCM_BUSYASSERT(d); + + cmax = d->playcount + d->reccount - 1; + if (d->pvchancount > 0) + cmax += MAX(d->pvchancount, snd_maxautovchans) - 1; + if (d->rvchancount > 0) + cmax += MAX(d->rvchancount, snd_maxautovchans) - 1; + if (cmax > PCMMAXCLONE) + cmax = PCMMAXCLONE; + (void)snd_clone_gc(d->clones); + (void)snd_clone_setmaxunit(d->clones, cmax); +} + static int -pcm_setvchans(struct snddev_info *d, int newcnt) +pcm_setvchans(struct snddev_info *d, int direction, int newcnt, int num) { - struct snddev_channel *sce = NULL; - struct pcm_channel *c = NULL; - int err = 0, vcnt, dcnt, i; + struct pcm_channel *c, *ch, *nch; + int err, vcnt; - pcm_inprog(d, 1); - - if (!(d->flags & SD_F_AUTOVCHAN)) { - err = EINVAL; - goto setvchans_out; - } + PCM_BUSYASSERT(d); - vcnt = d->vchancount; - dcnt = d->playcount + d->reccount; + if ((direction == PCMDIR_PLAY && d->playcount < 1) || + (direction == PCMDIR_REC && d->reccount < 1)) + return (ENODEV); - if (newcnt < 0 || (dcnt + newcnt) > (PCMMAXCHAN + 1)) { - err = E2BIG; - goto setvchans_out; - } + if (!(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); + + if (newcnt < 0 || newcnt > SND_MAXVCHANS) + return (E2BIG); - dcnt += vcnt; + if (direction == PCMDIR_PLAY) + vcnt = d->pvchancount; + else if (direction == PCMDIR_REC) + vcnt = d->rvchancount; + else + return (EINVAL); if (newcnt > vcnt) { + KASSERT(num == -1 || + (num >= 0 && num < SND_MAXVCHANS && (newcnt - 1) == vcnt), + ("bogus vchan_create() request num=%d newcnt=%d vcnt=%d", + num, newcnt, vcnt)); /* add new vchans - find a parent channel first */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + ch = NULL; + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY && - ((c->flags & CHN_F_HAS_VCHAN) || - (vcnt == 0 && - !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) - goto addok; + if (c->direction == direction && + ((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 && + c->refcount < 1 && + !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) { + ch = c; + break; + } CHN_UNLOCK(c); } - err = EBUSY; - goto setvchans_out; -addok: - c->flags |= CHN_F_BUSY; + if (ch == NULL) + return (EBUSY); + ch->flags |= CHN_F_BUSY; + err = 0; while (err == 0 && newcnt > vcnt) { - if (dcnt > PCMMAXCHAN) { - device_printf(d->dev, "%s: Maximum channel reached.\n", __func__); - break; - } - err = vchan_create(c); - if (err == 0) { + err = vchan_create(ch, num); + if (err == 0) vcnt++; - dcnt++; - } else if (err == E2BIG && newcnt > vcnt) - device_printf(d->dev, "%s: err=%d Maximum channel reached.\n", __func__, err); + else if (err == E2BIG && newcnt > vcnt) + device_printf(d->dev, + "%s: err=%d Maximum channel reached.\n", + __func__, err); } if (vcnt == 0) - c->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(c); + ch->flags &= ~CHN_F_BUSY; + CHN_UNLOCK(ch); + if (err != 0) + return (err); + else + pcm_clonereset(d); } else if (newcnt < vcnt) { -#define ORPHAN_CDEVT(cdevt) \ - ((cdevt) == NULL || ((cdevt)->si_drv1 == NULL && \ - (cdevt)->si_drv2 == NULL)) - while (err == 0 && newcnt < vcnt) { - i = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY && - (c->flags & CHN_F_VIRTUAL) && - (i++ == newcnt)) { - if (!(c->flags & CHN_F_BUSY) && - ORPHAN_CDEVT(sce->dsp_devt) && - ORPHAN_CDEVT(sce->dspW_devt) && - ORPHAN_CDEVT(sce->audio_devt) && - ORPHAN_CDEVT(sce->dspr_devt)) - goto remok; - /* - * Either we're busy, or our cdev - * has been stolen by dsp_clone(). - * Skip, and increase newcnt. - */ - if (!(c->flags & CHN_F_BUSY)) - device_printf(d->dev, - "%s: <%s> somebody steal my cdev!\n", - __func__, c->name); - newcnt++; - } + KASSERT(num == -1, + ("bogus vchan_destroy() request num=%d", num)); + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction != direction || + CHN_EMPTY(c, children) || + !(c->flags & CHN_F_HAS_VCHAN)) { CHN_UNLOCK(c); + continue; + } + CHN_FOREACH_SAFE(ch, c, nch, children) { + CHN_LOCK(ch); + if (vcnt == 1 && c->refcount > 0) { + CHN_UNLOCK(ch); + break; + } if (!(ch->flags & CHN_F_BUSY) && + ch->refcount < 1) { + err = vchan_destroy(ch); + if (err == 0) + vcnt--; + } else + CHN_UNLOCK(ch); + if (vcnt == newcnt) + break; } - if (vcnt != newcnt) - err = EBUSY; - break; -remok: CHN_UNLOCK(c); - err = vchan_destroy(c); - if (err == 0) - vcnt--; - else - device_printf(d->dev, - "%s: WARNING: vchan_destroy() failed!", - __func__); + break; } + pcm_clonereset(d); } -setvchans_out: - pcm_inprog(d, -1); - return err; + return (0); } /* return error status and a locked channel */ int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, - pid_t pid, int chnum) + pid_t pid, char *comm, int devunit) { struct pcm_channel *c; - struct snddev_channel *sce; - int err; + int err, vchancount, vchan_num; + + KASSERT(d != NULL && ch != NULL && (devunit == -1 || + !(devunit & ~(SND_U_MASK | SND_D_MASK | SND_C_MASK))) && + (direction == PCMDIR_PLAY || direction == PCMDIR_REC), + ("%s(): invalid d=%p ch=%p direction=%d pid=%d devunit=%d", + __func__, d, ch, direction, pid, devunit)); + PCM_BUSYASSERT(d); + + /* Double check again. */ + if (devunit != -1) { + switch (snd_unit2d(devunit)) { + case SND_DEV_DSPHW_PLAY: + case SND_DEV_DSPHW_VPLAY: + if (direction != PCMDIR_PLAY) + return (EOPNOTSUPP); + break; + case SND_DEV_DSPHW_REC: + case SND_DEV_DSPHW_VREC: + if (direction != PCMDIR_REC) + return (EOPNOTSUPP); + break; + default: + if (!(direction == PCMDIR_PLAY || + direction == PCMDIR_REC)) + return (EOPNOTSUPP); + break; + } + } + + *ch = NULL; + vchan_num = 0; + vchancount = (direction == PCMDIR_PLAY) ? d->pvchancount : + d->rvchancount; retry_chnalloc: - err = ENODEV; + err = EOPNOTSUPP; /* scan for a free channel */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if (c->direction == direction && !(c->flags & CHN_F_BUSY)) { - if (chnum < 0 || sce->chan_num == chnum) { - c->flags |= CHN_F_BUSY; - c->pid = pid; - *ch = c; - return 0; + if (devunit == -1 && c->direction == direction && + (c->flags & CHN_F_VIRTUAL)) { + if (vchancount < snd_maxautovchans && + vchan_num < CHN_CHAN(c)) { + CHN_UNLOCK(c); + goto vchan_alloc; } + vchan_num++; } - if (sce->chan_num == chnum) { + if (c->direction == direction && !(c->flags & CHN_F_BUSY) && + (devunit == -1 || devunit == -2 || c->unit == devunit)) { + c->flags |= CHN_F_BUSY; + c->pid = pid; + strlcpy(c->comm, (comm != NULL) ? comm : + CHN_COMM_UNKNOWN, sizeof(c->comm)); + *ch = c; + return (0); + } else if (c->unit == devunit) { if (c->direction != direction) err = EOPNOTSUPP; else if (c->flags & CHN_F_BUSY) @@ -295,95 +367,112 @@ else err = EINVAL; CHN_UNLOCK(c); - return err; - } else if (c->direction == direction && (c->flags & CHN_F_BUSY)) + return (err); + } else if ((devunit == -1 || devunit == -2) && + c->direction == direction && (c->flags & CHN_F_BUSY)) err = EBUSY; CHN_UNLOCK(c); } + if (devunit == -2) + return (err); + +vchan_alloc: /* no channel available */ - if (chnum == -1 && direction == PCMDIR_PLAY && d->vchancount > 0 && - d->vchancount < snd_maxautovchans && - d->devcount <= PCMMAXCHAN) { - err = pcm_setvchans(d, d->vchancount + 1); + if (devunit == -1 || snd_unit2d(devunit) == SND_DEV_DSPHW_VPLAY || + snd_unit2d(devunit) == SND_DEV_DSPHW_VREC) { + if (!(vchancount > 0 && vchancount < snd_maxautovchans) && + (devunit == -1 || snd_unit2c(devunit) < snd_maxautovchans)) + return (err); + err = pcm_setvchans(d, direction, vchancount + 1, + (devunit == -1) ? -1 : snd_unit2c(devunit)); if (err == 0) { - chnum = -2; + if (devunit == -1) + devunit = -2; goto retry_chnalloc; } } - return err; + return (err); } /* release a locked channel and unlock it */ int pcm_chnrelease(struct pcm_channel *c) { + PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); + c->flags &= ~CHN_F_BUSY; c->pid = -1; + strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); CHN_UNLOCK(c); - return 0; + + return (0); } int pcm_chnref(struct pcm_channel *c, int ref) { - int r; - + PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); + c->refcount += ref; - r = c->refcount; - return r; + + return (c->refcount); } int pcm_inprog(struct snddev_info *d, int delta) { - int r; + snd_mtxassert(d->lock); - if (delta == 0) - return d->inprog; - - /* backtrace(); */ - pcm_lock(d); d->inprog += delta; - r = d->inprog; - pcm_unlock(d); - return r; + + return (d->inprog); } static void pcm_setmaxautovchans(struct snddev_info *d, int num) { - if (num > 0 && d->vchancount == 0) - pcm_setvchans(d, 1); - else if (num == 0 && d->vchancount > 0) - pcm_setvchans(d, 0); + PCM_BUSYASSERT(d); + + if (num < 0) + return; + + if (num >= 0 && d->pvchancount > num) + (void)pcm_setvchans(d, PCMDIR_PLAY, num, -1); + else if (num > 0 && d->pvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_PLAY, 1, -1); + + if (num >= 0 && d->rvchancount > num) + (void)pcm_setvchans(d, PCMDIR_REC, num, -1); + else if (num > 0 && d->rvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_REC, 1, -1); + + pcm_clonereset(d); } #ifdef USING_DEVFS static int -sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int error, unit; unit = snd_unit; - error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); + error = sysctl_handle_int(oidp, &unit, 0, req); if (error == 0 && req->newptr != NULL) { - if (unit < 0 || (pcm_devclass != NULL && - unit >= devclass_get_maxunit(pcm_devclass))) - return EINVAL; d = devclass_get_softc(pcm_devclass, unit); - if (d == NULL || SLIST_EMPTY(&d->channels)) + if (!PCM_REGISTERED(d) || CHN_EMPTY(d, channels.pcm)) return EINVAL; snd_unit = unit; } return (error); } -SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_unit, "I", ""); +/* XXX: do we need a way to let the user change the default unit? */ +SYSCTL_PROC(_hw_snd, OID_AUTO, default_unit, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_default_unit, "I", "default sound device"); #endif static int @@ -393,132 +482,140 @@ int i, v, error; v = snd_maxautovchans; - error = sysctl_handle_int(oidp, &v, sizeof(v), req); + error = sysctl_handle_int(oidp, &v, 0, req); if (error == 0 && req->newptr != NULL) { - if (v < 0 || v > PCMMAXCHAN) - return E2BIG; - if (pcm_devclass != NULL && v != snd_maxautovchans) { - for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { - d = devclass_get_softc(pcm_devclass, i); - if (!d) - continue; - pcm_setmaxautovchans(d, v); - } - } + if (v < 0) + v = 0; + if (v > SND_MAXVCHANS) + v = SND_MAXVCHANS; snd_maxautovchans = v; + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + PCM_ACQUIRE_QUICK(d); + pcm_setmaxautovchans(d, v); + PCM_RELEASE_QUICK(d); + } } return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", ""); + 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); struct pcm_channel * -pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) +pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo) { - struct snddev_channel *sce; - struct pcm_channel *ch, *c; - char *dirs; - uint32_t flsearch = 0; - int direction, err, rpnum, *pnum; + struct pcm_channel *ch; + int direction, err, rpnum, *pnum, max; + int udc, device, chan; + char *dirs, *devname, buf[CHN_NAMELEN]; - switch(dir) { + PCM_BUSYASSERT(d); + snd_mtxassert(d->lock); + KASSERT(num >= -1, ("invalid num=%d", num)); + + + switch (dir) { case PCMDIR_PLAY: dirs = "play"; direction = PCMDIR_PLAY; pnum = &d->playcount; + device = SND_DEV_DSPHW_PLAY; + max = SND_MAXHWCHAN; + break; + case PCMDIR_PLAY_VIRTUAL: + dirs = "virtual"; + direction = PCMDIR_PLAY; + pnum = &d->pvchancount; + device = SND_DEV_DSPHW_VPLAY; + max = SND_MAXVCHANS; break; - case PCMDIR_REC: dirs = "record"; direction = PCMDIR_REC; pnum = &d->reccount; + device = SND_DEV_DSPHW_REC; + max = SND_MAXHWCHAN; break; - - case PCMDIR_VIRTUAL: + case PCMDIR_REC_VIRTUAL: dirs = "virtual"; - direction = PCMDIR_PLAY; - pnum = &d->vchancount; - flsearch = CHN_F_VIRTUAL; + direction = PCMDIR_REC; + pnum = &d->rvchancount; + device = SND_DEV_DSPHW_VREC; + max = SND_MAXVCHANS; break; - default: - return NULL; + return (NULL); } - ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); - if (!ch) - return NULL; - - ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); - if (!ch->methods) { - free(ch, M_DEVBUF); + chan = (num == -1) ? 0 : num; - return NULL; - } + if (*pnum >= max || chan >= max) + return (NULL); - snd_mtxlock(d->lock); - ch->num = 0; rpnum = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (direction != c->direction || - (c->flags & CHN_F_VIRTUAL) != flsearch) - continue; - if (ch->num == c->num) - ch->num++; - else { -#if 0 - device_printf(d->dev, - "%s: %s channel numbering screwed (Expect: %d, Got: %d)\n", - __func__, dirs, ch->num, c->num); -#endif - goto retry_num_search; - } - rpnum++; - } - goto retry_num_search_out; -retry_num_search: - rpnum = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (direction != c->direction || - (c->flags & CHN_F_VIRTUAL) != flsearch) + + CHN_FOREACH(ch, d, channels.pcm) { + if (CHN_DEV(ch) != device) continue; - if (ch->num == c->num) { - ch->num++; - goto retry_num_search; + if (chan == CHN_CHAN(ch)) { + if (num != -1) { + device_printf(d->dev, + "channel num=%d allocated!\n", chan); + return (NULL); + } + chan++; + if (chan >= max) { + device_printf(d->dev, + "chan=%d > %d\n", chan, max); + return (NULL); + } } rpnum++; } -retry_num_search_out: + if (*pnum != rpnum) { device_printf(d->dev, - "%s: WARNING: pnum screwed : dirs=%s, pnum=%d, rpnum=%d\n", - __func__, dirs, *pnum, rpnum); - *pnum = rpnum; + "%s(): WARNING: pnum screwed : dirs=%s pnum=%d rpnum=%d\n", + __func__, dirs, *pnum, rpnum); + return (NULL); + } + + udc = snd_mkunit(device_get_unit(d->dev), device, chan); + devname = dsp_unit2name(buf, sizeof(buf), udc); + + if (devname == NULL) { + device_printf(d->dev, + "Failed to query device name udc=0x%08x\n", udc); + return (NULL); } - (*pnum)++; - snd_mtxunlock(d->lock); + pcm_unlock(d); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); + ch->unit = udc; ch->pid = -1; + strlcpy(ch->comm, CHN_COMM_UNUSED, sizeof(ch->comm)); ch->parentsnddev = d; ch->parentchannel = parent; ch->dev = d->dev; - snprintf(ch->name, CHN_NAMELEN, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num); + ch->trigger = PCMTRIG_STOP; + snprintf(ch->name, sizeof(ch->name), "%s:%s:%s", + device_get_nameunit(ch->dev), dirs, devname); err = chn_init(ch, devinfo, dir, direction); + pcm_lock(d); if (err) { - device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err); + device_printf(d->dev, "chn_init(%s) failed: err = %d\n", + ch->name, err); kobj_delete(ch->methods, M_DEVBUF); free(ch, M_DEVBUF); - snd_mtxlock(d->lock); - (*pnum)--; - snd_mtxunlock(d->lock); - - return NULL; + return (NULL); } - return ch; + return (ch); } int @@ -528,191 +625,93 @@ int err; d = ch->parentsnddev; + PCM_BUSYASSERT(d); + err = chn_kill(ch); if (err) { - device_printf(d->dev, "chn_kill(%s) failed, err = %d\n", ch->name, err); - return err; + device_printf(ch->dev, "chn_kill(%s) failed, err = %d\n", + ch->name, err); + return (err); } kobj_delete(ch->methods, M_DEVBUF); free(ch, M_DEVBUF); - return 0; + return (0); } int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce, *tmp, *after; - unsigned rdevcount; - int device = device_get_unit(d->dev); - size_t namelen; - - /* - * Note it's confusing nomenclature. - * dev_t - * device -> pcm_device - * unit -> pcm_channel - * channel -> snddev_channel - * device_t - * unit -> pcm_device - */ - - sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO); - if (!sce) { - return ENOMEM; + PCM_BUSYASSERT(d); + snd_mtxassert(d->lock); + KASSERT(ch != NULL && (ch->direction == PCMDIR_PLAY || + ch->direction == PCMDIR_REC), ("Invalid pcm channel")); + + CHN_INSERT_SORT_ASCEND(d, ch, channels.pcm); + + switch (CHN_DEV(ch)) { + case SND_DEV_DSPHW_PLAY: + d->playcount++; + break; + case SND_DEV_DSPHW_VPLAY: + d->pvchancount++; + break; + case SND_DEV_DSPHW_REC: + d->reccount++; + break; + case SND_DEV_DSPHW_VREC: + d->rvchancount++; + break; + default: + break; } - snd_mtxlock(d->lock); - sce->channel = ch; - sce->chan_num = 0; - rdevcount = 0; - after = NULL; - SLIST_FOREACH(tmp, &d->channels, link) { - if (sce->chan_num == tmp->chan_num) - sce->chan_num++; - else { -#if 0 - device_printf(d->dev, - "%s: cdev numbering screwed (Expect: %d, Got: %d)\n", - __func__, sce->chan_num, tmp->chan_num); -#endif - goto retry_chan_num_search; - } - after = tmp; - rdevcount++; - } - goto retry_chan_num_search_out; -retry_chan_num_search: - /* - * Look for possible channel numbering collision. This may not - * be optimized, but it will ensure that no collision occured. - * Can be considered cheap since none of the locking/unlocking - * operations involved. - */ - rdevcount = 0; - after = NULL; - SLIST_FOREACH(tmp, &d->channels, link) { - if (sce->chan_num == tmp->chan_num) { - sce->chan_num++; - goto retry_chan_num_search; - } - if (sce->chan_num > tmp->chan_num) - after = tmp; - rdevcount++; - } -retry_chan_num_search_out: - /* - * Don't overflow PCMMKMINOR / PCMMAXCHAN. - */ - if (sce->chan_num > PCMMAXCHAN) { - snd_mtxunlock(d->lock); - device_printf(d->dev, - "%s: WARNING: sce->chan_num overflow! (%d)\n", - __func__, sce->chan_num); - free(sce, M_DEVBUF); - return E2BIG; - } - if (d->devcount != rdevcount) { - device_printf(d->dev, - "%s: WARNING: devcount screwed! d->devcount=%u, rdevcount=%u\n", - __func__, d->devcount, rdevcount); - d->devcount = rdevcount; - } d->devcount++; - if (after == NULL) { - SLIST_INSERT_HEAD(&d->channels, sce, link); - } else { - SLIST_INSERT_AFTER(after, sce, link); - } -#if 0 - if (1) { - int cnum = 0; - SLIST_FOREACH(tmp, &d->channels, link) { - if (cnum != tmp->chan_num) - device_printf(d->dev, - "%s: WARNING: inconsistent cdev numbering! (Expect: %d, Got: %d)\n", - __func__, cnum, tmp->chan_num); - cnum++; - } - } -#endif - - namelen = strlen(ch->name); - if ((CHN_NAMELEN - namelen) > 10) { /* ":dspXX.YYY" */ - snprintf(ch->name + namelen, - CHN_NAMELEN - namelen, ":dsp%d.%d", - device, sce->chan_num); - } - snd_mtxunlock(d->lock); - - /* - * I will revisit these someday, and nuke it mercilessly.. - */ - sce->dsp_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSP, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d", - device, sce->chan_num); - - sce->dspW_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSP16, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d", - device, sce->chan_num); - - sce->audio_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_AUDIO, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", - device, sce->chan_num); - - if (ch->direction == PCMDIR_REC) - sce->dspr_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSPREC, - sce->chan_num), UID_ROOT, GID_WHEEL, - 0666, "dspr%d.%d", device, sce->chan_num); - return 0; + return (0); } int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce; -#if 0 - int ourlock; + struct pcm_channel *tmp; - ourlock = 0; - if (!mtx_owned(d->lock)) { - snd_mtxlock(d->lock); - ourlock = 1; - } -#endif + PCM_BUSYASSERT(d); + snd_mtxassert(d->lock); + + tmp = NULL; - SLIST_FOREACH(sce, &d->channels, link) { - if (sce->channel == ch) - goto gotit; + CHN_FOREACH(tmp, d, channels.pcm) { + if (tmp == ch) + break; } -#if 0 - if (ourlock) - snd_mtxunlock(d->lock); -#endif - return EINVAL; -gotit: - SLIST_REMOVE(&d->channels, sce, snddev_channel, link); - - if (ch->flags & CHN_F_VIRTUAL) - d->vchancount--; - else if (ch->direction == PCMDIR_REC) - d->reccount--; - else + + if (tmp != ch) + return (EINVAL); + + CHN_REMOVE(d, ch, channels.pcm); + + switch (CHN_DEV(ch)) { + case SND_DEV_DSPHW_PLAY: d->playcount--; + break; + case SND_DEV_DSPHW_VPLAY: + d->pvchancount--; + break; + case SND_DEV_DSPHW_REC: + d->reccount--; + break; + case SND_DEV_DSPHW_VREC: + d->rvchancount--; + break; + default: + break; + } -#if 0 - if (ourlock) - snd_mtxunlock(d->lock); -#endif - free(sce, M_DEVBUF); + d->devcount--; - return 0; + return (0); } int @@ -722,34 +721,42 @@ struct pcm_channel *ch; int err; - ch = pcm_chn_create(d, NULL, cls, dir, devinfo); + PCM_BUSYASSERT(d); + + pcm_lock(d); + ch = pcm_chn_create(d, NULL, cls, dir, -1, devinfo); if (!ch) { - device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo); - return ENODEV; + device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", + cls->name, dir, devinfo); + pcm_unlock(d); + return (ENODEV); } err = pcm_chn_add(d, ch); + pcm_unlock(d); if (err) { - device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err); + device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", + ch->name, err); pcm_chn_destroy(ch); - return err; } - return err; + return (err); } static int pcm_killchan(device_t dev) { struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; struct pcm_channel *ch; - int error = 0; + int error; - sce = SLIST_FIRST(&d->channels); - ch = sce->channel; + PCM_BUSYASSERT(d); - error = pcm_chn_remove(d, sce->channel); + ch = CHN_FIRST(d, channels.pcm); + + pcm_lock(d); + error = pcm_chn_remove(d, ch); + pcm_unlock(d); if (error) return (error); return (pcm_chn_destroy(ch)); @@ -760,12 +767,38 @@ { struct snddev_info *d = device_get_softc(dev); - snd_mtxlock(d->lock); - strncpy(d->status, str, SND_STATUSLEN); - snd_mtxunlock(d->lock); - if (snd_maxautovchans > 0) - pcm_setvchans(d, 1); - return 0; + PCM_BUSYASSERT(d); + + if (d->playcount == 0 || d->reccount == 0) + d->flags |= SD_F_SIMPLEX; + + if ((d->playcount > 0 || d->reccount > 0) && + !(d->flags & SD_F_AUTOVCHAN)) { + d->flags |= SD_F_AUTOVCHAN; + vchan_initsys(dev); + } + + pcm_setmaxautovchans(d, snd_maxautovchans); + + strlcpy(d->status, str, SND_STATUSLEN); + + pcm_lock(d); + + /* Last stage, enable cloning. */ + if (d->clones != NULL) + (void)snd_clone_enable(d->clones); + + /* Done, we're ready.. */ + d->flags |= SD_F_REGISTERED; + + PCM_RELEASE(d); + + pcm_unlock(d); + + if (snd_unit < 0 || snd_unit_auto != 0) + snd_unit = device_get_unit(dev); + + return (0); } uint32_t @@ -793,7 +826,7 @@ } unsigned int -pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max) +pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz) { struct snddev_info *d = device_get_softc(dev); int sz, x; @@ -801,10 +834,10 @@ sz = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) { x = sz; - RANGE(sz, min, max); + RANGE(sz, minbufsz, maxbufsz); if (x != sz) - device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, min, max, sz); - x = min; + device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, minbufsz, maxbufsz, sz); + x = minbufsz; while (x < sz) x <<= 1; if (x > sz) @@ -822,10 +855,122 @@ return sz; } +#if defined(SND_DYNSYSCTL) && defined(SND_DEBUG) +static int +sysctl_dev_pcm_clone_flags(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + uint32_t flags; + int err; + + d = oidp->oid_arg1; + if (!PCM_REGISTERED(d) || d->clones == NULL) + return (ENODEV); + + PCM_ACQUIRE_QUICK(d); + + flags = snd_clone_getflags(d->clones); + err = sysctl_handle_int(oidp, &flags, 0, req); + + if (err == 0 && req->newptr != NULL) { + if (flags & ~SND_CLONE_MASK) + err = EINVAL; + else + (void)snd_clone_setflags(d->clones, flags); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_dev_pcm_clone_deadline(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int err, deadline; + + d = oidp->oid_arg1; + if (!PCM_REGISTERED(d) || d->clones == NULL) + return (ENODEV); + + PCM_ACQUIRE_QUICK(d); + + deadline = snd_clone_getdeadline(d->clones); + err = sysctl_handle_int(oidp, &deadline, 0, req); + + if (err == 0 && req->newptr != NULL) { + if (deadline < 0) + err = EINVAL; + else + (void)snd_clone_setdeadline(d->clones, deadline); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_dev_pcm_clone_gc(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int err, val; + + d = oidp->oid_arg1; + if (!PCM_REGISTERED(d) || d->clones == NULL) + return (ENODEV); + + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err == 0 && req->newptr != NULL && val != 0) { + PCM_ACQUIRE_QUICK(d); + val = snd_clone_gc(d->clones); + PCM_RELEASE_QUICK(d); + if (bootverbose != 0 || snd_verbose > 3) + device_printf(d->dev, "clone gc: pruned=%d\n", val); + } + + return (err); +} + +static int +sysctl_hw_snd_clone_gc(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int i, err, val; + + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err == 0 && req->newptr != NULL && val != 0) { + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d) || d->clones == NULL) + continue; + PCM_ACQUIRE_QUICK(d); + val = snd_clone_gc(d->clones); + PCM_RELEASE_QUICK(d); + if (bootverbose != 0 || snd_verbose > 3) + device_printf(d->dev, "clone gc: pruned=%d\n", + val); + } + } + + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, clone_gc, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_clone_gc, "I", + "global clone garbage collector"); +#endif + int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d; + int i; if (pcm_veto_load) { device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); @@ -833,8 +978,20 @@ return EINVAL; } - d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); + if (device_get_unit(dev) > PCMMAXUNIT) { + device_printf(dev, "PCMMAXUNIT reached : unit=%d > %d\n", + device_get_unit(dev), PCMMAXUNIT); + device_printf(dev, + "Use 'hw.snd.maxunit' tunable to raise the limit.\n"); + return ENODEV; + } + d = device_get_softc(dev); + d->dev = dev; + d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); + cv_init(&d->cv, device_get_nameunit(dev)); + PCM_ACQUIRE_QUICK(d); + dsp_cdevinfo_init(d); #if 0 /* * d->flags should be cleared by the allocator of the softc. @@ -843,16 +1000,44 @@ */ d->flags = 0; #endif - d->dev = dev; + i = 0; + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "vpc_disabled", &i) != 0 || i == 0) + d->flags |= SD_F_VPC; d->devinfo = devinfo; d->devcount = 0; d->reccount = 0; d->playcount = 0; - d->vchancount = 0; + d->pvchancount = 0; + d->rvchancount = 0; + d->pvchanrate = 0; + d->pvchanformat = 0; + d->rvchanrate = 0; + d->rvchanformat = 0; d->inprog = 0; - SLIST_INIT(&d->channels); + /* + * Create clone manager, disabled by default. Cloning will be + * enabled during final stage of driver iniialization through + * pcm_setstatus(). + */ + d->clones = snd_clone_create(SND_U_MASK | SND_D_MASK, PCMMAXCLONE, + SND_CLONE_DEADLINE_DEFAULT, SND_CLONE_WAITOK | + SND_CLONE_GC_ENABLE | SND_CLONE_GC_UNREF | + SND_CLONE_GC_LASTREF | SND_CLONE_GC_EXPIRED); + + if (bootverbose != 0 || snd_verbose > 3) { + device_printf(dev, + "clone manager: deadline=%dms flags=0x%08x\n", + snd_clone_getdeadline(d->clones), + snd_clone_getflags(d->clones)); + } + + CHN_INIT(d, channels.pcm); + CHN_INIT(d, channels.pcm.busy); + CHN_INIT(d, channels.pcm.opened); + /* XXX This is incorrect, but lets play along for now. */ if ((numplay == 0 || numrec == 0) && numplay != numrec) d->flags |= SD_F_SIMPLEX; @@ -860,274 +1045,379 @@ chn_init(d->fakechan, NULL, 0, 0); #ifdef SND_DYNSYSCTL - sysctl_ctx_init(&d->sysctl_tree); - d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree, - SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO, - device_get_nameunit(dev), CTLFLAG_RD, 0, ""); - if (d->sysctl_tree_top == NULL) { - sysctl_ctx_free(&d->sysctl_tree); - goto no; - } - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, ""); + sysctl_ctx_init(&d->play_sysctl_ctx); + d->play_sysctl_tree = SYSCTL_ADD_NODE(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "play", + CTLFLAG_RD, 0, "playback channels node"); + sysctl_ctx_init(&d->rec_sysctl_ctx); + d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", + CTLFLAG_RD, 0, "record channels node"); + /* XXX: an user should be able to set this with a control tool, the + sysadmin then needs min+max sysctls for this */ + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); +#ifdef SND_DEBUG + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_flags", CTLTYPE_UINT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_flags, "IU", + "clone flags"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_deadline", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_deadline, "I", + "clone expiration deadline (ms)"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_gc", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_gc, "I", + "clone garbage collector"); +#endif #endif - if (numplay > 0) { + + if (numplay > 0 || numrec > 0) { d->flags |= SD_F_AUTOVCHAN; vchan_initsys(dev); } sndstat_register(dev, d->status, sndstat_prepare_pcm); + return 0; -no: - snd_mtxfree(d->lock); - return ENXIO; } int pcm_unregister(device_t dev) { - struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; - struct pcmchan_children *pce; + struct snddev_info *d; struct pcm_channel *ch; + struct thread *td; + int i; + + td = curthread; + d = device_get_softc(dev); - if (sndstat_acquire() != 0) { + if (!PCM_ALIVE(d)) { + device_printf(dev, "unregister: device not configured\n"); + return (0); + } + + if (sndstat_acquire(td) != 0) { device_printf(dev, "unregister: sndstat busy\n"); - return EBUSY; + return (EBUSY); } - snd_mtxlock(d->lock); - if (d->inprog) { + pcm_lock(d); + PCM_WAIT(d); + + if (d->inprog != 0) { device_printf(dev, "unregister: operation in progress\n"); - snd_mtxunlock(d->lock); - sndstat_release(); - return EBUSY; + pcm_unlock(d); + sndstat_release(td); + return (EBUSY); } - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; + PCM_ACQUIRE(d); + pcm_unlock(d); + + CHN_FOREACH(ch, d, channels.pcm) { + CHN_LOCK(ch); if (ch->refcount > 0) { - device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid); - snd_mtxunlock(d->lock); - sndstat_release(); - return EBUSY; + device_printf(dev, + "unregister: channel %s busy (pid %d)\n", + ch->name, ch->pid); + CHN_UNLOCK(ch); + PCM_RELEASE_QUICK(d); + sndstat_release(td); + return (EBUSY); + } + CHN_UNLOCK(ch); + } + + if (d->clones != NULL) { + if (snd_clone_busy(d->clones) != 0) { + device_printf(dev, "unregister: clone busy\n"); + PCM_RELEASE_QUICK(d); + sndstat_release(td); + return (EBUSY); + } else { + pcm_lock(d); + (void)snd_clone_disable(d->clones); + pcm_unlock(d); } } if (mixer_uninit(dev) == EBUSY) { device_printf(dev, "unregister: mixer busy\n"); - snd_mtxunlock(d->lock); - sndstat_release(); - return EBUSY; + pcm_lock(d); + if (d->clones != NULL) + (void)snd_clone_enable(d->clones); + PCM_RELEASE(d); + pcm_unlock(d); + sndstat_release(td); + return (EBUSY); } - SLIST_FOREACH(sce, &d->channels, link) { - if (sce->dsp_devt) { - destroy_dev(sce->dsp_devt); - sce->dsp_devt = NULL; - } - if (sce->dspW_devt) { - destroy_dev(sce->dspW_devt); - sce->dspW_devt = NULL; - } - if (sce->audio_devt) { - destroy_dev(sce->audio_devt); - sce->audio_devt = NULL; - } - if (sce->dspr_devt) { - destroy_dev(sce->dspr_devt); - sce->dspr_devt = NULL; - } - d->devcount--; - ch = sce->channel; - if (ch == NULL) - continue; - pce = SLIST_FIRST(&ch->children); - while (pce != NULL) { -#if 0 - device_printf(d->dev, "<%s> removing <%s>\n", - ch->name, (pce->channel != NULL) ? - pce->channel->name : "unknown"); -#endif - SLIST_REMOVE(&ch->children, pce, pcmchan_children, link); - free(pce, M_DEVBUF); - pce = SLIST_FIRST(&ch->children); - } + pcm_lock(d); + d->flags |= SD_F_DYING; + d->flags &= ~SD_F_REGISTERED; + pcm_unlock(d); + + /* + * No lock being held, so this thing can be flushed without + * stucking into devdrn oblivion. + */ + if (d->clones != NULL) { + snd_clone_destroy(d->clones); + d->clones = NULL; } #ifdef SND_DYNSYSCTL - d->sysctl_tree_top = NULL; - sysctl_ctx_free(&d->sysctl_tree); -#endif - -#if 0 - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; - if (ch == NULL) - continue; - if (!SLIST_EMPTY(&ch->children)) - device_printf(d->dev, "%s: WARNING: <%s> dangling child!\n", - __func__, ch->name); + if (d->play_sysctl_tree != NULL) { + sysctl_ctx_free(&d->play_sysctl_ctx); + d->play_sysctl_tree = NULL; + } + if (d->rec_sysctl_tree != NULL) { + sysctl_ctx_free(&d->rec_sysctl_ctx); + d->rec_sysctl_tree = NULL; } #endif - while (!SLIST_EMPTY(&d->channels)) + + while (!CHN_EMPTY(d, channels.pcm)) pcm_killchan(dev); chn_kill(d->fakechan); fkchan_kill(d->fakechan); -#if 0 - device_printf(d->dev, "%s: devcount=%u, playcount=%u, " - "reccount=%u, vchancount=%u\n", - __func__, d->devcount, d->playcount, d->reccount, - d->vchancount); -#endif - snd_mtxunlock(d->lock); + dsp_cdevinfo_flush(d); + + pcm_lock(d); + PCM_RELEASE(d); + cv_destroy(&d->cv); + pcm_unlock(d); snd_mtxfree(d->lock); sndstat_unregister(dev); - sndstat_release(); - return 0; + sndstat_release(td); + + if (snd_unit == device_get_unit(dev)) { + /* + * Reassign default unit to the next available dev, but + * first, reset snd_unit to something ridiculous. + */ + snd_unit = -1; + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + if (device_get_unit(dev) == i) + continue; + d = devclass_get_softc(pcm_devclass, i); + if (PCM_REGISTERED(d)) { + snd_unit = i; + break; + } + } + } + + return (0); } /************************************************************************/ -static int -sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) +#ifdef SND_DYNSYSCTL +int +sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - struct snddev_channel *sce; - struct pcm_channel *c; - struct pcm_feeder *f; - int pc, rc, vc; + int direction, vchancount; + int err, cnt; - if (verbose < 1) - return 0; + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); - d = device_get_softc(dev); - if (!d) - return ENXIO; + pcm_lock(d); + PCM_WAIT(d); - snd_mtxlock(d->lock); - if (!SLIST_EMPTY(&d->channels)) { - pc = rc = vc = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (c->direction == PCMDIR_PLAY) { - if (c->flags & CHN_F_VIRTUAL) - vc++; - else - pc++; - } else - rc++; - } - sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", d->playcount, d->reccount, d->vchancount, - (d->flags & SD_F_SIMPLEX)? "" : " duplex", -#ifdef USING_DEVFS - (device_get_unit(dev) == snd_unit)? " default" : "" -#else - "" -#endif - ); + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + cnt = d->playcount; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + cnt = d->reccount; + break; + default: + pcm_unlock(d); + return (EINVAL); + break; + } - if (verbose <= 1) { - snd_mtxunlock(d->lock); - return 0; - } + if (cnt < 1) { + pcm_unlock(d); + return (ENODEV); + } - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + PCM_ACQUIRE(d); + pcm_unlock(d); - KASSERT(c->bufhard != NULL && c->bufsoft != NULL, - ("hosed pcm channel setup")); + cnt = vchancount; + err = sysctl_handle_int(oidp, &cnt, 0, req); - sbuf_printf(s, "\n\t"); + if (err == 0 && req->newptr != NULL && vchancount != cnt) { + if (cnt < 0) + cnt = 0; + if (cnt > SND_MAXVCHANS) + cnt = SND_MAXVCHANS; + err = pcm_setvchans(d, direction, cnt, -1); + } - /* it would be better to indent child channels */ - sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name); - sbuf_printf(s, "spd %d", c->speed); - if (c->speed != sndbuf_getspd(c->bufhard)) - sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard)); - sbuf_printf(s, ", fmt 0x%08x", c->format); - if (c->format != sndbuf_getfmt(c->bufhard)) - sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard)); - sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags); - if (c->pid != -1) - sbuf_printf(s, ", pid %d", c->pid); - sbuf_printf(s, "\n\t"); - - sbuf_printf(s, "interrupts %d, ", c->interrupts); - if (c->direction == PCMDIR_REC) - sbuf_printf(s, "overruns %d, hfree %d, sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", - c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft), - sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), - sndbuf_getblkcnt(c->bufhard), - sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), - sndbuf_getblkcnt(c->bufsoft)); - else - sbuf_printf(s, "underruns %d, ready %d [b:%d/%d/%d|bs:%d/%d/%d]", - c->xruns, sndbuf_getready(c->bufsoft), - sndbuf_getsize(c->bufhard), sndbuf_getblksz(c->bufhard), - sndbuf_getblkcnt(c->bufhard), - sndbuf_getsize(c->bufsoft), sndbuf_getblksz(c->bufsoft), - sndbuf_getblkcnt(c->bufsoft)); - sbuf_printf(s, "\n\t"); - - sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland"); - sbuf_printf(s, " -> "); - f = c->feeder; - while (f->source != NULL) - f = f->source; - while (f != NULL) { - sbuf_printf(s, "%s", f->class->name); - if (f->desc->type == FEEDER_FMT) - sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); - if (f->desc->type == FEEDER_RATE) - sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); - if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER || - f->desc->type == FEEDER_VOLUME) - sbuf_printf(s, "(0x%08x)", f->desc->out); - sbuf_printf(s, " -> "); - f = f->parent; - } - sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware"); - } - } else - sbuf_printf(s, " (mixer only)"); - snd_mtxunlock(d->lock); + PCM_RELEASE_QUICK(d); - return 0; + return err; } +#endif /************************************************************************/ -#ifdef SND_DYNSYSCTL -int -sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) +/** + * @brief Handle OSSv4 SNDCTL_SYSINFO ioctl. + * + * @param si Pointer to oss_sysinfo struct where information about the + * sound subsystem will be written/copied. + * + * This routine returns information about the sound system, such as the + * current OSS version, number of audio, MIDI, and mixer drivers, etc. + * Also includes a bitmask showing which of the above types of devices + * are open (busy). + * + * @note + * Calling threads must not hold any snddev_info or pcm_channel locks. + * + * @author Ryan Beasley + */ +void +sound_oss_sysinfo(oss_sysinfo *si) { + static char si_product[] = "FreeBSD native OSS ABI"; + static char si_version[] = __XSTRING(__FreeBSD_version); + static int intnbits = sizeof(int) * 8; /* Better suited as macro? + Must pester a C guru. */ + struct snddev_info *d; - int err, newcnt; + struct pcm_channel *c; + int i, j, ncards; + + ncards = 0; + + strlcpy(si->product, si_product, sizeof(si->product)); + strlcpy(si->version, si_version, sizeof(si->version)); + si->versionnum = SOUND_VERSION; - d = oidp->oid_arg1; + /* + * Iterate over PCM devices and their channels, gathering up data + * for the numaudios, ncards, and openedaudio fields. + */ + si->numaudios = 0; + bzero((void *)&si->openedaudio, sizeof(si->openedaudio)); - newcnt = d->vchancount; - err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); + j = 0; - if (err == 0 && req->newptr != NULL && d->vchancount != newcnt) - err = pcm_setvchans(d, newcnt); + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; - return err; + /* XXX Need Giant magic entry ??? */ + + /* See note in function's docblock */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_lock(d); + + si->numaudios += d->devcount; + ++ncards; + + CHN_FOREACH(c, d, channels.pcm) { + mtx_assert(c->lock, MA_NOTOWNED); + CHN_LOCK(c); + if (c->flags & CHN_F_BUSY) + si->openedaudio[j / intnbits] |= + (1 << (j % intnbits)); + CHN_UNLOCK(c); + j++; + } + + pcm_unlock(d); + } + + si->numsynths = 0; /* OSSv4 docs: this field is obsolete */ + /** + * @todo Collect num{midis,timers}. + * + * Need access to sound/midi/midi.c::midistat_lock in order + * to safely touch midi_devices and get a head count of, well, + * MIDI devices. midistat_lock is a global static (i.e., local to + * midi.c), but midi_devices is a regular global; should the mutex + * be publicized, or is there another way to get this information? + * + * NB: MIDI/sequencer stuff is currently on hold. + */ + si->nummidis = 0; + si->numtimers = 0; + si->nummixers = mixer_count; + si->numcards = ncards; + /* OSSv4 docs: Intended only for test apps; API doesn't + really have much of a concept of cards. Shouldn't be + used by applications. */ + + /** + * @todo Fill in "busy devices" fields. + * + * si->openedmidi = " MIDI devices + */ + bzero((void *)&si->openedmidi, sizeof(si->openedmidi)); + + /* + * Si->filler is a reserved array, but according to docs each + * element should be set to -1. + */ + for (i = 0; i < sizeof(si->filler)/sizeof(si->filler[0]); i++) + si->filler[i] = -1; } -#endif /************************************************************************/ static int sound_modevent(module_t mod, int type, void *data) { + int ret; #if 0 return (midi_modevent(mod, type, data)); #else - return 0; + ret = 0; + + switch(type) { + case MOD_LOAD: + pcmsg_unrhdr = new_unrhdr(1, INT_MAX, NULL); + break; + case MOD_UNLOAD: + case MOD_SHUTDOWN: + ret = sndstat_acquire(curthread); + if (ret != 0) + break; + if (pcmsg_unrhdr != NULL) { + delete_unrhdr(pcmsg_unrhdr); + pcmsg_unrhdr = NULL; + } + break; + default: + ret = EOPNOTSUPP; + } + + return ret; #endif } --- sys/dev/sound/pcm/sound.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/sound.h Thu Jul 12 12:04:19 2007 @@ -24,7 +24,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/sound.h,v 1.63.2.3 2007/05/13 20:53:39 ariff Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/sound.h,v 1.78 2007/06/16 03:37:28 ariff Exp $ */ /* @@ -55,10 +55,10 @@ #if __FreeBSD_version < 500000 #include #endif -#include /* for DELAY */ #include #include #include +#include #include #include #include @@ -74,6 +74,7 @@ #if __FreeBSD_version > 500000 #include #include +#include #define USING_MUTEX #define USING_DEVFS @@ -93,55 +94,66 @@ #include #include #include +#include +#include +#include #define PCM_SOFTC_SIZE 512 #define SND_STATUSLEN 64 -#define SOUND_MODVER 1 +#define SOUND_MODVER 2 -#define SOUND_MINVER 1 +#define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER -#define SOUND_MAXVER 1 +#define SOUND_MAXVER SOUND_MODVER /* -PROPOSAL: -each unit needs: -status, mixer, dsp, dspW, audio, sequencer, midi-in, seq2, sndproc = 9 devices -dspW and audio are deprecated. -dsp needs min 64 channels, will give it 256 - -minor = (unit << 20) + (dev << 16) + channel -currently minor = (channel << 16) + (unit << 4) + dev - -nomenclature: - /dev/pcmX/dsp.(0..255) - /dev/pcmX/dspW - /dev/pcmX/audio - /dev/pcmX/status - /dev/pcmX/mixer - [etc.] -*/ - -#define PCMMAXCHAN 0xff -#define PCMMAXDEV 0x0f -#define PCMMAXUNIT 0x0f -#define PCMMINOR(x) (minor(x)) -#define PCMCHAN(x) ((PCMMINOR(x) >> 16) & PCMMAXCHAN) -#define PCMUNIT(x) ((PCMMINOR(x) >> 4) & PCMMAXUNIT) -#define PCMDEV(x) (PCMMINOR(x) & PCMMAXDEV) -#define PCMMKMINOR(u, d, c) ((((c) & PCMMAXCHAN) << 16) | \ - (((u) & PCMMAXUNIT) << 4) | ((d) & PCMMAXDEV)) + * We're abusing the fact that MAXMINOR still have enough room + * for our bit twiddling and nobody ever need 512 unique soundcards, + * 32 unique device types and 1024 unique cloneable devices for the + * next 100 years... + */ + +#define PCMMAXUNIT (snd_max_u()) +#define PCMMAXDEV (snd_max_d()) +#define PCMMAXCHAN (snd_max_c()) + +#define PCMMAXCLONE PCMMAXCHAN + +#define PCMUNIT(x) (snd_unit2u(dev2unit(x))) +#define PCMDEV(x) (snd_unit2d(dev2unit(x))) +#define PCMCHAN(x) (snd_unit2c(dev2unit(x))) + +/* + * By design, limit possible channels for each direction. + */ +#define SND_MAXHWCHAN 256 +#define SND_MAXVCHANS SND_MAXHWCHAN #define SD_F_SIMPLEX 0x00000001 #define SD_F_AUTOVCHAN 0x00000002 #define SD_F_SOFTPCMVOL 0x00000004 +#define SD_F_PSWAPLR 0x00000008 +#define SD_F_RSWAPLR 0x00000010 +#define SD_F_DYING 0x00000020 +#define SD_F_SUICIDE 0x00000040 +#define SD_F_BUSY 0x00000080 +#define SD_F_MPSAFE 0x00000100 +#define SD_F_REGISTERED 0x00000200 +#define SD_F_VPC 0x00000400 /* volume-per-channel */ + #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 +#define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL && \ + !((x)->flags & SD_F_DYING)) +#define PCM_REGISTERED(x) (PCM_ALIVE(x) && \ + ((x)->flags & SD_F_REGISTERED)) + /* many variables should be reduced to a range. Here define a macro */ #define RANGE(var, low, high) (var) = \ (((var)<(low))? (low) : ((var)>(high))? (high) : (var)) @@ -160,9 +172,6 @@ struct pcm_channel *fkchan_setup(device_t dev); int fkchan_kill(struct pcm_channel *c); -/* XXX Flawed definition. I'll fix it someday. */ -#define SND_MAXVCHANS PCMMAXCHAN - /* * Minor numbers for the sound driver. * @@ -184,7 +193,27 @@ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 -#define SND_DEV_DSPREC 11 /* recording channels */ + +#define SND_DEV_DSPHW_PLAY 11 /* specific playback channel */ +#define SND_DEV_DSPHW_VPLAY 12 /* specific virtual playback channel */ +#define SND_DEV_DSPHW_REC 13 /* specific record channel */ +#define SND_DEV_DSPHW_VREC 14 /* specific virtual record channel */ + +#define SND_DEV_DSPHW_CD 15 /* s16le/stereo 44100Hz CD */ + +/* + * OSSv4 compatible device. For now, it serve no purpose and + * the cloning itself will forward the request to ordinary /dev/dsp + * instead. + */ +#define SND_DEV_DSP_MMAP 16 /* /dev/dsp_mmap */ +#define SND_DEV_DSP_AC3 17 /* /dev/dsp_ac3 */ +#define SND_DEV_DSP_MULTICH 18 /* /dev/dsp_multich */ +#define SND_DEV_DSP_SPDIFOUT 19 /* /dev/dsp_spdifout */ +#define SND_DEV_DSP_SPDIFIN 20 /* /dev/dsp_spdifin */ + +#define SND_DEV_LAST SND_DEV_DSP_SPDIFIN +#define SND_DEV_MAX PCMMAXDEV #define DSP_DEFAULT_SPEED 8000 @@ -193,7 +222,10 @@ extern int pcm_veto_load; extern int snd_unit; +extern int snd_maxautovchans; +extern int snd_verbose; extern devclass_t pcm_devclass; +extern struct unrhdr *pcmsg_unrhdr; /* * some macros for debugging purposes @@ -208,22 +240,20 @@ SYSCTL_DECL(_hw_snd); -struct sysctl_ctx_list *snd_sysctl_tree(device_t dev); -struct sysctl_oid *snd_sysctl_tree_top(device_t dev); - struct pcm_channel *pcm_getfakechan(struct snddev_info *d); -int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, pid_t pid, int chnum); +int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, + pid_t pid, char *comm, int devunit); int pcm_chnrelease(struct pcm_channel *c); int pcm_chnref(struct pcm_channel *c, int ref); int pcm_inprog(struct snddev_info *d, int delta); -struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo); +struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo); int pcm_chn_destroy(struct pcm_channel *ch); int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch); int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch); int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); -unsigned int pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max); +unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); int pcm_unregister(device_t dev); int pcm_setstatus(device_t dev, char *str); @@ -244,8 +274,8 @@ int sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS); typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); -int sndstat_acquire(void); -int sndstat_release(void); +int sndstat_acquire(struct thread *td); +int sndstat_release(struct thread *td); int sndstat_register(device_t dev, char *str, sndstat_handler handler); int sndstat_registerfile(char *str); int sndstat_unregister(device_t dev); @@ -278,33 +308,39 @@ * we also have to do this now makedev() has gone away. */ -struct snddev_channel { - SLIST_ENTRY(snddev_channel) link; - struct pcm_channel *channel; - int chan_num; - struct cdev *dsp_devt; - struct cdev *dspW_devt; - struct cdev *audio_devt; - struct cdev *dspr_devt; -}; - struct snddev_info { - SLIST_HEAD(, snddev_channel) channels; + struct { + struct { + SLIST_HEAD(, pcm_channel) head; + struct { + SLIST_HEAD(, pcm_channel) head; + } busy; + struct { + SLIST_HEAD(, pcm_channel) head; + } opened; + } pcm; + } channels; + TAILQ_HEAD(dsp_cdevinfo_linkhead, dsp_cdevinfo) dsp_cdevinfo_pool; + struct snd_clone *clones; struct pcm_channel *fakechan; - unsigned devcount, playcount, reccount, vchancount; + unsigned devcount, playcount, reccount, pvchancount, rvchancount ; unsigned flags; int inprog; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; - struct sysctl_ctx_list sysctl_tree; - struct sysctl_oid *sysctl_tree_top; struct mtx *lock; struct cdev *mixer_dev; - + uint32_t pvchanrate, pvchanformat; + uint32_t rvchanrate, rvchanformat; + struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; + struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; + struct cv cv; }; +void sound_oss_sysinfo(oss_sysinfo *); + #ifdef PCM_DEBUG_MTX #define pcm_lock(d) mtx_lock(((struct snddev_info *)(d))->lock) #define pcm_unlock(d) mtx_unlock(((struct snddev_info *)(d))->lock) @@ -312,6 +348,196 @@ void pcm_lock(struct snddev_info *d); void pcm_unlock(struct snddev_info *d); #endif + +/* + * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these + * with pcm_lock/unlock() sequence, or I'll come to gnaw upon you! + */ +#ifdef SND_DIAGNOSTIC +#define PCM_WAIT(x) do { \ + if (mtx_owned((x)->lock) == 0) \ + panic("%s(%d): [PCM WAIT] Mutex not owned!", \ + __func__, __LINE__); \ + while ((x)->flags & SD_F_BUSY) { \ + if (snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [PCM WAIT] calling cv_wait().\n", \ + __func__, __LINE__); \ + cv_wait(&(x)->cv, (x)->lock); \ + } \ +} while(0) + +#define PCM_ACQUIRE(x) do { \ + if (mtx_owned((x)->lock) == 0) \ + panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ + __func__, __LINE__); \ + if ((x)->flags & SD_F_BUSY) \ + panic("%s(%d): [PCM ACQUIRE] " \ + "Trying to acquire BUSY cv!", __func__, __LINE__); \ + (x)->flags |= SD_F_BUSY; \ +} while(0) + +#define PCM_RELEASE(x) do { \ + if (mtx_owned((x)->lock) == 0) \ + panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ + __func__, __LINE__); \ + if ((x)->flags & SD_F_BUSY) { \ + (x)->flags &= ~SD_F_BUSY; \ + if ((x)->cv.cv_waiters != 0) { \ + if ((x)->cv.cv_waiters > 1 && snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [PCM RELEASE] " \ + "cv_waiters=%d > 1!\n", \ + __func__, __LINE__, \ + (x)->cv.cv_waiters); \ + cv_broadcast(&(x)->cv); \ + } \ + } else \ + panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ + __func__, __LINE__); \ +} while(0) + +/* Quick version, for shorter path. */ +#define PCM_ACQUIRE_QUICK(x) do { \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ + __func__, __LINE__); \ + pcm_lock(x); \ + PCM_WAIT(x); \ + PCM_ACQUIRE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_RELEASE_QUICK(x) do { \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ + __func__, __LINE__); \ + pcm_lock(x); \ + PCM_RELEASE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_BUSYASSERT(x) do { \ + if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ + panic("%s(%d): [PCM BUSYASSERT] " \ + "Failed, snddev_info=%p", __func__, __LINE__, x); \ +} while(0) + +#define PCM_GIANT_ENTER(x) do { \ + int _pcm_giant = 0; \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ + __func__, __LINE__); \ + if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [GIANT ENTER] Giant owned!\n", \ + __func__, __LINE__); \ + if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ + do { \ + mtx_lock(&Giant); \ + _pcm_giant = 1; \ + } while(0) + +#define PCM_GIANT_EXIT(x) do { \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ + __func__, __LINE__); \ + if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ + panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ + __func__, __LINE__); \ + if ((x)->flags & SD_F_MPSAFE) { \ + if (_pcm_giant == 1) \ + panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ + __func__, __LINE__); \ + if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [GIANT EXIT] Giant owned!\n", \ + __func__, __LINE__); \ + } \ + if (_pcm_giant != 0) { \ + if (mtx_owned(&Giant) == 0) \ + panic("%s(%d): [GIANT EXIT] Giant not owned!", \ + __func__, __LINE__); \ + _pcm_giant = 0; \ + mtx_unlock(&Giant); \ + } \ +} while(0) +#else /* SND_DIAGNOSTIC */ +#define PCM_WAIT(x) do { \ + mtx_assert((x)->lock, MA_OWNED); \ + while ((x)->flags & SD_F_BUSY) \ + cv_wait(&(x)->cv, (x)->lock); \ +} while(0) + +#define PCM_ACQUIRE(x) do { \ + mtx_assert((x)->lock, MA_OWNED); \ + KASSERT(!((x)->flags & SD_F_BUSY), \ + ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ + __func__, __LINE__)); \ + (x)->flags |= SD_F_BUSY; \ +} while(0) + +#define PCM_RELEASE(x) do { \ + mtx_assert((x)->lock, MA_OWNED); \ + KASSERT((x)->flags & SD_F_BUSY, \ + ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ + __func__, __LINE__)); \ + (x)->flags &= ~SD_F_BUSY; \ + if ((x)->cv.cv_waiters != 0) \ + cv_broadcast(&(x)->cv); \ +} while(0) + +/* Quick version, for shorter path. */ +#define PCM_ACQUIRE_QUICK(x) do { \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + pcm_lock(x); \ + PCM_WAIT(x); \ + PCM_ACQUIRE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_RELEASE_QUICK(x) do { \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + pcm_lock(x); \ + PCM_RELEASE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ + ((x)->flags & SD_F_BUSY), \ + ("%s(%d): [PCM BUSYASSERT] " \ + "Failed, snddev_info=%p", \ + __func__, __LINE__, x)) + +#define PCM_GIANT_ENTER(x) do { \ + int _pcm_giant = 0; \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ + do { \ + mtx_lock(&Giant); \ + _pcm_giant = 1; \ + } while(0) + +#define PCM_GIANT_EXIT(x) do { \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ + ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ + __func__, __LINE__)); \ + KASSERT(!((x)->flags & SD_F_MPSAFE) || \ + (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ + ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ + __func__, __LINE__)); \ + if (_pcm_giant != 0) { \ + mtx_assert(&Giant, MA_OWNED); \ + _pcm_giant = 0; \ + mtx_unlock(&Giant); \ + } \ +} while(0) +#endif /* !SND_DIAGNOSTIC */ + +#define PCM_GIANT_LEAVE(x) \ + PCM_GIANT_EXIT(x); \ +} while(0) #ifdef KLD_MODULE #define PCM_KLDSTRING(a) ("kld " # a) --- sys/dev/sound/pcm/vchan.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/vchan.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,6 @@ /*- - * Copyright (c) 2001 Cameron Grant + * Copyright (c) 2001 Cameron Grant + * Copyright (c) 2006 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,300 +25,530 @@ * SUCH DAMAGE. */ +/* Almost entirely rewritten to add multi-format/channels mixing support. */ + #include +#include #include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/vchan.c,v 1.17.2.5 2007/02/04 06:17:14 ariff Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/vchan.c,v 1.36 2007/06/16 03:37:28 ariff Exp $"); -/* - * Default speed - */ -#define VCHAN_DEFAULT_SPEED 48000 +MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); -extern int feeder_rate_ratemin; -extern int feeder_rate_ratemax; +typedef uint32_t (*feed_vchan_mixer)(uint8_t *, uint8_t *, uint32_t); struct vchinfo { - u_int32_t spd, fmt, blksz, bps, run; - struct pcm_channel *channel, *parent; + struct pcm_channel *channel; struct pcmchan_caps caps; + uint32_t fmtlist[2]; + int trigger; +}; + +/* support everything (mono / stereo), except a-law / mu-law */ +static struct afmtstr_table vchan_supported_fmts[] = { + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +/* alias table, shorter. */ +static const struct { + char *alias, *fmtstr; +} vchan_fmtstralias[] = { + { "8", "u8" }, { "16", "s16le" }, + { "24", "s24le" }, { "32", "s32le" }, + { NULL, NULL }, +}; + +#define vchan_valid_format(fmt) \ + afmt2afmtstr(vchan_supported_fmts, fmt, NULL, 0, 0, \ + AFMTSTR_STEREO_RETURN) +#define vchan_valid_strformat(strfmt) \ + afmtstr2afmt(vchan_supported_fmts, strfmt, AFMTSTR_STEREO_RETURN); + +#define FEEDER_VCHAN_MIX(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_vchan_mix_##SIGNS##FMTBIT##ENDIANS##e(uint8_t *to, uint8_t *tmp, \ + uint32_t count) \ +{ \ + intpcm##FMTBIT##_t z; \ + intpcm_t x, y; \ + int i; \ + \ + i = count; \ + tmp += i; \ + to += i; \ + \ + do { \ + tmp -= PCM_##FMTBIT##_BPS; \ + to -= PCM_##FMTBIT##_BPS; \ + i -= PCM_##FMTBIT##_BPS; \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(tmp); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(to); \ + z = INTPCM##FMTBIT##_T(x) + y; \ + x = PCM_CLAMP_##SIGN##FMTBIT(z); \ + _PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(to, x); \ + } while (i != 0); \ + \ + return (count); \ +} + +FEEDER_VCHAN_MIX(8, S, s, N, n) +FEEDER_VCHAN_MIX(16, S, s, L, l) +FEEDER_VCHAN_MIX(24, S, s, L, l) +FEEDER_VCHAN_MIX(32, S, s, L, l) +FEEDER_VCHAN_MIX(16, S, s, B, b) +FEEDER_VCHAN_MIX(24, S, s, B, b) +FEEDER_VCHAN_MIX(32, S, s, B, b) +FEEDER_VCHAN_MIX(8, U, u, N, n) +FEEDER_VCHAN_MIX(16, U, u, L, l) +FEEDER_VCHAN_MIX(24, U, u, L, l) +FEEDER_VCHAN_MIX(32, U, u, L, l) +FEEDER_VCHAN_MIX(16, U, u, B, b) +FEEDER_VCHAN_MIX(24, U, u, B, b) +FEEDER_VCHAN_MIX(32, U, u, B, b) + +struct feed_vchan_info { + uint32_t format; + int bps; + feed_vchan_mixer mix; }; -static u_int32_t vchan_fmt[] = { - AFMT_STEREO | AFMT_S16_LE, - 0 +static struct feed_vchan_info feed_vchan_info_tbl[] = { + { AFMT_S8, PCM_8_BPS, feed_vchan_mix_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_vchan_mix_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_vchan_mix_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_vchan_mix_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_vchan_mix_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_vchan_mix_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_vchan_mix_s32be }, + { AFMT_U8, PCM_8_BPS, feed_vchan_mix_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_vchan_mix_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_vchan_mix_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_vchan_mix_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_vchan_mix_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_vchan_mix_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_vchan_mix_u32be }, }; +#define FVCHAN_DATA(i, c) ((intptr_t)((((i) & 0x1f) << 4) | ((c) & 0xf))) +#define FVCHAN_INFOIDX(m) (((m) >> 4) & 0x1f) +#define FVCHAN_CHANNELS(m) ((m) & 0xf) + static int -vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) +feed_vchan_init(struct pcm_feeder *f) +{ + int i, channels; + + if (f->desc->out != f->desc->in) + return (EINVAL); + + channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + + for (i = 0; i < sizeof(feed_vchan_info_tbl) / + sizeof(feed_vchan_info_tbl[0]); i++) { + if ((f->desc->out & ~AFMT_STEREO) == + feed_vchan_info_tbl[i].format) { + f->data = (void *)FVCHAN_DATA(i, channels); + return (0); + } + } + + return (-1); +} + +static __inline int +feed_vchan_rec(struct pcm_channel *c) { + struct pcm_channel *ch; + struct snd_dbuf *b, *bs; + int cnt, rdy; + /* - * to is the output buffer, tmp is the input buffer - * count is the number of 16bit samples to mix + * Reset ready and moving pointer. We're not using bufsoft + * anywhere since its sole purpose is to become the primary + * distributor for the recorded buffer and also as an interrupt + * threshold progress indicator. */ - int i; - int x; + b = c->bufsoft; + b->rp = 0; + b->rl = 0; + cnt = sndbuf_getsize(b); + + do { + cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, cnt, + c->bufhard); + if (cnt != 0) { + sndbuf_acquire(b, b->tmpbuf, cnt); + cnt = sndbuf_getfree(b); + } + } while (cnt != 0); + + /* Not enough data */ + if (b->rl < sndbuf_getbps(b)) { + b->rl = 0; + return (0); + } + + /* + * Keep track of ready and moving pointer since we will use + * bufsoft over and over again, pretending nothing has happened. + */ + rdy = b->rl; - for(i = 0; i < count; i++) { - x = to[i]; - x += tmp[i]; - if (x < -32768) { - /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */ - x = -32768; - } - if (x > 32767) { - /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */ - x = 32767; + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + if (CHN_STOPPED(ch)) { + CHN_UNLOCK(ch); + continue; + } + bs = ch->bufsoft; + if (ch->flags & CHN_F_MAPPED) + sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); + cnt = sndbuf_getfree(bs); + if (cnt < sndbuf_getbps(bs)) { + CHN_UNLOCK(ch); + continue; } - to[i] = x & 0x0000ffff; + do { + cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf, cnt, b); + if (cnt != 0) { + sndbuf_acquire(bs, bs->tmpbuf, cnt); + cnt = sndbuf_getfree(bs); + } + } while (cnt != 0); + /* + * Not entirely flushed out... + */ + if (b->rl != 0) + ch->xruns++; + CHN_UNLOCK(ch); + /* + * Rewind buffer position for next virtual channel. + */ + b->rp = 0; + b->rl = rdy; } - return 0; + + /* + * Set ready pointer to indicate that our children are ready + * to be woken up, also as an interrupt threshold progress + * indicator. + */ + b->rl = 1; + + /* + * Return 0 to bail out early from sndbuf_feed() loop. + * No need to increase feedcount counter since part of this + * feeder chains already include feed_root(). + */ + return (0); } static int -feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - /* we're going to abuse things a bit */ + struct feed_vchan_info *info; struct snd_dbuf *src = source; - struct pcmchan_children *cce; struct pcm_channel *ch; - uint32_t sz; - int16_t *tmp, *dst; - unsigned int cnt, rcnt = 0; - - #if 0 - if (sndbuf_getsize(src) < count) - panic("feed_vchan_s16(%s): tmp buffer size %d < count %d, flags = 0x%x", - c->name, sndbuf_getsize(src), count, c->flags); - #endif + uint32_t cnt, mcnt, rcnt, sz; + uint8_t *tmp; + + if (c->direction == PCMDIR_REC) + return (feed_vchan_rec(c)); + sz = sndbuf_getsize(src); if (sz < count) count = sz; - count &= ~1; - if (count < 2) - return 0; - bzero(b, count); + + info = &feed_vchan_info_tbl[FVCHAN_INFOIDX((intptr_t)f->data)]; + sz = info->bps * FVCHAN_CHANNELS((intptr_t)f->data); + count -= count % sz; + if (count < sz) + return (0); /* * we are going to use our source as a temporary buffer since it's * got no other purpose. we obtain our data by traversing the channel - * list of children and calling vchan_mix_* to mix count bytes from each - * into our destination buffer, b + * list of children and calling vchan_mix_* to mix count bytes from + * each into our destination buffer, b */ - dst = (int16_t *)b; - tmp = (int16_t *)sndbuf_getbuf(src); - bzero(tmp, count); - SLIST_FOREACH(cce, &c->children, link) { - ch = cce->channel; - CHN_LOCK(ch); - if (ch->flags & CHN_F_TRIGGERED) { - if (ch->flags & CHN_F_MAPPED) - sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); - cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); - vchan_mix_s16(dst, tmp, cnt >> 1); - if (cnt > rcnt) - rcnt = cnt; + tmp = sndbuf_getbuf(src); + rcnt = 0; + mcnt = 0; + + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + if (CHN_STOPPED(ch)) { + CHN_UNLOCK(ch); + continue; + } + if ((ch->flags & CHN_F_MAPPED) && !(ch->flags & CHN_F_CLOSING)) + sndbuf_acquire(ch->bufsoft, NULL, + sndbuf_getfree(ch->bufsoft)); + if (rcnt == 0) { + rcnt = FEEDER_FEED(ch->feeder, ch, b, count, + ch->bufsoft); + rcnt -= rcnt % sz; + mcnt = count - rcnt; + } else { + cnt = FEEDER_FEED(ch->feeder, ch, tmp, count, + ch->bufsoft); + cnt -= cnt % sz; + if (cnt != 0) { + if (mcnt != 0) { + memset(b + rcnt, + sndbuf_zerodata(f->desc->out), + mcnt); + mcnt = 0; + } + cnt = info->mix(b, tmp, cnt); + if (cnt > rcnt) + rcnt = cnt; + } } - CHN_UNLOCK(ch); + CHN_UNLOCK(ch); } - return rcnt & ~1; + if (++c->feedcount == 0) + c->feedcount = 2; + + return (rcnt); } -static struct pcm_feederdesc feeder_vchan_s16_desc[] = { +static struct pcm_feederdesc feeder_vchan_desc[] = { + {FEEDER_MIXER, AFMT_S8, AFMT_S8, 0}, + {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_MIXER, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_MIXER, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_MIXER, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_MIXER, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_MIXER, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_MIXER, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, + {FEEDER_MIXER, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U8, AFMT_U8, 0}, + {FEEDER_MIXER, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_MIXER, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_MIXER, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_MIXER, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_MIXER, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_MIXER, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_MIXER, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_vchan_s16_methods[] = { - KOBJMETHOD(feeder_feed, feed_vchan_s16), - { 0, 0 } +static kobj_method_t feeder_vchan_methods[] = { + KOBJMETHOD(feeder_init, feed_vchan_init), + KOBJMETHOD(feeder_feed, feed_vchan), + {0, 0} }; -FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); +FEEDER_DECLARE(feeder_vchan, 2, NULL); /************************************************************/ static void * -vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { struct vchinfo *ch; - struct pcm_channel *parent = devinfo; - KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction")); + KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, + ("vchan_init: bad direction")); + KASSERT(c != NULL && c->parentchannel != NULL, + ("vchan_init: bad channels")); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); - if (!ch) - return NULL; - ch->parent = parent; ch->channel = c; - ch->fmt = AFMT_U8; - ch->spd = DSP_DEFAULT_SPEED; - ch->blksz = 2048; + ch->trigger = PCMTRIG_STOP; c->flags |= CHN_F_VIRTUAL; - return ch; + return (ch); } static int vchan_free(kobj_t obj, void *data) { free(data, M_DEVBUF); - return 0; -} -static int -vchan_setformat(kobj_t obj, void *data, u_int32_t format) -{ - struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; - - ch->fmt = format; - ch->bps = 1; - ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0; - if (ch->fmt & AFMT_16BIT) - ch->bps <<= 1; - else if (ch->fmt & AFMT_24BIT) - ch->bps *= 3; - else if (ch->fmt & AFMT_32BIT) - ch->bps <<= 2; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_FORMAT); - CHN_LOCK(channel); - sndbuf_setfmt(channel->bufsoft, format); - return 0; + return (0); } static int -vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; - ch->spd = speed; - CHN_UNLOCK(channel); - CHN_LOCK(parent); - speed = sndbuf_getspd(parent->bufsoft); - CHN_UNLOCK(parent); - CHN_LOCK(channel); - return speed; + if (fmtvalid(format, ch->fmtlist) == 0) + return (-1); + + return (0); } static int -vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchinfo *ch = data; - struct pcm_channel *channel = ch->channel; - struct pcm_channel *parent = ch->parent; - /* struct pcm_channel *channel = ch->channel; */ - int prate, crate; - - ch->blksz = blocksize; - /* CHN_UNLOCK(channel); */ - sndbuf_setblksz(channel->bufhard, blocksize); - chn_notify(parent, CHN_N_BLOCKSIZE); - CHN_LOCK(parent); - /* CHN_LOCK(channel); */ - - crate = ch->spd * ch->bps; - prate = sndbuf_getspd(parent->bufsoft) * sndbuf_getbps(parent->bufsoft); - blocksize = sndbuf_getblksz(parent->bufsoft); - CHN_UNLOCK(parent); - blocksize *= prate; - blocksize /= crate; + struct pcm_channel *p = ch->channel->parentchannel; - return blocksize; + return (sndbuf_getspd(p->bufsoft)); } static int vchan_trigger(kobj_t obj, void *data, int go) { struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; + struct pcm_channel *c, *p; + int err, otrigger; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) - return 0; + if (!PCMTRIG_COMMON(go) || go == ch->trigger) + return (0); - ch->run = (go == PCMTRIG_START)? 1 : 0; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_TRIGGER); - CHN_LOCK(channel); + c = ch->channel; + p = c->parentchannel; + otrigger = ch->trigger; + ch->trigger = go; + + CHN_UNLOCK(c); + CHN_LOCK(p); + + switch (go) { + case PCMTRIG_START: + if (otrigger != PCMTRIG_START) + CHN_INSERT_HEAD(p, c, children.busy); + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + if (otrigger == PCMTRIG_START) + CHN_REMOVE(p, c, children.busy); + break; + default: + break; + } + + err = chn_notify(p, CHN_N_TRIGGER); + CHN_UNLOCK(p); + CHN_LOCK(c); - return 0; + return (err); } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; + struct pcm_channel *c, *p; + uint32_t fmt; - ch->caps.minspeed = sndbuf_getspd(ch->parent->bufsoft); + c = ch->channel; + p = c->parentchannel; + ch->caps.minspeed = sndbuf_getspd(p->bufsoft); ch->caps.maxspeed = ch->caps.minspeed; - ch->caps.fmtlist = vchan_fmt; ch->caps.caps = 0; + ch->fmtlist[1] = 0; + fmt = sndbuf_getfmt(p->bufsoft); + if (fmt != vchan_valid_format(fmt)) { + device_printf(c->dev, + "%s: WARNING: invalid vchan format! (0x%08x)\n", + __func__, fmt); + fmt = VCHAN_DEFAULT_AFMT; + } + ch->fmtlist[0] = fmt; + ch->caps.fmtlist = ch->fmtlist; - return &ch->caps; + return (&ch->caps); } static kobj_method_t vchan_methods[] = { - KOBJMETHOD(channel_init, vchan_init), - KOBJMETHOD(channel_free, vchan_free), - KOBJMETHOD(channel_setformat, vchan_setformat), - KOBJMETHOD(channel_setspeed, vchan_setspeed), - KOBJMETHOD(channel_setblocksize, vchan_setblocksize), - KOBJMETHOD(channel_trigger, vchan_trigger), - KOBJMETHOD(channel_getcaps, vchan_getcaps), - { 0, 0 } + KOBJMETHOD(channel_init, vchan_init), + KOBJMETHOD(channel_free, vchan_free), + KOBJMETHOD(channel_setformat, vchan_setformat), + KOBJMETHOD(channel_setspeed, vchan_setspeed), + KOBJMETHOD(channel_trigger, vchan_trigger), + KOBJMETHOD(channel_getcaps, vchan_getcaps), + {0, 0} }; CHANNEL_DECLARE(vchan); -#if 0 /* * On the fly vchan rate settings */ #ifdef SND_DYNSYSCTL static int -sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS) +sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; - struct snddev_channel *sce; - struct pcm_channel *c, *ch = NULL, *fake; + struct pcm_channel *c, *ch = NULL; struct pcmchan_caps *caps; - int err = 0; - int newspd = 0; + int *vchanrate, vchancount, direction, err, newspd; - d = oidp->oid_arg1; - if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1) - return EINVAL; - if (pcm_inprog(d, 1) != 1 && req->newptr != NULL) { - pcm_inprog(d, -1); - return EINPROGRESS; + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); + + pcm_lock(d); + PCM_WAIT(d); + + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + vchanrate = &d->pvchanrate; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + vchanrate = &d->rvchanrate; + break; + default: + pcm_unlock(d); + return (EINVAL); + break; + } + + if (vchancount < 1) { + pcm_unlock(d); + return (EINVAL); } - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + + PCM_ACQUIRE(d); + pcm_unlock(d); + + newspd = 0; + + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if (c->direction == PCMDIR_PLAY) { + if (c->direction == direction) { if (c->flags & CHN_F_VIRTUAL) { /* Sanity check */ if (ch != NULL && ch != c->parentchannel) { CHN_UNLOCK(c); - pcm_inprog(d, -1); - return EINVAL; - } - if (req->newptr != NULL && - (c->flags & CHN_F_BUSY)) { - CHN_UNLOCK(c); - pcm_inprog(d, -1); - return EBUSY; + PCM_RELEASE_QUICK(d); + return (EINVAL); } } else if (c->flags & CHN_F_HAS_VCHAN) { /* No way!! */ if (ch != NULL) { CHN_UNLOCK(c); - pcm_inprog(d, -1); - return EINVAL; + PCM_RELEASE_QUICK(d); + return (EINVAL); } ch = c; newspd = ch->speed; @@ -326,100 +557,217 @@ CHN_UNLOCK(c); } if (ch == NULL) { - pcm_inprog(d, -1); - return EINVAL; + PCM_RELEASE_QUICK(d); + return (EINVAL); } - err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req); + + err = sysctl_handle_int(oidp, &newspd, 0, req); if (err == 0 && req->newptr != NULL) { - if (newspd < 1 || newspd < feeder_rate_ratemin || - newspd > feeder_rate_ratemax) { - pcm_inprog(d, -1); - return EINVAL; + if (newspd < 1 || newspd < feeder_rate_min || + newspd > feeder_rate_max) { + PCM_RELEASE_QUICK(d); + return (EINVAL); } CHN_LOCK(ch); - caps = chn_getcaps(ch); - if (caps == NULL || newspd < caps->minspeed || - newspd > caps->maxspeed) { - CHN_UNLOCK(ch); - pcm_inprog(d, -1); - return EINVAL; + if (feeder_rate_round) { + caps = chn_getcaps(ch); + if (caps == NULL || newspd < caps->minspeed || + newspd > caps->maxspeed) { + CHN_UNLOCK(ch); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } } - if (newspd != ch->speed) { + if (CHN_STOPPED(ch) && newspd != ch->speed) { err = chn_setspeed(ch, newspd); /* * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ - if (!err && (ch->feederflags & (1 << FEEDER_RATE))) { + if (!err && feeder_rate_round && + (ch->feederflags & (1 << FEEDER_RATE))) { newspd = sndbuf_getspd(ch->bufhard); err = chn_setspeed(ch, newspd); } - CHN_UNLOCK(ch); - if (err == 0) { - fake = pcm_getfakechan(d); - if (fake != NULL) { - CHN_LOCK(fake); - fake->speed = newspd; - CHN_UNLOCK(fake); + if (err == 0) + *vchanrate = newspd; + } + CHN_UNLOCK(ch); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + struct pcm_channel *c, *ch = NULL; + uint32_t newfmt, spd; + int *vchanformat, vchancount, direction, err, i; + char fmtstr[AFMTSTR_MAXSZ]; + + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); + + pcm_lock(d); + PCM_WAIT(d); + + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + vchanformat = &d->pvchanformat; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + vchanformat = &d->rvchanformat; + break; + default: + pcm_unlock(d); + return (EINVAL); + break; + } + + if (vchancount < 1) { + pcm_unlock(d); + return (EINVAL); + } + + PCM_ACQUIRE(d); + pcm_unlock(d); + + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction == direction) { + if (c->flags & CHN_F_VIRTUAL) { + /* Sanity check */ + if (ch != NULL && ch != c->parentchannel) { + CHN_UNLOCK(c); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + } else if (c->flags & CHN_F_HAS_VCHAN) { + /* No way!! */ + if (ch != NULL) { + CHN_UNLOCK(c); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + ch = c; + if (ch->format != + afmt2afmtstr(vchan_supported_fmts, + ch->format, fmtstr, sizeof(fmtstr), + AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { + strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, + sizeof(fmtstr)); } } - } else - CHN_UNLOCK(ch); + } + CHN_UNLOCK(c); + } + if (ch == NULL) { + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + + err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); + if (err == 0 && req->newptr != NULL) { + for (i = 0; vchan_fmtstralias[i].alias != NULL; i++) { + if (strcmp(fmtstr, vchan_fmtstralias[i].alias) == 0) { + strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, + sizeof(fmtstr)); + break; + } + } + newfmt = vchan_valid_strformat(fmtstr); + if (newfmt == 0) { + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + CHN_LOCK(ch); + if (CHN_STOPPED(ch) && newfmt != ch->format) { + /* Get channel speed, before chn_reset() screw it. */ + spd = ch->speed; + err = chn_reset(ch, newfmt); + if (err == 0) + err = chn_setspeed(ch, spd); + if (err == 0) + *vchanformat = newfmt; + } + CHN_UNLOCK(ch); } - pcm_inprog(d, -1); - return err; + + PCM_RELEASE_QUICK(d); + + return (err); } #endif -#endif /* virtual channel interface */ +#define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ + "play.vchanformat" : "rec.vchanformat" +#define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ + "play.vchanrate" : "rec.vchanrate" + int -vchan_create(struct pcm_channel *parent) +vchan_create(struct pcm_channel *parent, int num) { - struct snddev_info *d = parent->parentsnddev; - struct pcmchan_children *pce; - struct pcm_channel *child, *fake; + struct snddev_info *d = parent->parentsnddev; + struct pcm_channel *ch; struct pcmchan_caps *parent_caps; - int err, first, speed = 0; + uint32_t vchanfmt; + int err, first, speed, r; + int direction; - if (!(parent->flags & CHN_F_BUSY)) - return EBUSY; + PCM_BUSYASSERT(d); + if (!(parent->flags & CHN_F_BUSY)) + return (EBUSY); + if (parent->direction == PCMDIR_PLAY) { + direction = PCMDIR_PLAY_VIRTUAL; + vchanfmt = d->pvchanformat; + speed = d->pvchanrate; + } else if (parent->direction == PCMDIR_REC) { + direction = PCMDIR_REC_VIRTUAL; + vchanfmt = d->rvchanformat; + speed = d->rvchanrate; + } else + return (EINVAL); CHN_UNLOCK(parent); - pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); - if (!pce) { - CHN_LOCK(parent); - return ENOMEM; - } - /* create a new playback channel */ - child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); - if (!child) { - free(pce, M_DEVBUF); - CHN_LOCK(parent); - return ENODEV; + pcm_lock(d); + ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent); + if (ch == NULL) { + pcm_unlock(d); + CHN_LOCK(parent); + return (ENODEV); } - pce->channel = child; /* add us to our grandparent's channel list */ - /* - * XXX maybe we shouldn't always add the dev_t - */ - err = pcm_chn_add(d, child); + err = pcm_chn_add(d, ch); + pcm_unlock(d); if (err) { - pcm_chn_destroy(child); - free(pce, M_DEVBUF); + pcm_chn_destroy(ch); CHN_LOCK(parent); - return err; + return (err); } - CHN_LOCK(parent); - /* add us to our parent channel's children */ - first = SLIST_EMPTY(&parent->children); - SLIST_INSERT_HEAD(&parent->children, pce, link); + CHN_LOCK(parent); + /* + * Add us to our parent channel's children in reverse order + * so future destruction will pick the last (biggest number) + * channel. + */ + first = CHN_EMPTY(parent, children); + CHN_INSERT_SORT_DESCEND(parent, ch, children); parent->flags |= CHN_F_HAS_VCHAN; if (first) { @@ -427,23 +775,37 @@ if (parent_caps == NULL) err = EINVAL; - if (!err) - err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); - if (!err) { - fake = pcm_getfakechan(d); - if (fake != NULL) { - /* - * Avoid querying kernel hint, use saved value - * from fake channel. - */ + if (vchanfmt == 0) { + const char *vfmt; + CHN_UNLOCK(parent); - CHN_LOCK(fake); - speed = fake->speed; - CHN_UNLOCK(fake); + r = resource_string_value( + device_get_name(parent->dev), + device_get_unit(parent->dev), + VCHAN_FMT_HINT(direction), + &vfmt); CHN_LOCK(parent); + if (r != 0) + vfmt = NULL; + if (vfmt != NULL) { + vchanfmt = vchan_valid_strformat(vfmt); + for (r = 0; vchanfmt == 0 && + vchan_fmtstralias[r].alias != NULL; + r++) { + if (strcmp(vfmt, vchan_fmtstralias[r].alias) == 0) { + vchanfmt = vchan_valid_strformat(vchan_fmtstralias[r].fmtstr); + break; + } + } + } + if (vchanfmt == 0) + vchanfmt = VCHAN_DEFAULT_AFMT; } + err = chn_reset(parent, vchanfmt); + } + if (!err) { /* * This is very sad. Few soundcards advertised as being * able to do (insanely) higher/lower speed, but in @@ -451,14 +813,17 @@ * to set sane value via kernel hints or sysctl. */ if (speed < 1) { - int r; CHN_UNLOCK(parent); - r = resource_int_value(device_get_name(parent->dev), - device_get_unit(parent->dev), - "vchanrate", &speed); + r = resource_int_value( + device_get_name(parent->dev), + device_get_unit(parent->dev), + VCHAN_SPD_HINT(direction), + &speed); CHN_LOCK(parent); if (r != 0) { /* + * No saved value, no hint, NOTHING. + * * Workaround for sb16 running * poorly at 45k / 49k. */ @@ -469,138 +834,135 @@ break; default: speed = VCHAN_DEFAULT_SPEED; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; break; } + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; } } - /* - * Limit speed based on driver caps. - * This is supposed to help fixed rate, non-VRA - * AC97 cards, but.. (see below) - */ - if (speed < parent_caps->minspeed) - speed = parent_caps->minspeed; - if (speed > parent_caps->maxspeed) - speed = parent_caps->maxspeed; + if (feeder_rate_round) { + /* + * Limit speed based on driver caps. + * This is supposed to help fixed rate, non-VRA + * AC97 cards, but.. (see below) + */ + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; + } /* * We still need to limit the speed between - * feeder_rate_ratemin <-> feeder_rate_ratemax. This is + * feeder_rate_min <-> feeder_rate_max. This is * just an escape goat if all of the above failed * miserably. */ - if (speed < feeder_rate_ratemin) - speed = feeder_rate_ratemin; - if (speed > feeder_rate_ratemax) - speed = feeder_rate_ratemax; + if (speed < feeder_rate_min) + speed = feeder_rate_min; + if (speed > feeder_rate_max) + speed = feeder_rate_max; err = chn_setspeed(parent, speed); /* * Try to avoid FEEDER_RATE on parent channel if the * requested value is not supported by the hardware. */ - if (!err && (parent->feederflags & (1 << FEEDER_RATE))) { + if (!err && feeder_rate_round && + (parent->feederflags & (1 << FEEDER_RATE))) { speed = sndbuf_getspd(parent->bufhard); err = chn_setspeed(parent, speed); } - if (!err && fake != NULL) { + if (!err) { /* - * Save new value to fake channel. + * Save new value. */ CHN_UNLOCK(parent); - CHN_LOCK(fake); - fake->speed = speed; - CHN_UNLOCK(fake); + if (direction == PCMDIR_PLAY_VIRTUAL) { + d->pvchanformat = vchanfmt; + d->pvchanrate = speed; + } else { + d->rvchanformat = vchanfmt; + d->rvchanrate = speed; + } CHN_LOCK(parent); } } if (err) { - SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); + CHN_REMOVE(parent, ch, children); parent->flags &= ~CHN_F_HAS_VCHAN; CHN_UNLOCK(parent); - free(pce, M_DEVBUF); - if (pcm_chn_remove(d, child) == 0) - pcm_chn_destroy(child); + pcm_lock(d); + if (pcm_chn_remove(d, ch) == 0) { + pcm_unlock(d); + pcm_chn_destroy(ch); + } else + pcm_unlock(d); CHN_LOCK(parent); - return err; + return (err); } } - return 0; + return (0); } int vchan_destroy(struct pcm_channel *c) { - struct pcm_channel *parent = c->parentchannel; - struct snddev_info *d = parent->parentsnddev; - struct pcmchan_children *pce; - struct snddev_channel *sce; + struct pcm_channel *parent; + struct snddev_info *d; uint32_t spd; int err; - CHN_LOCK(parent); - if (!(parent->flags & CHN_F_BUSY)) { - CHN_UNLOCK(parent); - return EBUSY; - } - if (SLIST_EMPTY(&parent->children)) { - CHN_UNLOCK(parent); - return EINVAL; - } + KASSERT(c != NULL && c->parentchannel != NULL && + c->parentsnddev != NULL, ("%s(): invalid channel=%p", + __func__, c)); + + CHN_LOCKASSERT(c); + + d = c->parentsnddev; + parent = c->parentchannel; + + PCM_BUSYASSERT(d); + CHN_LOCKASSERT(parent); + + CHN_UNLOCK(c); + + if (!(parent->flags & CHN_F_BUSY)) + return (EBUSY); + + if (CHN_EMPTY(parent, children)) + return (EINVAL); /* remove us from our parent's children list */ - SLIST_FOREACH(pce, &parent->children, link) { - if (pce->channel == c) - goto gotch; - } - CHN_UNLOCK(parent); - return EINVAL; -gotch: - SLIST_FOREACH(sce, &d->channels, link) { - if (sce->channel == c) { - if (sce->dsp_devt) { - destroy_dev(sce->dsp_devt); - sce->dsp_devt = NULL; - } - if (sce->dspW_devt) { - destroy_dev(sce->dspW_devt); - sce->dspW_devt = NULL; - } - if (sce->audio_devt) { - destroy_dev(sce->audio_devt); - sce->audio_devt = NULL; - } - if (sce->dspr_devt) { - destroy_dev(sce->dspr_devt); - sce->dspr_devt = NULL; - } - d->devcount--; - break; - } - } - SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); - free(pce, M_DEVBUF); + CHN_REMOVE(parent, c, children); - if (SLIST_EMPTY(&parent->children)) { + if (CHN_EMPTY(parent, children)) { parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); spd = parent->speed; if (chn_reset(parent, parent->format) == 0) chn_setspeed(parent, spd); } + CHN_UNLOCK(parent); + /* remove us from our grandparent's channel list */ + pcm_lock(d); err = pcm_chn_remove(d, c); + pcm_unlock(d); - CHN_UNLOCK(parent); /* destroy ourselves */ if (!err) err = pcm_chn_destroy(c); - return err; + CHN_LOCK(parent); + + return (err); } int @@ -608,17 +970,44 @@ { #ifdef SND_DYNSYSCTL struct snddev_info *d; + int unit; - d = device_get_softc(dev); - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), - sysctl_hw_snd_vchans, "I", ""); -#if 0 - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), - sysctl_hw_snd_vchanrate, "I", ""); -#endif + unit = device_get_unit(dev); + d = device_get_softc(dev); + + /* Play */ + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanformat, "A", "virtual channel format"); + /* Rec */ + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanrate, "I", "virtual channel base speed/rate"); + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanformat, "A", "virtual channel format"); #endif - return 0; + return (0); } --- sys/dev/sound/pcm/vchan.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/pcm/vchan.h Thu Jul 12 12:04:19 2007 @@ -23,11 +23,30 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/vchan.h,v 1.4 2005/01/06 01:43:21 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/vchan.h,v 1.5 2007/05/31 18:43:32 ariff Exp $ */ -int vchan_create(struct pcm_channel *parent); +int vchan_create(struct pcm_channel *parent, int num); int vchan_destroy(struct pcm_channel *c); int vchan_initsys(device_t dev); +/* + * Default speed / format + */ +#define VCHAN_DEFAULT_SPEED 48000 +#define VCHAN_DEFAULT_AFMT (AFMT_S16_LE | AFMT_STEREO) +#define VCHAN_DEFAULT_STRFMT "s16le" + +#define VCHAN_PLAY 0 +#define VCHAN_REC 1 + +/* + * Offset by +/- 1 so we can distinguish bogus pointer. + */ +#define VCHAN_SYSCTL_DATA(x, y) \ + ((void *)((intptr_t)(((((x) + 1) & 0xfff) << 2) | \ + (((VCHAN_##y) + 1) & 0x3)))) +#define VCHAN_SYSCTL_DATA_SIZE sizeof(void *) +#define VCHAN_SYSCTL_UNIT(x) ((int)(((intptr_t)(x) >> 2) & 0xfff) - 1) +#define VCHAN_SYSCTL_DIR(x) ((int)((intptr_t)(x) & 0x3) - 1) --- sys/dev/sound/sbus/cs4231.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/sbus/cs4231.c Thu Jul 12 12:04:19 2007 @@ -37,7 +37,7 @@ */ #include -__FBSDID("$FreeBSD: src/sys/dev/sound/sbus/cs4231.c,v 1.4 2005/05/19 18:13:49 marius Exp $"); +__FBSDID("$FreeBSD: src/sys/dev/sound/sbus/cs4231.c,v 1.9 2007/06/17 06:10:43 ariff Exp $"); #include #include @@ -306,10 +306,12 @@ static int cs4231_bus_probe(device_t dev) { - const char *name; + const char *compat, *name; + compat = ofw_bus_get_compat(dev); name = ofw_bus_get_name(dev); - if (strcmp("SUNW,CS4231", name) == 0) { + if (strcmp("SUNW,CS4231", name) == 0 || + (compat != NULL && strcmp("SUNW,CS4231", compat) == 0)) { device_set_desc(dev, "Sun Audiocs"); return (BUS_PROBE_DEFAULT); } @@ -319,16 +321,10 @@ static int cs4231_sbus_attach(device_t dev) { - struct snddev_info *d; struct cs4231_softc *sc; int burst; - d = device_get_softc(dev); - sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO); - if (sc == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return (ENOMEM); - } + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_dev = dev; /* * XXX @@ -352,10 +348,8 @@ static int cs4231_ebus_attach(device_t dev) { - struct snddev_info *d; struct cs4231_softc *sc; - d = device_get_softc(dev); sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO); if (sc == NULL) { device_printf(dev, "cannot allocate softc\n"); @@ -377,12 +371,7 @@ int i; sc->sc_lock = snd_mtxcreate(device_get_nameunit(sc->sc_dev), - "sound softc"); - if (sc->sc_lock == NULL) { - device_printf(sc->sc_dev, "cannot create mutex\n"); - free(sc, M_DEVBUF); - return (ENXIO); - } + "snd_cs4231 softc"); for (i = 0; i < sc->sc_nmres; i++) { sc->sc_rid[i] = i; @@ -436,7 +425,7 @@ CS4231_DEFAULT_BUF_SZ, CS4231_MAX_BUF_SZ); for (i = 0; i < sc->sc_nires; i++) { if (bus_dma_tag_create( - NULL, /* parent */ + bus_get_dma_tag(sc->sc_dev),/* parent */ 64, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ @@ -1043,7 +1032,7 @@ else dmat = sc->sc_dmat[0]; } - if (sndbuf_alloc(ch->buffer, dmat, sc->sc_bufsz) != 0) + if (sndbuf_alloc(ch->buffer, dmat, 0, sc->sc_bufsz) != 0) return (NULL); DPRINTF(("%s channel addr: 0x%lx\n", dir == PCMDIR_PLAY ? "playback" : "capture", sndbuf_getbufaddr(ch->buffer))); --- sys/dev/sound/unit.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/unit.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,194 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/unit.c,v 1.1 2007/05/31 18:35:24 ariff Exp $ + */ + +#include +#include + +#include + +/* + * Unit magic allocator for sound driver. + * + * 'u' = Unit of attached soundcards + * 'd' = Device type + * 'c' = Channel number + * + * eg: dsp0.p1 - u=0, d=p, c=1 + * dsp1.vp0 - u=1, d=vp, c=0 + * dsp0.10 - u=0, d=clone, c=allocated clone (see further explanation) + * + * Maximum unit of soundcards can be tuned through "hw.snd.maxunit", which + * is between SND_UNIT_UMIN (16) and SND_UNIT_UMAX (2048). By design, + * maximum allowable allocated channel is 256, with exception for clone + * devices which doesn't have any notion of channel numbering. The use of + * channel numbering in a clone device is simply to provide uniqueness among + * allocated clones. This also means that the maximum allowable clonable + * device is largely dependant and dynamically tuned depending on + * hw.snd.maxunit. + */ + +/* Default width */ +static int snd_u_shift = 9; /* 0 - 0x1ff : 512 distinct soundcards */ +static int snd_d_shift = 5; /* 0 - 0x1f : 32 distinct device types */ +static int snd_c_shift = 10; /* 0 - 0x3ff : 1024 distinct channels + (256 limit "by design", + except for clone devices) */ + +static int snd_unit_initialized = 0; + +#ifdef SND_DIAGNOSTIC +#define SND_UNIT_ASSERT() do { \ + if (snd_unit_initialized == 0) \ + panic("%s(): Uninitialized sound unit!", __func__); \ +} while(0) +#else +#define SND_UNIT_ASSERT() KASSERT(snd_unit_initialized != 0, \ + ("%s(): Uninitialized sound unit!", \ + __func__)) +#endif + +#define MKMASK(x) ((1 << snd_##x##_shift) - 1) + +int +snd_max_u(void) +{ + SND_UNIT_ASSERT(); + + return (MKMASK(u)); +} + +int +snd_max_d(void) +{ + SND_UNIT_ASSERT(); + + return (MKMASK(d)); +} + +int +snd_max_c(void) +{ + SND_UNIT_ASSERT(); + + return (MKMASK(c)); +} + +int +snd_unit2u(int unit) +{ + SND_UNIT_ASSERT(); + + return ((unit >> (snd_c_shift + snd_d_shift)) & MKMASK(u)); +} + +int +snd_unit2d(int unit) +{ + SND_UNIT_ASSERT(); + + return ((unit >> snd_c_shift) & MKMASK(d)); +} + +int +snd_unit2c(int unit) +{ + SND_UNIT_ASSERT(); + + return (unit & MKMASK(c)); +} + +int +snd_u2unit(int u) +{ + SND_UNIT_ASSERT(); + + return ((u & MKMASK(u)) << (snd_c_shift + snd_d_shift)); +} + +int +snd_d2unit(int d) +{ + SND_UNIT_ASSERT(); + + return ((d & MKMASK(d)) << snd_c_shift); +} + +int +snd_c2unit(int c) +{ + SND_UNIT_ASSERT(); + + return (c & MKMASK(c)); +} + +int +snd_mkunit(int u, int d, int c) +{ + SND_UNIT_ASSERT(); + + return ((c & MKMASK(c)) | ((d & MKMASK(d)) << snd_c_shift) | + ((u & MKMASK(u)) << (snd_c_shift + snd_d_shift))); +} + +/* + * This *must* be called first before any of the functions above!!! + */ +void +snd_unit_init(void) +{ + int i; + + if (snd_unit_initialized != 0) + return; + + snd_unit_initialized = 1; + + if (getenv_int("hw.snd.maxunit", &i) != 0) { + if (i < SND_UNIT_UMIN) + i = SND_UNIT_UMIN; + else if (i > SND_UNIT_UMAX) + i = SND_UNIT_UMAX; + else + i = roundup2(i, 2); + + for (snd_u_shift = 0; (i >> (snd_u_shift + 1)) != 0; + snd_u_shift++) + ; + + /* + * Make room for channels/clones allocation unit + * to fit within 24bit MAXMINOR limit. + */ + snd_c_shift = 24 - snd_u_shift - snd_d_shift; + } + + if (bootverbose != 0) + printf("%s() u=0x%08x [%d] d=0x%08x [%d] c=0x%08x [%d]\n", + __func__, SND_U_MASK, snd_max_u() + 1, + SND_D_MASK, snd_max_d() + 1, SND_C_MASK, snd_max_c() + 1); +} --- sys/dev/sound/unit.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/unit.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/unit.h,v 1.1 2007/05/31 18:35:24 ariff Exp $ + */ + +#ifndef _SND_UNIT_H_ +#define _SND_UNIT_H_ + +#define SND_UNIT_UMIN 16 +#define SND_UNIT_UMAX 2048 + +int snd_max_u(void); +int snd_max_d(void); +int snd_max_c(void); +int snd_unit2u(int); +int snd_unit2d(int); +int snd_unit2c(int); +int snd_u2unit(int); +int snd_d2unit(int); +int snd_c2unit(int); +int snd_mkunit(int, int, int); + +void snd_unit_init(void); + +#define SND_U_MASK (snd_u2unit(snd_max_u())) +#define SND_D_MASK (snd_d2unit(snd_max_d())) +#define SND_C_MASK (snd_c2unit(snd_max_c())) + +#endif /* !_SND_UNIT_H_ */ --- sys/dev/sound/usb/uaudio.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/usb/uaudio.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,5 @@ /* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ -/* $FreeBSD: src/sys/dev/sound/usb/uaudio.c,v 1.14.2.2 2006/04/04 17:34:10 ariff Exp $ */ +/* $FreeBSD: src/sys/dev/sound/usb/uaudio.c,v 1.36 2007/06/20 05:11:37 imp Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. @@ -55,6 +55,9 @@ * $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $ * $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $ * $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $ + * $NetBSD: uaudio.c,v 1.102 2006/04/14 17:00:55 christos Exp $ + * $NetBSD: uaudio.c,v 1.103 2006/05/11 19:09:25 mrg Exp $ + * $NetBSD: uaudio.c,v 1.105 2006/10/04 16:00:15 christos Exp $ */ #include @@ -91,10 +94,12 @@ #include #elif defined(__FreeBSD__) #include /* XXXXX */ +#include #include #include "feeder_if.h" #endif +#include #include #include #include @@ -114,8 +119,8 @@ #endif /* #define UAUDIO_MULTIPLE_ENDPOINTS */ #ifdef USB_DEBUG -#define DPRINTF(x) do { if (uaudiodebug) logprintf x; } while (0) -#define DPRINTFN(n,x) do { if (uaudiodebug>(n)) logprintf x; } while (0) +#define DPRINTF(x) do { if (uaudiodebug) printf x; } while (0) +#define DPRINTFN(n,x) do { if (uaudiodebug>(n)) printf x; } while (0) int uaudiodebug = 0; #if defined(__FreeBSD__) SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio"); @@ -221,7 +226,7 @@ }; struct uaudio_softc { - USBBASEDEVICE sc_dev; /* base device */ + device_t sc_dev; /* base device */ usbd_device_handle sc_udev; /* USB device */ int sc_ac_iface; /* Audio Control interface */ usbd_interface_handle sc_ac_ifaceh; @@ -243,12 +248,16 @@ int sc_mode; /* play/record capability */ struct mixerctl *sc_ctls; /* mixer controls */ int sc_nctls; /* # of mixer controls */ - device_ptr_t sc_audiodev; + device_t sc_audiodev; char sc_dying; #if defined(__FreeBSD__) struct sbuf uaudio_sndstat; int uaudio_sndstat_flag; + int async; #endif + int sc_vendor; + int sc_product; + int sc_release; }; struct terminal_list { @@ -287,140 +296,140 @@ #define AudioCrecord "record" #define AudioCequalization "equalization" #endif -Static const char *uac_names[] = { +static const char *uac_names[] = { AudioCoutputs, AudioCinputs, AudioCequalization, AudioCrecord, }; #endif -Static usbd_status uaudio_identify_ac +static usbd_status uaudio_identify_ac (struct uaudio_softc *, const usb_config_descriptor_t *); -Static usbd_status uaudio_identify_as +static usbd_status uaudio_identify_as (struct uaudio_softc *, const usb_config_descriptor_t *); -Static usbd_status uaudio_process_as +static usbd_status uaudio_process_as (struct uaudio_softc *, const char *, int *, int, const usb_interface_descriptor_t *); -Static void uaudio_add_alt(struct uaudio_softc *, const struct as_info *); +static void uaudio_add_alt(struct uaudio_softc *, const struct as_info *); -Static const usb_interface_descriptor_t *uaudio_find_iface +static const usb_interface_descriptor_t *uaudio_find_iface (const char *, int, int *, int); -Static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct mixerctl *); +static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct mixerctl *); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static char *uaudio_id_name +static char *uaudio_id_name (struct uaudio_softc *, const struct io_terminal *, int); #endif #ifdef USB_DEBUG -Static void uaudio_dump_cluster(const struct usb_audio_cluster *); +static void uaudio_dump_cluster(const struct usb_audio_cluster *); #endif -Static struct usb_audio_cluster uaudio_get_cluster +static struct usb_audio_cluster uaudio_get_cluster (int, const struct io_terminal *); -Static void uaudio_add_input +static void uaudio_add_input (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_output +static void uaudio_add_output (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_mixer +static void uaudio_add_mixer (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_selector +static void uaudio_add_selector (struct uaudio_softc *, const struct io_terminal *, int); #ifdef USB_DEBUG -Static const char *uaudio_get_terminal_name(int); +static const char *uaudio_get_terminal_name(int); #endif -Static int uaudio_determine_class +static int uaudio_determine_class (const struct io_terminal *, struct mixerctl *); #if defined(__FreeBSD__) -Static const int uaudio_feature_name(const struct io_terminal *, - struct mixerctl *); +static int uaudio_feature_name(const struct io_terminal *, + struct mixerctl *); #else -Static const char *uaudio_feature_name +static const char *uaudio_feature_name (const struct io_terminal *, struct mixerctl *); #endif -Static void uaudio_add_feature +static void uaudio_add_feature (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_processing_updown +static void uaudio_add_processing_updown (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_processing +static void uaudio_add_processing (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_extension +static void uaudio_add_extension (struct uaudio_softc *, const struct io_terminal *, int); -Static struct terminal_list *uaudio_merge_terminal_list +static struct terminal_list *uaudio_merge_terminal_list (const struct io_terminal *); -Static struct terminal_list *uaudio_io_terminaltype +static struct terminal_list *uaudio_io_terminaltype (int, struct io_terminal *, int); -Static usbd_status uaudio_identify +static usbd_status uaudio_identify (struct uaudio_softc *, const usb_config_descriptor_t *); -Static int uaudio_signext(int, int); +static int uaudio_signext(int, int); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int uaudio_value2bsd(struct mixerctl *, int); +static int uaudio_value2bsd(struct mixerctl *, int); #endif -Static int uaudio_bsd2value(struct mixerctl *, int); -Static int uaudio_get(struct uaudio_softc *, int, int, int, int, int); +static int uaudio_bsd2value(struct mixerctl *, int); +static int uaudio_get(struct uaudio_softc *, int, int, int, int, int); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int uaudio_ctl_get +static int uaudio_ctl_get (struct uaudio_softc *, int, struct mixerctl *, int); #endif -Static void uaudio_set +static void uaudio_set (struct uaudio_softc *, int, int, int, int, int, int); -Static void uaudio_ctl_set +static void uaudio_ctl_set (struct uaudio_softc *, int, struct mixerctl *, int, int); -Static usbd_status uaudio_set_speed(struct uaudio_softc *, int, u_int); +static usbd_status uaudio_set_speed(struct uaudio_softc *, int, u_int); -Static usbd_status uaudio_chan_open(struct uaudio_softc *, struct chan *); -Static void uaudio_chan_close(struct uaudio_softc *, struct chan *); -Static usbd_status uaudio_chan_alloc_buffers +static usbd_status uaudio_chan_open(struct uaudio_softc *, struct chan *); +static void uaudio_chan_close(struct uaudio_softc *, struct chan *); +static usbd_status uaudio_chan_alloc_buffers (struct uaudio_softc *, struct chan *); -Static void uaudio_chan_free_buffers(struct uaudio_softc *, struct chan *); +static void uaudio_chan_free_buffers(struct uaudio_softc *, struct chan *); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static void uaudio_chan_init +static void uaudio_chan_init (struct chan *, int, const struct audio_params *, int); -Static void uaudio_chan_set_param(struct chan *, u_char *, u_char *, int); +static void uaudio_chan_set_param(struct chan *, u_char *, u_char *, int); #endif -Static void uaudio_chan_ptransfer(struct chan *); -Static void uaudio_chan_pintr +static void uaudio_chan_ptransfer(struct chan *); +static void uaudio_chan_pintr (usbd_xfer_handle, usbd_private_handle, usbd_status); -Static void uaudio_chan_rtransfer(struct chan *); -Static void uaudio_chan_rintr +static void uaudio_chan_rtransfer(struct chan *); +static void uaudio_chan_rintr (usbd_xfer_handle, usbd_private_handle, usbd_status); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int uaudio_open(void *, int); -Static void uaudio_close(void *); -Static int uaudio_drain(void *); -Static int uaudio_query_encoding(void *, struct audio_encoding *); -Static void uaudio_get_minmax_rates +static int uaudio_open(void *, int); +static void uaudio_close(void *); +static int uaudio_drain(void *); +static int uaudio_query_encoding(void *, struct audio_encoding *); +static void uaudio_get_minmax_rates (int, const struct as_info *, const struct audio_params *, int, u_long *, u_long *); -Static int uaudio_match_alt_sub +static int uaudio_match_alt_sub (int, const struct as_info *, const struct audio_params *, int, u_long); -Static int uaudio_match_alt_chan +static int uaudio_match_alt_chan (int, const struct as_info *, struct audio_params *, int); -Static int uaudio_match_alt +static int uaudio_match_alt (int, const struct as_info *, struct audio_params *, int); -Static int uaudio_set_params +static int uaudio_set_params (void *, int, int, struct audio_params *, struct audio_params *); -Static int uaudio_round_blocksize(void *, int); -Static int uaudio_trigger_output +static int uaudio_round_blocksize(void *, int); +static int uaudio_trigger_output (void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); -Static int uaudio_trigger_input +static int uaudio_trigger_input (void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); -Static int uaudio_halt_in_dma(void *); -Static int uaudio_halt_out_dma(void *); -Static int uaudio_getdev(void *, struct audio_device *); -Static int uaudio_mixer_set_port(void *, mixer_ctrl_t *); -Static int uaudio_mixer_get_port(void *, mixer_ctrl_t *); -Static int uaudio_query_devinfo(void *, mixer_devinfo_t *); -Static int uaudio_get_props(void *); +static int uaudio_halt_in_dma(void *); +static int uaudio_halt_out_dma(void *); +static int uaudio_getdev(void *, struct audio_device *); +static int uaudio_mixer_set_port(void *, mixer_ctrl_t *); +static int uaudio_mixer_get_port(void *, mixer_ctrl_t *); +static int uaudio_query_devinfo(void *, mixer_devinfo_t *); +static int uaudio_get_props(void *); -Static const struct audio_hw_if uaudio_hw_if = { +static const struct audio_hw_if uaudio_hw_if = { uaudio_open, uaudio_close, uaudio_drain, @@ -450,16 +459,15 @@ NULL, }; -Static struct audio_device uaudio_device = { +static struct audio_device uaudio_device = { "USB audio", "", "uaudio" }; #elif defined(__FreeBSD__) -Static int audio_attach_mi(device_t); -Static int uaudio_init_params(struct uaudio_softc * sc, struct chan *ch, int mode); -static int uaudio_sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); +static int audio_attach_mi(device_t); +static int uaudio_init_params(struct uaudio_softc * sc, struct chan *ch, int mode); /* for NetBSD compatibirity */ #define AUMODE_PLAY 0x01 @@ -516,32 +524,43 @@ USB_ATTACH_START(uaudio, sc, uaa); usb_interface_descriptor_t *id; usb_config_descriptor_t *cdesc; +#if !defined(__FreeBSD__) char devinfo[1024]; +#endif usbd_status err; int i, j, found; #if defined(__FreeBSD__) - usbd_devinfo(uaa->device, 0, devinfo); - USB_ATTACH_SETUP; + sc->sc_dev = self; #else usbd_devinfo(uaa->device, 0, devinfo, sizeof(devinfo)); printf(": %s\n", devinfo); #endif sc->sc_udev = uaa->device; + sc->sc_vendor = uaa->vendor; + sc->sc_product = uaa->product; + sc->sc_release = uaa->release; +#if defined(__FreeBSD__) + if (resource_int_value(device_get_name(sc->sc_dev), + device_get_unit(sc->sc_dev), "async", &i) == 0 && i != 0) + sc->async = 1; + else + sc->async = 0; +#endif cdesc = usbd_get_config_descriptor(sc->sc_udev); if (cdesc == NULL) { printf("%s: failed to get configuration descriptor\n", - USBDEVNAME(sc->sc_dev)); - USB_ATTACH_ERROR_RETURN; + device_get_nameunit(sc->sc_dev)); + return ENXIO; } err = uaudio_identify(sc, cdesc); if (err) { printf("%s: audio descriptors make no sense, error=%d\n", - USBDEVNAME(sc->sc_dev), err); - USB_ATTACH_ERROR_RETURN; + device_get_nameunit(sc->sc_dev), err); + return ENXIO; } sc->sc_ac_ifaceh = uaa->iface; @@ -567,12 +586,12 @@ for (j = 0; j < sc->sc_nalts; j++) { if (sc->sc_alts[j].ifaceh == NULL) { printf("%s: alt %d missing AS interface(s)\n", - USBDEVNAME(sc->sc_dev), j); - USB_ATTACH_ERROR_RETURN; + device_get_nameunit(sc->sc_dev), j); + return ENXIO; } } - printf("%s: audio rev %d.%02x\n", USBDEVNAME(sc->sc_dev), + printf("%s: audio rev %d.%02x\n", device_get_nameunit(sc->sc_dev), sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); sc->sc_playchan.sc = sc->sc_recchan.sc = sc; @@ -585,7 +604,7 @@ #ifndef USB_DEBUG if (bootverbose) #endif - printf("%s: %d mixer controls\n", USBDEVNAME(sc->sc_dev), + printf("%s: %d mixer controls\n", device_get_nameunit(sc->sc_dev), sc->sc_nctls); #if !defined(__FreeBSD__) @@ -602,16 +621,22 @@ sc->sc_dying = 0; if (audio_attach_mi(sc->sc_dev)) { printf("audio_attach_mi failed\n"); - USB_ATTACH_ERROR_RETURN; + return ENXIO; } #endif - USB_ATTACH_SUCCESS_RETURN; +#if defined(__FreeBSD__) + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->sc_dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), + OID_AUTO, "async", CTLFLAG_RW, &sc->async, 0, + "Asynchronous USB request"); +#endif + return 0; } #if defined(__NetBSD__) || defined(__OpenBSD__) int -uaudio_activate(device_ptr_t self, enum devact act) +uaudio_activate(device_t self, enum devact act) { struct uaudio_softc *sc; int rv; @@ -634,7 +659,7 @@ #if defined(__NetBSD__) || defined(__OpenBSD__) int -uaudio_detach(device_ptr_t self, int flags) +uaudio_detach(device_t self, int flags) { struct uaudio_softc *sc; int rv; @@ -656,6 +681,10 @@ USB_DETACH(uaudio) { + struct sndcard_func *func; + device_t *devlist = NULL; + int err, i, devcount; + USB_DETACH_START(uaudio, sc); sbuf_delete(&(sc->uaudio_sndstat)); @@ -668,13 +697,29 @@ usbd_delay_ms(sc->sc_udev, UAUDIO_NCHANBUFS * UAUDIO_NFRAMES); #endif - /* do nothing ? */ - return bus_generic_detach(sc->sc_dev); + err = bus_generic_detach(sc->sc_dev); + + if (err == 0) { + device_get_children(sc->sc_dev, &devlist, &devcount); + for (i = 0; devlist != NULL && i < devcount; i++) { + func = device_get_ivars(devlist[i]); + if (func != NULL && func->func == SCF_PCM && + func->varinfo == NULL) { + device_set_ivars(devlist[i], NULL); + free(func, M_DEVBUF); + device_delete_child(sc->sc_dev, devlist[i]); + } + } + if (devlist != NULL) + free(devlist, M_TEMP); + } + + return err; } #endif #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_query_encoding(void *addr, struct audio_encoding *fp) { struct uaudio_softc *sc; @@ -745,7 +790,7 @@ } #endif -Static const usb_interface_descriptor_t * +static const usb_interface_descriptor_t * uaudio_find_iface(const char *buf, int size, int *offsp, int subtype) { const usb_interface_descriptor_t *d; @@ -761,7 +806,7 @@ return NULL; } -Static void +static void uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct mixerctl *mc) { int res; @@ -846,7 +891,7 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static char * +static char * uaudio_id_name(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { static char buf[32]; @@ -857,7 +902,7 @@ #endif #ifdef USB_DEBUG -Static void +static void uaudio_dump_cluster(const struct usb_audio_cluster *cl) { static const char *channel_names[16] = { @@ -869,21 +914,21 @@ int cc, i, first; cc = UGETW(cl->wChannelConfig); - logprintf("cluster: bNrChannels=%u wChannelConfig=0x%.4x", + printf("cluster: bNrChannels=%u wChannelConfig=0x%.4x", cl->bNrChannels, cc); first = TRUE; for (i = 0; cc != 0; i++) { if (cc & 1) { - logprintf("%c%s", first ? '<' : ',', channel_names[i]); + printf("%c%s", first ? '<' : ',', channel_names[i]); first = FALSE; } cc = cc >> 1; } - logprintf("> iChannelNames=%u", cl->iChannelNames); + printf("> iChannelNames=%u", cl->iChannelNames); } #endif -Static struct usb_audio_cluster +static struct usb_audio_cluster uaudio_get_cluster(int id, const struct io_terminal *iot) { struct usb_audio_cluster r; @@ -933,7 +978,7 @@ } -Static void +static void uaudio_add_input(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { #ifdef USB_DEBUG @@ -948,7 +993,7 @@ #endif } -Static void +static void uaudio_add_output(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { #ifdef USB_DEBUG @@ -962,7 +1007,7 @@ #endif } -Static void +static void uaudio_add_mixer(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { const struct usb_audio_mixer_unit *d = iot[id].d.mu; @@ -1031,7 +1076,7 @@ } -Static void +static void uaudio_add_selector(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { const struct usb_audio_selector_unit *d; @@ -1079,7 +1124,7 @@ } #ifdef USB_DEBUG -Static const char * +static const char * uaudio_get_terminal_name(int terminal_type) { static char buf[100]; @@ -1155,7 +1200,7 @@ } #endif -Static int +static int uaudio_determine_class(const struct io_terminal *iot, struct mixerctl *mix) { int terminal_type; @@ -1206,7 +1251,7 @@ } #if defined(__FreeBSD__) -const int +static int uaudio_feature_name(const struct io_terminal *iot, struct mixerctl *mix) { int terminal_type; @@ -1307,7 +1352,7 @@ return SOUND_MIXER_VOLUME; } #else -Static const char * +static const char * uaudio_feature_name(const struct io_terminal *iot, struct mixerctl *mix) { int terminal_type; @@ -1410,7 +1455,7 @@ } #endif -Static void +static void uaudio_add_feature(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { const struct usb_audio_feature_unit *d; @@ -1573,7 +1618,7 @@ } } -Static void +static void uaudio_add_processing_updown(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { @@ -1614,7 +1659,7 @@ uaudio_mixer_add_ctl(sc, &mix); } -Static void +static void uaudio_add_processing(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { const struct usb_audio_processing_unit *d; @@ -1661,7 +1706,7 @@ } } -Static void +static void uaudio_add_extension(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { const struct usb_audio_extension_unit *d; @@ -1692,7 +1737,7 @@ } } -Static struct terminal_list* +static struct terminal_list* uaudio_merge_terminal_list(const struct io_terminal *iot) { struct terminal_list *tml; @@ -1727,7 +1772,7 @@ return tml; } -Static struct terminal_list * +static struct terminal_list * uaudio_io_terminaltype(int outtype, struct io_terminal *iot, int id) { struct terminal_list *tml; @@ -1878,7 +1923,7 @@ } } -Static usbd_status +static usbd_status uaudio_identify(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) { usbd_status err; @@ -1889,7 +1934,7 @@ return uaudio_identify_as(sc, cdesc); } -Static void +static void uaudio_add_alt(struct uaudio_softc *sc, const struct as_info *ai) { size_t len; @@ -1912,7 +1957,7 @@ sc->sc_alts[sc->sc_nalts++] = *ai; } -Static usbd_status +static usbd_status uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, int size, const usb_interface_descriptor_t *id) #define offs (*offsp) @@ -1948,7 +1993,7 @@ if (asf1d->bFormatType != FORMAT_TYPE_I) { printf("%s: ignored setting with type %d format\n", - USBDEVNAME(sc->sc_dev), UGETW(asid->wFormatTag)); + device_get_nameunit(sc->sc_dev), UGETW(asid->wFormatTag)); return USBD_NORMAL_COMPLETION; } @@ -1979,7 +2024,7 @@ sync = TRUE; #ifndef UAUDIO_MULTIPLE_ENDPOINTS printf("%s: ignored input endpoint of type adaptive\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_NORMAL_COMPLETION; #endif } @@ -1987,7 +2032,7 @@ sync = TRUE; #ifndef UAUDIO_MULTIPLE_ENDPOINTS printf("%s: ignored output endpoint of type async\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_NORMAL_COMPLETION; #endif } @@ -2001,14 +2046,16 @@ if (offs > size) return USBD_INVAL; +#ifdef UAUDIO_MULTIPLE_ENDPOINTS if (sync && id->bNumEndpoints <= 1) { printf("%s: a sync-pipe endpoint but no other endpoint\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_INVAL; } +#endif if (!sync && id->bNumEndpoints > 1) { printf("%s: non sync-pipe endpoint but multiple endpoints\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_INVAL; } epdesc1 = NULL; @@ -2029,19 +2076,19 @@ return USBD_INVAL; if (epdesc1->bSynchAddress != 0) { printf("%s: invalid endpoint: bSynchAddress=0\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_INVAL; } if (UE_GET_XFERTYPE(epdesc1->bmAttributes) != UE_ISOCHRONOUS) { printf("%s: invalid endpoint: bmAttributes=0x%x\n", - USBDEVNAME(sc->sc_dev), epdesc1->bmAttributes); + device_get_nameunit(sc->sc_dev), epdesc1->bmAttributes); return USBD_INVAL; } if (epdesc1->bEndpointAddress != ed->bSynchAddress) { printf("%s: invalid endpoint addresses: " "ep[0]->bSynchAddress=0x%x " "ep[1]->bEndpointAddress=0x%x\n", - USBDEVNAME(sc->sc_dev), ed->bSynchAddress, + device_get_nameunit(sc->sc_dev), ed->bSynchAddress, epdesc1->bEndpointAddress); return USBD_INVAL; } @@ -2053,7 +2100,7 @@ prec = asf1d->bBitResolution; if (prec != 8 && prec != 16 && prec != 24 && prec != 32) { printf("%s: ignored setting with precision %d\n", - USBDEVNAME(sc->sc_dev), prec); + device_get_nameunit(sc->sc_dev), prec); return USBD_NORMAL_COMPLETION; } switch (format) { @@ -2088,11 +2135,11 @@ case UA_FMT_IEEE_FLOAT: default: printf("%s: ignored setting with format %d\n", - USBDEVNAME(sc->sc_dev), format); + device_get_nameunit(sc->sc_dev), format); return USBD_NORMAL_COMPLETION; } #ifdef USB_DEBUG - printf("%s: %s: %dch, %d/%dbit, %s,", USBDEVNAME(sc->sc_dev), + printf("%s: %s: %dch, %d/%dbit, %s,", device_get_nameunit(sc->sc_dev), dir == UE_DIR_IN ? "recording" : "playback", chan, prec, asf1d->bSubFrameSize * 8, format_str); if (asf1d->bSamFreqType == UA_SAMP_CONTNUOUS) { @@ -2135,6 +2182,7 @@ ai.edesc1 = epdesc1; ai.asf1desc = asf1d; ai.sc_busy = 0; + ai.ifaceh = NULL; uaudio_add_alt(sc, &ai); #ifdef USB_DEBUG if (ai.attributes & UA_SED_FREQ_CONTROL) @@ -2148,7 +2196,7 @@ } #undef offs -Static usbd_status +static usbd_status uaudio_identify_as(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) { @@ -2189,7 +2237,7 @@ default: printf("%s: ignored audio interface with %d " "endpoints\n", - USBDEVNAME(sc->sc_dev), id->bNumEndpoints); + device_get_nameunit(sc->sc_dev), id->bNumEndpoints); break; } id = uaudio_find_iface(buf, size, &offs,UISUBCLASS_AUDIOSTREAM); @@ -2205,14 +2253,14 @@ if (sc->sc_mode == 0) { printf("%s: no usable endpoint found\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_INVAL; } return USBD_NORMAL_COMPLETION; } -Static usbd_status +static usbd_status uaudio_identify_ac(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) { struct io_terminal* iot; @@ -2306,79 +2354,79 @@ if (iot[i].d.desc == NULL) continue; - logprintf("id %d:\t", i); + printf("id %d:\t", i); switch (iot[i].d.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: - logprintf("AC_INPUT type=%s\n", uaudio_get_terminal_name + printf("AC_INPUT type=%s\n", uaudio_get_terminal_name (UGETW(iot[i].d.it->wTerminalType))); - logprintf("\t"); + printf("\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; case UDESCSUB_AC_OUTPUT: - logprintf("AC_OUTPUT type=%s ", uaudio_get_terminal_name + printf("AC_OUTPUT type=%s ", uaudio_get_terminal_name (UGETW(iot[i].d.ot->wTerminalType))); - logprintf("src=%d\n", iot[i].d.ot->bSourceId); + printf("src=%d\n", iot[i].d.ot->bSourceId); break; case UDESCSUB_AC_MIXER: - logprintf("AC_MIXER src="); + printf("AC_MIXER src="); for (j = 0; j < iot[i].d.mu->bNrInPins; j++) - logprintf("%d ", iot[i].d.mu->baSourceId[j]); - logprintf("\n\t"); + printf("%d ", iot[i].d.mu->baSourceId[j]); + printf("\n\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; case UDESCSUB_AC_SELECTOR: - logprintf("AC_SELECTOR src="); + printf("AC_SELECTOR src="); for (j = 0; j < iot[i].d.su->bNrInPins; j++) - logprintf("%d ", iot[i].d.su->baSourceId[j]); - logprintf("\n"); + printf("%d ", iot[i].d.su->baSourceId[j]); + printf("\n"); break; case UDESCSUB_AC_FEATURE: - logprintf("AC_FEATURE src=%d\n", iot[i].d.fu->bSourceId); + printf("AC_FEATURE src=%d\n", iot[i].d.fu->bSourceId); break; case UDESCSUB_AC_PROCESSING: - logprintf("AC_PROCESSING src="); + printf("AC_PROCESSING src="); for (j = 0; j < iot[i].d.pu->bNrInPins; j++) - logprintf("%d ", iot[i].d.pu->baSourceId[j]); - logprintf("\n\t"); + printf("%d ", iot[i].d.pu->baSourceId[j]); + printf("\n\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; case UDESCSUB_AC_EXTENSION: - logprintf("AC_EXTENSION src="); + printf("AC_EXTENSION src="); for (j = 0; j < iot[i].d.eu->bNrInPins; j++) - logprintf("%d ", iot[i].d.eu->baSourceId[j]); - logprintf("\n\t"); + printf("%d ", iot[i].d.eu->baSourceId[j]); + printf("\n\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; default: - logprintf("unknown audio control (subtype=%d)\n", + printf("unknown audio control (subtype=%d)\n", iot[i].d.desc->bDescriptorSubtype); } for (j = 0; j < iot[i].inputs_size; j++) { int k; - logprintf("\tinput%d: ", j); + printf("\tinput%d: ", j); tml = iot[i].inputs[j]; if (tml == NULL) { - logprintf("NULL\n"); + printf("NULL\n"); continue; } for (k = 0; k < tml->size; k++) - logprintf("%s ", uaudio_get_terminal_name + printf("%s ", uaudio_get_terminal_name (tml->terminals[k])); - logprintf("\n"); + printf("\n"); } - logprintf("\toutput: "); + printf("\toutput: "); tml = iot[i].output; for (j = 0; j < tml->size; j++) - logprintf("%s ", uaudio_get_terminal_name(tml->terminals[j])); - logprintf("\n"); + printf("%s ", uaudio_get_terminal_name(tml->terminals[j])); + printf("\n"); } #endif @@ -2441,7 +2489,7 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_query_devinfo(void *addr, mixer_devinfo_t *mi) { struct uaudio_softc *sc; @@ -2517,7 +2565,7 @@ break; default: mi->type = AUDIO_MIXER_VALUE; - strncpy(mi->un.v.units.name, mc->ctlunit, MAX_AUDIO_DEV_LEN); + strlcpy(mi->un.v.units.name, mc->ctlunit, MAX_AUDIO_DEV_LEN); mi->un.v.num_channels = mc->nchan; mi->un.v.delta = mc->delta; break; @@ -2525,7 +2573,7 @@ return 0; } -Static int +static int uaudio_open(void *addr, int flags) { struct uaudio_softc *sc; @@ -2546,12 +2594,12 @@ /* * Close function is called at splaudio(). */ -Static void +static void uaudio_close(void *addr) { } -Static int +static int uaudio_drain(void *addr) { struct uaudio_softc *sc; @@ -2562,7 +2610,7 @@ return 0; } -Static int +static int uaudio_halt_out_dma(void *addr) { struct uaudio_softc *sc; @@ -2581,7 +2629,7 @@ return 0; } -Static int +static int uaudio_halt_in_dma(void *addr) { struct uaudio_softc *sc; @@ -2597,7 +2645,7 @@ return 0; } -Static int +static int uaudio_getdev(void *addr, struct audio_device *retp) { struct uaudio_softc *sc; @@ -2614,7 +2662,7 @@ /* * Make sure the block size is large enough to hold all outstanding transfers. */ -Static int +static int uaudio_round_blocksize(void *addr, int blk) { struct uaudio_softc *sc; @@ -2659,7 +2707,7 @@ return blk; } -Static int +static int uaudio_get_props(void *addr) { return AUDIO_PROP_FULLDUPLEX | AUDIO_PROP_INDEPENDENT; @@ -2667,7 +2715,7 @@ } #endif /* NetBSD or OpenBSD */ -Static int +static int uaudio_get(struct uaudio_softc *sc, int which, int type, int wValue, int wIndex, int len) { @@ -2692,7 +2740,12 @@ DPRINTFN(2,("uaudio_get: type=0x%02x req=0x%02x wValue=0x%04x " "wIndex=0x%04x len=%d\n", type, which, wValue, wIndex, len)); - err = usbd_do_request(sc->sc_udev, &req, data); +#if defined(__FreeBSD__) + if (sc->async != 0) + err = usbd_do_request_async(sc->sc_udev, &req, data); + else +#endif + err = usbd_do_request(sc->sc_udev, &req, data); if (err) { DPRINTF(("uaudio_get: err=%s\n", usbd_errstr(err))); return -1; @@ -2712,7 +2765,7 @@ return val; } -Static void +static void uaudio_set(struct uaudio_softc *sc, int which, int type, int wValue, int wIndex, int len, int val) { @@ -2747,14 +2800,19 @@ DPRINTFN(2,("uaudio_set: type=0x%02x req=0x%02x wValue=0x%04x " "wIndex=0x%04x len=%d, val=%d\n", type, which, wValue, wIndex, len, val & 0xffff)); - err = usbd_do_request(sc->sc_udev, &req, data); +#if defined(__FreeBSD__) + if (sc->async != 0) + err = usbd_do_request_async(sc->sc_udev, &req, data); + else +#endif + err = usbd_do_request(sc->sc_udev, &req, data); #ifdef USB_DEBUG if (err) DPRINTF(("uaudio_set: err=%d\n", err)); #endif } -Static int +static int uaudio_signext(int type, int val) { if (!MIX_UNSIGNED(type)) { @@ -2767,7 +2825,7 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_value2bsd(struct mixerctl *mc, int val) { DPRINTFN(5, ("uaudio_value2bsd: type=%03x val=%d min=%d max=%d ", @@ -2802,7 +2860,7 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_ctl_get(struct uaudio_softc *sc, int which, struct mixerctl *mc, int chan) { @@ -2815,7 +2873,7 @@ } #endif -Static void +static void uaudio_ctl_set(struct uaudio_softc *sc, int which, struct mixerctl *mc, int chan, int val) { @@ -2825,7 +2883,7 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_mixer_get_port(void *addr, mixer_ctrl_t *cp) { struct uaudio_softc *sc; @@ -2870,7 +2928,7 @@ return 0; } -Static int +static int uaudio_mixer_set_port(void *addr, mixer_ctrl_t *cp) { struct uaudio_softc *sc; @@ -2912,7 +2970,7 @@ return 0; } -Static int +static int uaudio_trigger_input(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) @@ -2955,7 +3013,7 @@ return 0; } -Static int +static int uaudio_trigger_output(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) @@ -3000,11 +3058,14 @@ #endif /* NetBSD or OpenBSD */ /* Set up a pipe for a channel. */ -Static usbd_status +static usbd_status uaudio_chan_open(struct uaudio_softc *sc, struct chan *ch) { struct as_info *as; int endpt; +#if defined(__FreeBSD__) + int locked; +#endif usbd_status err; #if defined(__FreeBSD__) @@ -3017,8 +3078,17 @@ DPRINTF(("uaudio_chan_open: endpt=0x%02x, speed=%d, alt=%d\n", endpt, ch->sample_rate, as->alt)); +#if defined(__FreeBSD__) + locked = (ch->pcm_ch != NULL && mtx_owned(ch->pcm_ch->lock)) ? 1 : 0; + if (locked) + CHN_UNLOCK(ch->pcm_ch); +#endif /* Set alternate interface corresponding to the mode. */ err = usbd_set_interface(as->ifaceh, as->alt); +#if defined(__FreeBSD__) + if (locked) + CHN_LOCK(ch->pcm_ch); +#endif if (err) return err; @@ -3029,9 +3099,10 @@ */ if (as->asf1desc->bSamFreqType != 1) { err = uaudio_set_speed(sc, endpt, ch->sample_rate); - if (err) + if (err) { DPRINTF(("uaudio_chan_open: set_speed failed err=%s\n", usbd_errstr(err))); + } } ch->pipe = 0; @@ -3048,22 +3119,32 @@ return err; } -Static void +static void uaudio_chan_close(struct uaudio_softc *sc, struct chan *ch) { struct as_info *as; - #if defined(__FreeBSD__) + int locked; + if (sc->sc_dying) return ; #endif as = &sc->sc_alts[ch->altidx]; as->sc_busy = 0; +#if defined(__FreeBSD__) + locked = (ch->pcm_ch != NULL && mtx_owned(ch->pcm_ch->lock)) ? 1 : 0; + if (locked) + CHN_UNLOCK(ch->pcm_ch); +#endif if (sc->sc_nullalt >= 0) { DPRINTF(("uaudio_chan_close: set null alt=%d\n", sc->sc_nullalt)); - usbd_set_interface(as->ifaceh, sc->sc_nullalt); + /* + * The interface will be initialized later again, so an + * error does not hurt. + */ + (void)usbd_set_interface(as->ifaceh, sc->sc_nullalt); } if (ch->pipe) { usbd_abort_pipe(ch->pipe); @@ -3073,9 +3154,13 @@ usbd_abort_pipe(ch->sync_pipe); usbd_close_pipe(ch->sync_pipe); } +#if defined(__FreeBSD__) + if (locked) + CHN_LOCK(ch->pcm_ch); +#endif } -Static usbd_status +static usbd_status uaudio_chan_alloc_buffers(struct uaudio_softc *sc, struct chan *ch) { usbd_xfer_handle xfer; @@ -3106,7 +3191,7 @@ return USBD_NOMEM; } -Static void +static void uaudio_chan_free_buffers(struct uaudio_softc *sc, struct chan *ch) { int i; @@ -3116,7 +3201,7 @@ } /* Called at splusb() */ -Static void +static void uaudio_chan_ptransfer(struct chan *ch) { struct chanbuf *cb; @@ -3181,7 +3266,7 @@ (void)usbd_transfer(cb->xfer); } -Static void +static void uaudio_chan_pintr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { @@ -3229,7 +3314,7 @@ } /* Called at splusb() */ -Static void +static void uaudio_chan_rtransfer(struct chan *ch) { struct chanbuf *cb; @@ -3274,7 +3359,7 @@ (void)usbd_transfer(cb->xfer); } -Static void +static void uaudio_chan_rintr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { @@ -3339,7 +3424,7 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static void +static void uaudio_chan_init(struct chan *ch, int altidx, const struct audio_params *param, int maxpktsize) { @@ -3360,7 +3445,7 @@ ch->residue = 0; } -Static void +static void uaudio_chan_set_param(struct chan *ch, u_char *start, u_char *end, int blksize) { ch->start = start; @@ -3371,7 +3456,7 @@ ch->curchanbuf = 0; } -Static void +static void uaudio_get_minmax_rates(int nalts, const struct as_info *alts, const struct audio_params *p, int mode, u_long *min, u_long *max) @@ -3413,7 +3498,7 @@ } } -Static int +static int uaudio_match_alt_sub(int nalts, const struct as_info *alts, const struct audio_params *p, int mode, u_long rate) { @@ -3452,7 +3537,7 @@ return -1; } -Static int +static int uaudio_match_alt_chan(int nalts, const struct as_info *alts, struct audio_params *p, int mode) { @@ -3496,7 +3581,7 @@ return -1; } -Static int +static int uaudio_match_alt(int nalts, const struct as_info *alts, struct audio_params *p, int mode) { @@ -3520,7 +3605,7 @@ return uaudio_match_alt_chan(nalts, alts, p, mode); } -Static int +static int uaudio_set_params(void *addr, int setmode, int usemode, struct audio_params *play, struct audio_params *rec) { @@ -3714,7 +3799,7 @@ } #endif /* NetBSD or OpenBSD */ -Static usbd_status +static usbd_status uaudio_set_speed(struct uaudio_softc *sc, int endpt, u_int speed) { usb_device_request_t req; @@ -3730,6 +3815,10 @@ data[1] = speed >> 8; data[2] = speed >> 16; +#if defined(__FreeBSD__) + if (sc->async != 0) + return usbd_do_request_async(sc->sc_udev, &req, data); +#endif return usbd_do_request(sc->sc_udev, &req, data); } @@ -4114,7 +4203,7 @@ bestspeed = UA_SAMP_LO(asf1d); hiscore = score; } - } else if (speed < UA_SAMP_HI(asf1d)) { + } else if (speed > UA_SAMP_HI(asf1d)) { score = 0xfff * UA_SAMP_HI(asf1d) / speed; if (score > hiscore) { bestspeed = UA_SAMP_HI(asf1d); @@ -4408,109 +4497,17 @@ } static int -uaudio_sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) +uaudio_sndstat_prepare_pcm(SNDSTAT_PREPARE_PCM_ARGS) { - struct snddev_info *d; - struct snddev_channel *sce; - struct pcm_channel *c; - struct pcm_feeder *f; - int pc, rc, vc; device_t pa_dev = device_get_parent(dev); struct uaudio_softc *sc = device_get_softc(pa_dev); - if (verbose < 1) - return 0; - - d = device_get_softc(dev); - if (!d) - return ENXIO; - - snd_mtxlock(d->lock); - if (SLIST_EMPTY(&d->channels)) { - sbuf_printf(s, " (mixer only)"); - snd_mtxunlock(d->lock); - return 0; - } - pc = rc = vc = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (c->direction == PCMDIR_PLAY) { - if (c->flags & CHN_F_VIRTUAL) - vc++; - else - pc++; - } else - rc++; - } - sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", - d->playcount, d->reccount, d->vchancount, - (d->flags & SD_F_SIMPLEX)? "" : " duplex", -#ifdef USING_DEVFS - (device_get_unit(dev) == snd_unit)? " default" : "" -#else - "" -#endif - ); + SNDSTAT_PREPARE_PCM_BEGIN(); - if (sc->uaudio_sndstat_flag != 0) { + if (sc->uaudio_sndstat_flag != 0) sbuf_cat(s, sbuf_data(&(sc->uaudio_sndstat))); - } - - if (verbose <= 1) { - snd_mtxunlock(d->lock); - return 0; - } - - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - sbuf_printf(s, "\n\t"); - - KASSERT(c->bufhard != NULL && c->bufsoft != NULL, - ("hosed pcm channel setup")); - - /* it would be better to indent child channels */ - sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name); - sbuf_printf(s, "spd %d", c->speed); - if (c->speed != sndbuf_getspd(c->bufhard)) - sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard)); - sbuf_printf(s, ", fmt 0x%08x", c->format); - if (c->format != sndbuf_getfmt(c->bufhard)) - sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard)); - sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags); - if (c->pid != -1) - sbuf_printf(s, ", pid %d", c->pid); - sbuf_printf(s, "\n\t"); - - sbuf_printf(s, "interrupts %d, ", c->interrupts); - if (c->direction == PCMDIR_REC) - sbuf_printf(s, "overruns %d, hfree %d, sfree %d", - c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft)); - else - sbuf_printf(s, "underruns %d, ready %d", - c->xruns, sndbuf_getready(c->bufsoft)); - sbuf_printf(s, "\n\t"); - - sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland"); - sbuf_printf(s, " -> "); - f = c->feeder; - while (f->source != NULL) - f = f->source; - while (f != NULL) { - sbuf_printf(s, "%s", f->class->name); - if (f->desc->type == FEEDER_FMT) - sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); - if (f->desc->type == FEEDER_RATE) - sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); - if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER) - sbuf_printf(s, "(0x%08x)", f->desc->out); - sbuf_printf(s, " -> "); - f = f->parent; - } - sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware"); - } - snd_mtxunlock(d->lock); - return 0; + SNDSTAT_PREPARE_PCM_END(); } void @@ -4519,8 +4516,41 @@ struct snddev_info *d = device_get_softc(dev); sndstat_register(dev, d->status, uaudio_sndstat_prepare_pcm); } - -Static int + +int +uaudio_get_vendor(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_vendor; +} + +int +uaudio_get_product(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_product; +} + +int +uaudio_get_release(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_release; +} + +static int audio_attach_mi(device_t dev) { device_t child; @@ -4528,10 +4558,9 @@ /* Attach the children. */ /* PCM Audio */ - func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) return (ENOMEM); - bzero(func, sizeof(*func)); func->func = SCF_PCM; child = device_add_child(dev, "pcm", -1); device_set_ivars(child, func); --- sys/dev/sound/usb/uaudio.h.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/usb/uaudio.h Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -/* $FreeBSD: src/sys/dev/sound/usb/uaudio.h,v 1.6.2.1 2005/12/30 19:55:54 netchild Exp $ */ +/* $FreeBSD: src/sys/dev/sound/usb/uaudio.h,v 1.8 2007/03/16 17:19:03 ariff Exp $ */ /*- * Copyright (c) 2000-2002 Hiroyuki Aizu @@ -51,3 +51,6 @@ u_int32_t uaudio_query_recsrc_info(device_t dev); unsigned uaudio_query_formats(device_t dev, int dir, unsigned maxfmt, struct pcmchan_caps *fmt); void uaudio_sndstat_register(device_t dev); +int uaudio_get_vendor(device_t dev); +int uaudio_get_product(device_t dev); +int uaudio_get_release(device_t dev); --- sys/dev/sound/usb/uaudio_pcm.c.orig Tue Jul 10 04:51:37 2007 +++ sys/dev/sound/usb/uaudio_pcm.c Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -/* $FreeBSD: src/sys/dev/sound/usb/uaudio_pcm.c,v 1.15.2.2 2007/05/13 20:53:40 ariff Exp $ */ +/* $FreeBSD: src/sys/dev/sound/usb/uaudio_pcm.c,v 1.24 2007/06/17 06:10:43 ariff Exp $ */ /*- * Copyright (c) 2000-2002 Hiroyuki Aizu @@ -54,10 +54,20 @@ u_int32_t ua_recfmt[FORMAT_NUM*2+1]; /* FORMAT_NUM format * (stereo or mono) + endptr */ struct pcmchan_caps ua_playcaps; struct pcmchan_caps ua_reccaps; + int vendor, product, release; }; #define UAUDIO_DEFAULT_BUFSZ 16*1024 +static const struct { + int vendor; + int product; + int release; + uint32_t dflags; +} ua_quirks[] = { + { 0x1130, 0xf211, 0x0101, SD_F_PSWAPLR }, +}; + /************************************************************/ static void * ua_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) @@ -144,20 +154,47 @@ } static int -ua_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +ua_chan_setfragments(kobj_t obj, void *data, u_int32_t blksz, u_int32_t blkcnt) { device_t pa_dev; struct ua_chinfo *ch = data; struct ua_info *ua = ch->parent; - if (blocksize) { - RANGE(blocksize, 128, ua->bufsz / 2); - if (sndbuf_resize(ch->buffer, ua->bufsz/blocksize, blocksize) != 0) { - ch->blksz = blocksize; - } + RANGE(blksz, 128, sndbuf_getmaxsize(ch->buffer) / 2); + RANGE(blkcnt, 2, 512); + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= 2) + blkcnt >>= 1; + else if ((blksz >> 1) >= 128) + blksz >>= 1; + else + break; } + + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(ua->sc_dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + ch->blksz = sndbuf_getblksz(ch->buffer); + pa_dev = device_get_parent(ua->sc_dev); - uaudio_chan_set_param_blocksize(pa_dev, blocksize, ch->dir); + uaudio_chan_set_param_pcm_dma_buff(pa_dev, ch->buf, + ch->buf + sndbuf_getsize(ch->buffer), ch->channel, ch->dir); + uaudio_chan_set_param_blocksize(pa_dev, ch->blksz, ch->dir); + + return 1; +} + +static int +ua_chan_setblocksize(kobj_t obj, void *data, u_int32_t blksz) +{ + struct ua_chinfo *ch = data; + + ua_chan_setfragments(obj, data, blksz, + sndbuf_getmaxsize(ch->buffer) / blksz); return ch->blksz; } @@ -169,7 +206,7 @@ struct ua_info *ua; struct ua_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ua = ch->parent; @@ -222,6 +259,7 @@ KOBJMETHOD(channel_setformat, ua_chan_setformat), KOBJMETHOD(channel_setspeed, ua_chan_setspeed), KOBJMETHOD(channel_setblocksize, ua_chan_setblocksize), + KOBJMETHOD(channel_setfragments, ua_chan_setfragments), KOBJMETHOD(channel_trigger, ua_chan_trigger), KOBJMETHOD(channel_getptr, ua_chan_getptr), KOBJMETHOD(channel_getcaps, ua_chan_getcaps), @@ -236,27 +274,22 @@ { u_int32_t mask; device_t pa_dev; - struct snddev_info *d; struct ua_info *ua = mix_getdevinfo(m); pa_dev = device_get_parent(ua->sc_dev); - d = device_get_softc(ua->sc_dev); mask = uaudio_query_mix_info(pa_dev); - if (d != NULL) { - if (!(mask & SOUND_MASK_PCM)) { - /* - * Emulate missing pcm mixer controller - * through FEEDER_VOLUME - */ - d->flags |= SD_F_SOFTPCMVOL; - } - if (!(mask & SOUND_MASK_VOLUME)) { - mix_setparentchild(m, SOUND_MIXER_VOLUME, - SOUND_MASK_PCM); - mix_setrealdev(m, SOUND_MIXER_VOLUME, - SOUND_MIXER_NONE); - } + if (!(mask & SOUND_MASK_PCM)) { + /* + * Emulate missing pcm mixer controller + * through FEEDER_VOLUME + */ + pcm_setflags(ua->sc_dev, pcm_getflags(ua->sc_dev) | + SD_F_SOFTPCMVOL); + } + if (!(mask & SOUND_MASK_VOLUME)) { + mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); + mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); } mix_setdevs(m, mask); @@ -321,18 +354,30 @@ ua_attach(device_t dev) { struct ua_info *ua; + struct sndcard_func *func; char status[SND_STATUSLEN]; device_t pa_dev; - u_int32_t nplay, nrec; + u_int32_t nplay, nrec, flags; int i; - ua = (struct ua_info *)malloc(sizeof *ua, M_DEVBUF, M_ZERO | M_NOWAIT); - if (ua == NULL) - return ENXIO; - + ua = malloc(sizeof(*ua), M_DEVBUF, M_WAITOK | M_ZERO); ua->sc_dev = dev; + /* Mark for existence */ + func = device_get_ivars(dev); + if (func != NULL) + func->varinfo = (void *)ua; + pa_dev = device_get_parent(dev); + ua->vendor = uaudio_get_vendor(pa_dev); + ua->product = uaudio_get_product(pa_dev); + ua->release = uaudio_get_release(pa_dev); + + if (bootverbose) + device_printf(dev, + "USB Audio: " + "vendor=0x%04x, product=0x%04x, release=0x%04x\n", + ua->vendor, ua->product, ua->release); ua->bufsz = pcm_getbuffersize(dev, 4096, UAUDIO_DEFAULT_BUFSZ, 65536); if (bootverbose) @@ -354,6 +399,15 @@ if (nrec > 1) nrec = 1; + flags = pcm_getflags(dev); + for (i = 0; i < (sizeof(ua_quirks) / sizeof(ua_quirks[0])); i++) { + if (ua->vendor == ua_quirks[i].vendor && + ua->product == ua_quirks[i].product && + ua->release == ua_quirks[i].release) + flags |= ua_quirks[i].dflags; + } + pcm_setflags(dev, flags); + #ifndef NO_RECORDING if (pcm_register(dev, ua, nplay, nrec)) { #else @@ -384,8 +438,9 @@ static int ua_detach(device_t dev) { - int r; struct ua_info *sc; + struct sndcard_func *func; + int r; r = pcm_unregister(dev); if (r) @@ -393,6 +448,11 @@ sc = pcm_getdevinfo(dev); free(sc, M_DEVBUF); + + /* Mark for deletion */ + func = device_get_ivars(dev); + if (func != NULL) + func->varinfo = NULL; return 0; } --- sys/dev/sound/version.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/version.h Thu Jul 12 14:46:27 2007 @@ -0,0 +1,42 @@ +/*- + * Copyright (c) 2007 Ariff Abdullah + * 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 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 AUTHOR 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. + * + * $FreeBSD: src/sys/dev/sound/version.h,v 1.2 2007/06/16 03:37:27 ariff Exp $ + */ + +#ifndef _SND_VERSION_H_ +#define _SND_VERSION_H_ + +/* + * FreeBSD sound driver internal versioning, nothing to do + * with OSS whatsoever. Dear future maintainer, please revisit + * this _before_ Jan 1 2148 + * + * Last 2 decimal places reserved for daily versioning, starting + * with 0. + */ +#define SND_DRV_VERSION 2007071200 + +#endif /* !_SND_VERSION_H_ */ --- sys/gnu/dev/sound/pci/emu10k1-ac97.h.orig Tue Jul 10 04:51:37 2007 +++ sys/gnu/dev/sound/pci/emu10k1-ac97.h Thu Jan 1 07:30:00 1970 @@ -1,293 +0,0 @@ -/* $FreeBSD: src/sys/gnu/dev/sound/pci/emu10k1-ac97.h,v 1.2 2004/01/09 05:08:32 obrien Exp $ */ - -#ifndef _AC97_CODEC_H_ -#define _AC97_CODEC_H_ - -#ifdef __linux__ -#include -#include -#endif - -/* AC97 1.0 */ -#define AC97_RESET 0x0000 // -#define AC97_MASTER_VOL_STEREO 0x0002 // Line Out -#define AC97_HEADPHONE_VOL 0x0004 // -#define AC97_MASTER_VOL_MONO 0x0006 // TAD Output -#define AC97_MASTER_TONE 0x0008 // -#define AC97_PCBEEP_VOL 0x000a // none -#define AC97_PHONE_VOL 0x000c // TAD Input (mono) -#define AC97_MIC_VOL 0x000e // MIC Input (mono) -#define AC97_LINEIN_VOL 0x0010 // Line Input (stereo) -#define AC97_CD_VOL 0x0012 // CD Input (stereo) -#define AC97_VIDEO_VOL 0x0014 // none -#define AC97_AUX_VOL 0x0016 // Aux Input (stereo) -#define AC97_PCMOUT_VOL 0x0018 // Wave Output (stereo) -#define AC97_RECORD_SELECT 0x001a // -#define AC97_RECORD_GAIN 0x001c -#define AC97_RECORD_GAIN_MIC 0x001e -#define AC97_GENERAL_PURPOSE 0x0020 -#define AC97_3D_CONTROL 0x0022 -#define AC97_MODEM_RATE 0x0024 -#define AC97_POWER_CONTROL 0x0026 - -/* AC'97 2.0 */ -#define AC97_EXTENDED_ID 0x0028 /* Extended Audio ID */ -#define AC97_EXTENDED_STATUS 0x002A /* Extended Audio Status */ -#define AC97_PCM_FRONT_DAC_RATE 0x002C /* PCM Front DAC Rate */ -#define AC97_PCM_SURR_DAC_RATE 0x002E /* PCM Surround DAC Rate */ -#define AC97_PCM_LFE_DAC_RATE 0x0030 /* PCM LFE DAC Rate */ -#define AC97_PCM_LR_ADC_RATE 0x0032 /* PCM LR ADC Rate */ -#define AC97_PCM_MIC_ADC_RATE 0x0034 /* PCM MIC ADC Rate */ -#define AC97_CENTER_LFE_MASTER 0x0036 /* Center + LFE Master Volume */ -#define AC97_SURROUND_MASTER 0x0038 /* Surround (Rear) Master Volume */ -#define AC97_RESERVED_3A 0x003A /* Reserved in AC '97 < 2.2 */ - -/* AC'97 2.2 */ -#define AC97_SPDIF_CONTROL 0x003A /* S/PDIF Control */ - -/* range 0x3c-0x58 - MODEM */ -#define AC97_EXTENDED_MODEM_ID 0x003C -#define AC97_EXTEND_MODEM_STAT 0x003E -#define AC97_LINE1_RATE 0x0040 -#define AC97_LINE2_RATE 0x0042 -#define AC97_HANDSET_RATE 0x0044 -#define AC97_LINE1_LEVEL 0x0046 -#define AC97_LINE2_LEVEL 0x0048 -#define AC97_HANDSET_LEVEL 0x004A -#define AC97_GPIO_CONFIG 0x004C -#define AC97_GPIO_POLARITY 0x004E -#define AC97_GPIO_STICKY 0x0050 -#define AC97_GPIO_WAKE_UP 0x0052 -#define AC97_GPIO_STATUS 0x0054 -#define AC97_MISC_MODEM_STAT 0x0056 -#define AC97_RESERVED_58 0x0058 - -/* registers 0x005a - 0x007a are vendor reserved */ - -#define AC97_VENDOR_ID1 0x007c -#define AC97_VENDOR_ID2 0x007e - -/* volume control bit defines */ -#ifndef AC97_MUTE -#define AC97_MUTE 0x8000 -#endif -#define AC97_MICBOOST 0x0040 -#define AC97_LEFTVOL 0x3f00 -#define AC97_RIGHTVOL 0x003f - -/* record mux defines */ -#define AC97_RECMUX_MIC 0x0000 -#define AC97_RECMUX_CD 0x0101 -#define AC97_RECMUX_VIDEO 0x0202 -#define AC97_RECMUX_AUX 0x0303 -#define AC97_RECMUX_LINE 0x0404 -#define AC97_RECMUX_STEREO_MIX 0x0505 -#define AC97_RECMUX_MONO_MIX 0x0606 -#define AC97_RECMUX_PHONE 0x0707 - -/* general purpose register bit defines */ -#define AC97_GP_LPBK 0x0080 /* Loopback mode */ -#define AC97_GP_MS 0x0100 /* Mic Select 0=Mic1, 1=Mic2 */ -#define AC97_GP_MIX 0x0200 /* Mono output select 0=Mix, 1=Mic */ -#define AC97_GP_RLBK 0x0400 /* Remote Loopback - Modem line codec */ -#define AC97_GP_LLBK 0x0800 /* Local Loopback - Modem Line codec */ -#define AC97_GP_LD 0x1000 /* Loudness 1=on */ -#define AC97_GP_3D 0x2000 /* 3D Enhancement 1=on */ -#define AC97_GP_ST 0x4000 /* Stereo Enhancement 1=on */ -#define AC97_GP_POP 0x8000 /* Pcm Out Path, 0=pre 3D, 1=post 3D */ - -/* extended audio status and control bit defines */ -#define AC97_EA_VRA 0x0001 /* Variable bit rate enable bit */ -#define AC97_EA_DRA 0x0002 /* Double-rate audio enable bit */ -#define AC97_EA_SPDIF 0x0004 /* S/PDIF Enable bit */ -#define AC97_EA_VRM 0x0008 /* Variable bit rate for MIC enable bit */ -#define AC97_EA_CDAC 0x0040 /* PCM Center DAC is ready (Read only) */ -#define AC97_EA_SDAC 0x0040 /* PCM Surround DACs are ready (Read only) */ -#define AC97_EA_LDAC 0x0080 /* PCM LFE DAC is ready (Read only) */ -#define AC97_EA_MDAC 0x0100 /* MIC ADC is ready (Read only) */ -#define AC97_EA_SPCV 0x0400 /* S/PDIF configuration valid (Read only) */ -#define AC97_EA_PRI 0x0800 /* Turns the PCM Center DAC off */ -#define AC97_EA_PRJ 0x1000 /* Turns the PCM Surround DACs off */ -#define AC97_EA_PRK 0x2000 /* Turns the PCM LFE DAC off */ -#define AC97_EA_PRL 0x4000 /* Turns the MIC ADC off */ -#define AC97_EA_SLOT_MASK 0xffcf /* Mask for slot assignment bits */ -#define AC97_EA_SPSA_3_4 0x0000 /* Slot assigned to 3 & 4 */ -#define AC97_EA_SPSA_7_8 0x0010 /* Slot assigned to 7 & 8 */ -#define AC97_EA_SPSA_6_9 0x0020 /* Slot assigned to 6 & 9 */ -#define AC97_EA_SPSA_10_11 0x0030 /* Slot assigned to 10 & 11 */ - -/* S/PDIF control bit defines */ -#define AC97_SC_PRO 0x0001 /* Professional status */ -#define AC97_SC_NAUDIO 0x0002 /* Non audio stream */ -#define AC97_SC_COPY 0x0004 /* Copyright status */ -#define AC97_SC_PRE 0x0008 /* Preemphasis status */ -#define AC97_SC_CC_MASK 0x07f0 /* Category Code mask */ -#define AC97_SC_L 0x0800 /* Generation Level status */ -#define AC97_SC_SPSR_MASK 0xcfff /* S/PDIF Sample Rate bits */ -#define AC97_SC_SPSR_44K 0x0000 /* Use 44.1kHz Sample rate */ -#define AC97_SC_SPSR_48K 0x2000 /* Use 48kHz Sample rate */ -#define AC97_SC_SPSR_32K 0x3000 /* Use 32kHz Sample rate */ -#define AC97_SC_DRS 0x4000 /* Double Rate S/PDIF */ -#define AC97_SC_V 0x8000 /* Validity status */ - -/* powerdown control and status bit defines */ - -/* status */ -#define AC97_PWR_MDM 0x0010 /* Modem section ready */ -#define AC97_PWR_REF 0x0008 /* Vref nominal */ -#define AC97_PWR_ANL 0x0004 /* Analog section ready */ -#define AC97_PWR_DAC 0x0002 /* DAC section ready */ -#define AC97_PWR_ADC 0x0001 /* ADC section ready */ - -/* control */ -#define AC97_PWR_PR0 0x0100 /* ADC and Mux powerdown */ -#define AC97_PWR_PR1 0x0200 /* DAC powerdown */ -#define AC97_PWR_PR2 0x0400 /* Output mixer powerdown (Vref on) */ -#define AC97_PWR_PR3 0x0800 /* Output mixer powerdown (Vref off) */ -#define AC97_PWR_PR4 0x1000 /* AC-link powerdown */ -#define AC97_PWR_PR5 0x2000 /* Internal Clk disable */ -#define AC97_PWR_PR6 0x4000 /* HP amp powerdown */ -#define AC97_PWR_PR7 0x8000 /* Modem off - if supported */ - -/* extended audio ID register bit defines */ -#define AC97_EXTID_VRA 0x0001 -#define AC97_EXTID_DRA 0x0002 -#define AC97_EXTID_SPDIF 0x0004 -#define AC97_EXTID_VRM 0x0008 -#define AC97_EXTID_DSA0 0x0010 -#define AC97_EXTID_DSA1 0x0020 -#define AC97_EXTID_CDAC 0x0040 -#define AC97_EXTID_SDAC 0x0080 -#define AC97_EXTID_LDAC 0x0100 -#define AC97_EXTID_AMAP 0x0200 -#define AC97_EXTID_REV0 0x0400 -#define AC97_EXTID_REV1 0x0800 -#define AC97_EXTID_ID0 0x4000 -#define AC97_EXTID_ID1 0x8000 - -/* extended status register bit defines */ -#define AC97_EXTSTAT_VRA 0x0001 -#define AC97_EXTSTAT_DRA 0x0002 -#define AC97_EXTSTAT_SPDIF 0x0004 -#define AC97_EXTSTAT_VRM 0x0008 -#define AC97_EXTSTAT_SPSA0 0x0010 -#define AC97_EXTSTAT_SPSA1 0x0020 -#define AC97_EXTSTAT_CDAC 0x0040 -#define AC97_EXTSTAT_SDAC 0x0080 -#define AC97_EXTSTAT_LDAC 0x0100 -#define AC97_EXTSTAT_MADC 0x0200 -#define AC97_EXTSTAT_SPCV 0x0400 -#define AC97_EXTSTAT_PRI 0x0800 -#define AC97_EXTSTAT_PRJ 0x1000 -#define AC97_EXTSTAT_PRK 0x2000 -#define AC97_EXTSTAT_PRL 0x4000 - -/* useful power states */ -#define AC97_PWR_D0 0x0000 /* everything on */ -#define AC97_PWR_D1 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR4 -#define AC97_PWR_D2 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR2|AC97_PWR_PR3|AC97_PWR_PR4 -#define AC97_PWR_D3 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR2|AC97_PWR_PR3|AC97_PWR_PR4 -#define AC97_PWR_ANLOFF AC97_PWR_PR2|AC97_PWR_PR3 /* analog section off */ - -/* Total number of defined registers. */ -#define AC97_REG_CNT 64 - - -/* OSS interface to the ac97s.. */ -#define AC97_STEREO_MASK (SOUND_MASK_VOLUME|SOUND_MASK_PCM|\ - SOUND_MASK_LINE|SOUND_MASK_CD|\ - SOUND_MASK_ALTPCM|SOUND_MASK_IGAIN|\ - SOUND_MASK_LINE1|SOUND_MASK_VIDEO) - -#define AC97_SUPPORTED_MASK (AC97_STEREO_MASK | \ - SOUND_MASK_BASS|SOUND_MASK_TREBLE|\ - SOUND_MASK_SPEAKER|SOUND_MASK_MIC|\ - SOUND_MASK_PHONEIN|SOUND_MASK_PHONEOUT) - -#define AC97_RECORD_MASK (SOUND_MASK_MIC|\ - SOUND_MASK_CD|SOUND_MASK_IGAIN|SOUND_MASK_VIDEO|\ - SOUND_MASK_LINE1| SOUND_MASK_LINE|\ - SOUND_MASK_PHONEIN) - -/* original check is not good enough in case FOO is greater than - * SOUND_MIXER_NRDEVICES because the supported_mixers has exactly - * SOUND_MIXER_NRDEVICES elements. - * before matching the given mixer against the bitmask in supported_mixers we - * check if mixer number exceeds maximum allowed size which is as mentioned - * above SOUND_MIXER_NRDEVICES */ -#define supported_mixer(CODEC,FOO) ((FOO >= 0) && \ - (FOO < SOUND_MIXER_NRDEVICES) && \ - (CODEC)->supported_mixers & (1< #include #include +#include +#include #include +#include #include -#ifndef PCI_VENDOR_ID_CREATIVE -#define PCI_VENDOR_ID_CREATIVE 0x1102 -#endif -#ifndef PCI_DEVICE_ID_CREATIVE_EMU10K1 -#define PCI_DEVICE_ID_CREATIVE_EMU10K1 0x0002 -#endif - /* ------------------- DEFINES -------------------- */ #define EMUPAGESIZE 4096 @@ -51,9 +47,12 @@ #define NUM_MIDI 16 #define NUM_G 64 /* use all channels */ #define NUM_FXSENDS 4 +#define NUM_EFX_PLAYBACK 16 +/* FIXME? - according to the OSS driver the EMU10K1 needs a 29 bit DMA mask */ #define EMU10K1_DMA_MASK 0x7fffffffUL /* 31bit */ -#define AUDIGY_DMA_MASK 0xffffffffUL /* 32bit */ +#define AUDIGY_DMA_MASK 0x7fffffffUL /* 31bit FIXME - 32 should work? */ + /* See ALSA bug #1276 - rlrevell */ #define TMEMSIZE 256*1024 #define TMEMSIZEREG 4 @@ -81,11 +80,18 @@ #define IPR 0x08 /* Global interrupt pending register */ /* Clear pending interrupts by writing a 1 to */ /* the relevant bits and zero to the other bits */ +#define IPR_P16V 0x80000000 /* Bit set when the CA0151 P16V chip wishes + to interrupt */ +#define IPR_GPIOMSG 0x20000000 /* GPIO message interrupt (RE'd, still not sure + which INTE bits enable it) */ /* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ #define IPR_A_MIDITRANSBUFEMPTY2 0x10000000 /* MIDI UART transmit buffer empty */ #define IPR_A_MIDIRECVBUFEMPTY2 0x08000000 /* MIDI UART receive buffer empty */ +#define IPR_SPDIFBUFFULL 0x04000000 /* SPDIF capture related, 10k2 only? (RE) */ +#define IPR_SPDIFBUFHALFFULL 0x02000000 /* SPDIF capture related? (RE) */ + #define IPR_SAMPLERATETRACKER 0x01000000 /* Sample rate tracker lock status change */ #define IPR_FXDSP 0x00800000 /* Enable FX DSP interrupts */ #define IPR_FORCEINT 0x00400000 /* Force Sound Blaster interrupt */ @@ -104,12 +110,12 @@ #define IPR_INTERVALTIMER 0x00000200 /* Interval timer terminal count */ #define IPR_MIDITRANSBUFEMPTY 0x00000100 /* MIDI UART transmit buffer empty */ #define IPR_MIDIRECVBUFEMPTY 0x00000080 /* MIDI UART receive buffer empty */ -#define IPR_CHANNELLOOP 0x00000040 /* One or more channel loop interrupts pending */ +#define IPR_CHANNELLOOP 0x00000040 /* Channel (half) loop interrupt(s) pending */ #define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */ - /* Highest set channel in CLIPL or CLIPH. When */ - /* IP is written with CL set, the bit in CLIPL */ - /* or CLIPH corresponding to the CIN value */ - /* written will be cleared. */ + /* highest set channel in CLIPL, CLIPH, HLIPL, */ + /* or HLIPH. When IP is written with CL set, */ + /* the bit in H/CLIPL or H/CLIPH corresponding */ + /* to the CIN value written will be cleared. */ #define INTE 0x0c /* Interrupt enable register */ #define INTE_VIRTUALSB_MASK 0xc0000000 /* Virtual Soundblaster I/O port capture */ @@ -236,9 +242,28 @@ #define A_IOCFG 0x18 /* GPIO on Audigy card (16bits) */ #define A_GPINPUT_MASK 0xff00 #define A_GPOUTPUT_MASK 0x00ff -#define A_IOCFG_GPOUT0 0x0044 /* analog/digital? */ -#define A_IOCFG_GPOUT1 0x0002 /* IR */ + +// Audigy output/GPIO stuff taken from the kX drivers +#define A_IOCFG_GPOUT0 0x0044 /* analog/digital */ +#define A_IOCFG_DISABLE_ANALOG 0x0040 /* = 'enable' for Audigy2 (chiprev=4) */ +#define A_IOCFG_ENABLE_DIGITAL 0x0004 +#define A_IOCFG_ENABLE_DIGITAL_AUDIGY4 0x0080 +#define A_IOCFG_UNKNOWN_20 0x0020 +#define A_IOCFG_DISABLE_AC97_FRONT 0x0080 /* turn off ac97 front -> front (10k2.1) */ +#define A_IOCFG_GPOUT1 0x0002 /* IR? drive's internal bypass (?) */ #define A_IOCFG_GPOUT2 0x0001 /* IR */ +#define A_IOCFG_MULTIPURPOSE_JACK 0x2000 /* center+lfe+rear_center (a2/a2ex) */ + /* + digital for generic 10k2 */ +#define A_IOCFG_DIGITAL_JACK 0x1000 /* digital for a2 platinum */ +#define A_IOCFG_FRONT_JACK 0x4000 +#define A_IOCFG_REAR_JACK 0x8000 +#define A_IOCFG_PHONES_JACK 0x0100 /* LiveDrive */ + +/* outputs: + * for audigy2 platinum: 0xa00 + * for a2 platinum ex: 0x1c00 + * for a1 platinum: 0x0 + */ #define TIMER 0x1a /* Timer terminal count register */ /* NOTE: After the rate is changed, a maximum */ @@ -254,6 +279,46 @@ #define AC97ADDRESS_READY 0x80 /* Read-only bit, reflects CODEC READY signal */ #define AC97ADDRESS_ADDRESS 0x7f /* Address of indexed AC97 register */ +/* Available on the Audigy 2 and Audigy 4 only. This is the P16V chip. */ +#define PTR2 0x20 /* Indexed register set pointer register */ +#define DATA2 0x24 /* Indexed register set data register */ +#define IPR2 0x28 /* P16V interrupt pending register */ +#define IPR2_PLAYBACK_CH_0_LOOP 0x00001000 /* Playback Channel 0 loop */ +#define IPR2_PLAYBACK_CH_0_HALF_LOOP 0x00000100 /* Playback Channel 0 half loop */ +#define IPR2_CAPTURE_CH_0_LOOP 0x00100000 /* Capture Channel 0 loop */ +#define IPR2_CAPTURE_CH_0_HALF_LOOP 0x00010000 /* Capture Channel 0 half loop */ + /* 0x00000100 Playback. Only in once per period. + * 0x00110000 Capture. Int on half buffer. + */ +#define INTE2 0x2c /* P16V Interrupt enable register. */ +#define INTE2_PLAYBACK_CH_0_LOOP 0x00001000 /* Playback Channel 0 loop */ +#define INTE2_PLAYBACK_CH_0_HALF_LOOP 0x00000100 /* Playback Channel 0 half loop */ +#define INTE2_PLAYBACK_CH_1_LOOP 0x00002000 /* Playback Channel 1 loop */ +#define INTE2_PLAYBACK_CH_1_HALF_LOOP 0x00000200 /* Playback Channel 1 half loop */ +#define INTE2_PLAYBACK_CH_2_LOOP 0x00004000 /* Playback Channel 2 loop */ +#define INTE2_PLAYBACK_CH_2_HALF_LOOP 0x00000400 /* Playback Channel 2 half loop */ +#define INTE2_PLAYBACK_CH_3_LOOP 0x00008000 /* Playback Channel 3 loop */ +#define INTE2_PLAYBACK_CH_3_HALF_LOOP 0x00000800 /* Playback Channel 3 half loop */ +#define INTE2_CAPTURE_CH_0_LOOP 0x00100000 /* Capture Channel 0 loop */ +#define INTE2_CAPTURE_CH_0_HALF_LOOP 0x00010000 /* Caputre Channel 0 half loop */ +#define HCFG2 0x34 /* Defaults: 0, win2000 sets it to 00004201 */ + /* 0x00000000 2-channel output. */ + /* 0x00000200 8-channel output. */ + /* 0x00000004 pauses stream/irq fail. */ + /* Rest of bits no nothing to sound output */ + /* bit 0: Enable P16V audio. + * bit 1: Lock P16V record memory cache. + * bit 2: Lock P16V playback memory cache. + * bit 3: Dummy record insert zero samples. + * bit 8: Record 8-channel in phase. + * bit 9: Playback 8-channel in phase. + * bit 11-12: Playback mixer attenuation: 0=0dB, 1=-6dB, 2=-12dB, 3=Mute. + * bit 13: Playback mixer enable. + * bit 14: Route SRC48 mixer output to fx engine. + * bit 15: Enable IEEE 1394 chip. + */ +#define IPR3 0x38 /* Cdif interrupt pending register */ +#define INTE3 0x3c /* Cdif interrupt enable register. */ /************************************************************************************************/ /* PCI function 1 registers, address = + PCIBASE1 */ /************************************************************************************************/ @@ -464,6 +529,8 @@ /* NOTE: All channels contain internal variables; do */ /* not write to these locations. */ +/* 1f something */ + #define CD0 0x20 /* Cache data 0 register */ #define CD1 0x21 /* Cache data 1 register */ #define CD2 0x22 /* Cache data 2 register */ @@ -481,6 +548,8 @@ #define CDE 0x2e /* Cache data E register */ #define CDF 0x2f /* Cache data F register */ +/* 0x30-3f seem to be the same as 0x20-2f */ + #define PTB 0x40 /* Page table base register */ #define PTB_MASK 0xfffff000 /* Physical address of the page table in host memory */ @@ -511,7 +580,11 @@ #define FXWC 0x43 /* FX output write channels register */ /* When set, each bit enables the writing of the */ - /* corresponding FX output channel into host memory */ + /* corresponding FX output channel (internal registers */ + /* 0x20-0x3f) to host memory. This mode of recording */ + /* is 16bit, 48KHz only. All 32 channels can be enabled */ + /* simultaneously. */ + #define FXWC_DEFAULTROUTE_C (1<<0) /* left emu out? */ #define FXWC_DEFAULTROUTE_B (1<<1) /* right emu out? */ #define FXWC_DEFAULTROUTE_A (1<<12) @@ -546,12 +619,16 @@ #define FXBA 0x47 /* FX Buffer Address */ #define FXBA_MASK 0xfffff000 /* 20 bit base address */ +/* 0x48 something - word access, defaults to 3f */ + #define MICBS 0x49 /* Microphone buffer size register */ #define ADCBS 0x4a /* ADC buffer size register */ #define FXBS 0x4b /* FX buffer size register */ +/* register: 0x4c..4f: ffff-ffff current amounts, per-channel */ + /* The following mask values define the size of the ADC, MIX and FX buffers in bytes */ #define ADCBS_BUFSIZE_NONE 0x00000000 #define ADCBS_BUFSIZE_384 0x00000001 @@ -602,6 +679,7 @@ #define A_DBG_SATURATION_OCCURED 0x20000000 #define A_DBG_SATURATION_ADDR 0x0ffc0000 +// NOTE: 0x54,55,56: 64-bit #define SPCS0 0x54 /* SPDIF output Channel Status 0 register */ #define SPCS1 0x55 /* SPDIF output Channel Status 1 register */ @@ -646,12 +724,18 @@ #define SOLEH 0x5d /* Stop on loop enable high register */ #define SPBYPASS 0x5e /* SPDIF BYPASS mode register */ -#define SPBYPASS_ENABLE 0x00000001 /* Enable SPDIF bypass mode */ +#define SPBYPASS_SPDIF0_MASK 0x00000003 /* SPDIF 0 bypass mode */ +#define SPBYPASS_SPDIF1_MASK 0x0000000c /* SPDIF 1 bypass mode */ +/* bypass mode: 0 - DSP; 1 - SPDIF A, 2 - SPDIF B, 3 - SPDIF C */ +#define SPBYPASS_FORMAT 0x00000f00 /* If 1, SPDIF XX uses 24 bit, if 0 - 20 bit */ #define AC97SLOT 0x5f /* additional AC97 slots enable bits */ +#define AC97SLOT_REAR_RIGHT 0x01 /* Rear left */ +#define AC97SLOT_REAR_LEFT 0x02 /* Rear right */ #define AC97SLOT_CNTR 0x10 /* Center enable */ #define AC97SLOT_LFE 0x20 /* LFE enable */ +// NOTE: 0x60,61,62: 64-bit #define CDSRCS 0x60 /* CD-ROM Sample Rate Converter status register */ #define GPSRCS 0x61 /* General Purpose SPDIF sample rate cvt status */ @@ -661,6 +745,7 @@ /* Assumes sample lock */ /* These three bitfields apply to CDSRCS, GPSRCS, and (except as noted) ZVSRCS. */ +#define SRCS_SPDIFVALID 0x04000000 /* SPDIF stream valid */ #define SRCS_SPDIFLOCKED 0x02000000 /* SPDIF stream locked */ #define SRCS_RATELOCKED 0x01000000 /* Sample rate locked */ #define SRCS_ESTSAMPLERATE 0x0007ffff /* Do not modify this field. */ @@ -688,6 +773,19 @@ #define FXIDX_MASK 0x0000ffff /* 16-bit value */ #define FXIDX_IDX 0x10000065 +/* The 32-bit HLIx and HLIPx registers all have one bit per channel control/status */ +#define HLIEL 0x66 /* Channel half loop interrupt enable low register */ + +#define HLIEH 0x67 /* Channel half loop interrupt enable high register */ + +#define HLIPL 0x68 /* Channel half loop interrupt pending low register */ + +#define HLIPH 0x69 /* Channel half loop interrupt pending high register */ + +// 0x6a,6b,6c used for some recording +// 0x6d unused +// 0x6e,6f - tanktable base / offset + /* This is the MPU port on the card (via the game port) */ #define A_MUDATA1 0x70 #define A_MUCMD1 0x71 @@ -705,9 +803,29 @@ #define A_FXWC2 0x75 /* Selects 0x9f-0x80 for FX recording */ #define A_SPDIF_SAMPLERATE 0x76 /* Set the sample rate of SPDIF output */ -#define A_SPDIF_48000 0x00000080 -#define A_SPDIF_44100 0x00000000 +#define A_SAMPLE_RATE 0x76 /* Various sample rate settings. */ +#define A_SAMPLE_RATE_NOT_USED 0x0ffc111e /* Bits that are not used and cannot be set. */ +#define A_SAMPLE_RATE_UNKNOWN 0xf0030001 /* Bits that can be set, but have unknown use. */ +#define A_SPDIF_RATE_MASK 0x000000e0 /* Any other values for rates, just use 48000 */ +#define A_SPDIF_48000 0x00000000 +#define A_SPDIF_192000 0x00000020 #define A_SPDIF_96000 0x00000040 +#define A_SPDIF_44100 0x00000080 + +#define A_I2S_CAPTURE_RATE_MASK 0x00000e00 /* This sets the capture PCM rate, but it is */ +#define A_I2S_CAPTURE_48000 0x00000000 /* unclear if this sets the ADC rate as well. */ +#define A_I2S_CAPTURE_192000 0x00000200 +#define A_I2S_CAPTURE_96000 0x00000400 +#define A_I2S_CAPTURE_44100 0x00000800 + +#define A_PCM_RATE_MASK 0x0000e000 /* This sets the playback PCM rate on the P16V */ +#define A_PCM_48000 0x00000000 +#define A_PCM_192000 0x00002000 +#define A_PCM_96000 0x00004000 +#define A_PCM_44100 0x00008000 + +/* 0x77,0x78,0x79 "something i2s-related" - default to 0x01080000 on my audigy 2 ZS --rlrevell */ +/* 0x7a, 0x7b - lookup tables */ #define A_FXRT2 0x7c #define A_FXRT_CHANNELE 0x0000003f /* Effects send bus number for channel's effects send E */ @@ -720,7 +838,8 @@ #define A_FXSENDAMOUNT_F_MASK 0x00FF0000 #define A_FXSENDAMOUNT_G_MASK 0x0000FF00 #define A_FXSENDAMOUNT_H_MASK 0x000000FF - +/* 0x7c, 0x7e "high bit is used for filtering" */ + /* The send amounts for this one are the same as used with the emu10k1 */ #define A_FXRT1 0x7e #define A_FXRT_CHANNELA 0x0000003f @@ -733,6 +852,9 @@ #define FXGPREGBASE 0x100 /* FX general purpose registers base */ #define A_FXGPREGBASE 0x400 /* Audigy GPRs, 0x400 to 0x5ff */ +#define A_TANKMEMCTLREGBASE 0x100 /* Tank memory control registers base - only for Audigy */ +#define A_TANKMEMCTLREG_MASK 0x1f /* only 5 bits used - only for Audigy */ + /* Tank audio data is logarithmically compressed down to 16 bits before writing to TRAM and is */ /* decompressed back to 20 bits on a read. There are a total of 160 locations, the last 32 */ /* locations are for external TRAM. */ @@ -769,44 +891,45 @@ /* ------------------- STRUCTURES -------------------- */ -typedef struct _snd_emu10k1 emu10k1_t; -typedef struct _snd_emu10k1_voice emu10k1_voice_t; -typedef struct _snd_emu10k1_pcm emu10k1_pcm_t; - -typedef enum { +enum { + EMU10K1_EFX, EMU10K1_PCM, EMU10K1_SYNTH, EMU10K1_MIDI -} emu10k1_voice_type_t; +}; + +struct snd_emu10k1; -struct _snd_emu10k1_voice { - emu10k1_t *emu; +struct snd_emu10k1_voice { + struct snd_emu10k1 *emu; int number; - int use: 1, + unsigned int use: 1, pcm: 1, + efx: 1, synth: 1, midi: 1; - void (*interrupt)(emu10k1_t *emu, emu10k1_voice_t *pvoice); + void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); - emu10k1_pcm_t *epcm; + struct snd_emu10k1_pcm *epcm; }; -typedef enum { +enum { PLAYBACK_EMUVOICE, + PLAYBACK_EFX, CAPTURE_AC97ADC, CAPTURE_AC97MIC, CAPTURE_EFX -} snd_emu10k1_pcm_type_t; +}; -struct _snd_emu10k1_pcm { - emu10k1_t *emu; - snd_emu10k1_pcm_type_t type; - snd_pcm_substream_t *substream; - emu10k1_voice_t *voices[2]; - emu10k1_voice_t *extra; +struct snd_emu10k1_pcm { + struct snd_emu10k1 *emu; + int type; + struct snd_pcm_substream *substream; + struct snd_emu10k1_voice *voices[NUM_EFX_PLAYBACK]; + struct snd_emu10k1_voice *extra; unsigned short running; unsigned short first_ptr; - snd_util_memblk_t *memblk; + struct snd_util_memblk *memblk; unsigned int start_addr; unsigned int ccca_start_addr; unsigned int capture_ipr; /* interrupt acknowledge mask */ @@ -820,106 +943,102 @@ unsigned int capture_bufsize; /* buffer size in bytes */ }; -typedef struct { +struct snd_emu10k1_pcm_mixer { + /* mono, left, right x 8 sends (4 on emu10k1) */ unsigned char send_routing[3][8]; unsigned char send_volume[3][8]; unsigned short attn[3]; - emu10k1_pcm_t *epcm; -} emu10k1_pcm_mixer_t; + struct snd_emu10k1_pcm *epcm; +}; #define snd_emu10k1_compose_send_routing(route) \ ((route[0] | (route[1] << 4) | (route[2] << 8) | (route[3] << 12)) << 16) #define snd_emu10k1_compose_audigy_fxrt1(route) \ -(((unsigned int)route[0] | ((unsigned int)route[1] << 8) | ((unsigned int)route[2] << 16) | ((unsigned int)route[3] << 12)) << 24) +((unsigned int)route[0] | ((unsigned int)route[1] << 8) | ((unsigned int)route[2] << 16) | ((unsigned int)route[3] << 24)) #define snd_emu10k1_compose_audigy_fxrt2(route) \ -(((unsigned int)route[4] | ((unsigned int)route[5] << 8) | ((unsigned int)route[6] << 16) | ((unsigned int)route[7] << 12)) << 24) +((unsigned int)route[4] | ((unsigned int)route[5] << 8) | ((unsigned int)route[6] << 16) | ((unsigned int)route[7] << 24)) -typedef struct snd_emu10k1_memblk { - snd_util_memblk_t mem; +struct snd_emu10k1_memblk { + struct snd_util_memblk mem; /* private part */ - short first_page, last_page, pages, mapped_page; + int first_page, last_page, pages, mapped_page; unsigned int map_locked; struct list_head mapped_link; struct list_head mapped_order_link; -} emu10k1_memblk_t; +}; #define snd_emu10k1_memblk_offset(blk) (((blk)->mapped_page << PAGE_SHIFT) | ((blk)->mem.offset & (PAGE_SIZE - 1))) #define EMU10K1_MAX_TRAM_BLOCKS_PER_CODE 16 -typedef struct { +struct snd_emu10k1_fx8010_ctl { struct list_head list; /* list link container */ unsigned int vcount; unsigned int count; /* count of GPR (1..16) */ - unsigned char gpr[32]; /* GPR number(s) */ + unsigned short gpr[32]; /* GPR number(s) */ unsigned int value[32]; unsigned int min; /* minimum range */ unsigned int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ - snd_kcontrol_t *kcontrol; -} snd_emu10k1_fx8010_ctl_t; + struct snd_kcontrol *kcontrol; +}; -typedef void (snd_fx8010_irq_handler_t)(emu10k1_t *emu, void *private_data); +typedef void (snd_fx8010_irq_handler_t)(struct snd_emu10k1 *emu, void *private_data); -typedef struct _snd_emu10k1_fx8010_irq { - struct _snd_emu10k1_fx8010_irq *next; +struct snd_emu10k1_fx8010_irq { + struct snd_emu10k1_fx8010_irq *next; snd_fx8010_irq_handler_t *handler; - unsigned char gpr_running; + unsigned short gpr_running; void *private_data; -} snd_emu10k1_fx8010_irq_t; +}; -typedef struct { +struct snd_emu10k1_fx8010_pcm { unsigned int valid: 1, opened: 1, active: 1; unsigned int channels; /* 16-bit channels count */ unsigned int tram_start; /* initial ring buffer position in TRAM (in samples) */ unsigned int buffer_size; /* count of buffered samples */ - unsigned char gpr_size; /* GPR containing size of ring buffer in samples (host) */ - unsigned char gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ - unsigned char gpr_count; /* GPR containing count of samples between two interrupts (host) */ - unsigned char gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ - unsigned char gpr_trigger; /* GPR containing trigger (activate) information (host) */ - unsigned char gpr_running; /* GPR containing info if PCM is running (FX8010) */ + unsigned short gpr_size; /* GPR containing size of ring buffer in samples (host) */ + unsigned short gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ + unsigned short gpr_count; /* GPR containing count of samples between two interrupts (host) */ + unsigned short gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ + unsigned short gpr_trigger; /* GPR containing trigger (activate) information (host) */ + unsigned short gpr_running; /* GPR containing info if PCM is running (FX8010) */ unsigned char etram[32]; /* external TRAM address & data */ - unsigned int sw_data, hw_data; - unsigned int sw_io, hw_io; - unsigned int sw_ready, hw_ready; - unsigned int appl_ptr; + struct snd_pcm_indirect pcm_rec; unsigned int tram_pos; unsigned int tram_shift; - snd_emu10k1_fx8010_irq_t *irq; -} snd_emu10k1_fx8010_pcm_t; + struct snd_emu10k1_fx8010_irq *irq; +}; -typedef struct { +struct snd_emu10k1_fx8010 { unsigned short fxbus_mask; /* used FX buses (bitmask) */ unsigned short extin_mask; /* used external inputs (bitmask) */ unsigned short extout_mask; /* used external outputs (bitmask) */ unsigned short pad1; unsigned int itram_size; /* internal TRAM size in samples */ - unsigned int etram_size; /* external TRAM size in samples */ - void *etram_pages; /* allocated pages for external TRAM */ - dma_addr_t etram_pages_dmaaddr; + struct snd_dma_buffer etram_pages; /* external TRAM pages and size */ unsigned int dbg; /* FX debugger register */ unsigned char name[128]; int gpr_size; /* size of allocated GPR controls */ int gpr_count; /* count of used kcontrols */ struct list_head gpr_ctl; /* GPR controls */ - struct semaphore lock; - snd_emu10k1_fx8010_pcm_t pcm[8]; + struct mutex lock; + struct snd_emu10k1_fx8010_pcm pcm[8]; spinlock_t irq_lock; - snd_emu10k1_fx8010_irq_t *irq_handlers; -} snd_emu10k1_fx8010_t; + struct snd_emu10k1_fx8010_irq *irq_handlers; +}; -#define emu10k1_gpr_ctl(n) list_entry(n, snd_emu10k1_fx8010_ctl_t, list) +#define emu10k1_gpr_ctl(n) list_entry(n, struct snd_emu10k1_fx8010_ctl, list) -typedef struct { - struct _snd_emu10k1 *emu; - snd_rawmidi_t *rmidi; - snd_rawmidi_substream_t *substream_input; - snd_rawmidi_substream_t *substream_output; +struct snd_emu10k1_midi { + struct snd_emu10k1 *emu; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream_input; + struct snd_rawmidi_substream *substream_output; unsigned int midi_mode; spinlock_t input_lock; spinlock_t output_lock; @@ -927,17 +1046,43 @@ int tx_enable, rx_enable; int port; int ipr_tx, ipr_rx; - void (*interrupt)(emu10k1_t *emu, unsigned int status); -} emu10k1_midi_t; + void (*interrupt)(struct snd_emu10k1 *emu, unsigned int status); +}; + +struct snd_emu_chip_details { + u32 vendor; + u32 device; + u32 subsystem; + unsigned char revision; + unsigned char emu10k1_chip; /* Original SB Live. Not SB Live 24bit. */ + unsigned char emu10k2_chip; /* Audigy 1 or Audigy 2. */ + unsigned char ca0102_chip; /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */ + unsigned char ca0108_chip; /* Audigy 2 Value */ + unsigned char ca_cardbus_chip; /* Audigy 2 ZS Notebook */ + unsigned char ca0151_chip; /* P16V */ + unsigned char spk71; /* Has 7.1 speakers */ + unsigned char sblive51; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ + unsigned char spdif_bug; /* Has Spdif phasing bug */ + unsigned char ac97_chip; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ + unsigned char ecard; /* APS EEPROM */ + unsigned char emu1212m; /* EMU 1212m card */ + unsigned char spi_dac; /* SPI interface for DAC */ + unsigned char i2c_adc; /* I2C interface for ADC */ + unsigned char adc_1361t; /* Use Philips 1361T ADC */ + const char *driver; + const char *name; + const char *id; /* for backward compatibility - can be NULL if not needed */ +}; -struct _snd_emu10k1 { +struct snd_emu10k1 { int irq; unsigned long port; /* I/O port number */ - struct resource *res_port; - int APS: 1, /* APS flag */ - no_ac97: 1, /* no AC'97 */ - tos_link: 1; /* tos link detected */ + unsigned int tos_link: 1, /* tos link detected */ + rear_ac97: 1, /* rear channels are on AC'97 */ + enable_ir: 1; + /* Contains profile of card capabilities */ + const struct snd_emu_chip_details *card_capabilities; unsigned int audigy; /* is Audigy? */ unsigned int revision; /* chip revision */ unsigned int serial; /* serial number */ @@ -946,12 +1091,13 @@ unsigned int ecard_ctrl; /* ecard control bits */ unsigned long dma_mask; /* PCI DMA mask */ int max_cache_pages; /* max memory size / PAGE_SIZE */ - void *silent_page; /* silent page */ - dma_addr_t silent_page_dmaaddr; - volatile u32 *ptb_pages; /* page table pages */ - dma_addr_t ptb_pages_dmaaddr; - snd_util_memhdr_t *memhdr; /* page allocation list */ - emu10k1_memblk_t *reserved_page; /* reserved page */ + struct snd_dma_buffer silent_page; /* silent page */ + struct snd_dma_buffer ptb_pages; /* page table pages */ + struct snd_dma_device p16v_dma_dev; + struct snd_dma_buffer p16v_buffer; + + struct snd_util_memhdr *memhdr; /* page allocation list */ + struct snd_emu10k1_memblk *reserved_page; /* reserved page */ struct list_head mapped_link_head; struct list_head mapped_order_link_head; @@ -961,112 +1107,168 @@ unsigned int spdif_bits[3]; /* s/pdif out setup */ - snd_emu10k1_fx8010_t fx8010; /* FX8010 info */ + struct snd_emu10k1_fx8010 fx8010; /* FX8010 info */ int gpr_base; - ac97_t *ac97; + struct snd_ac97 *ac97; struct pci_dev *pci; - snd_card_t *card; - snd_pcm_t *pcm; - snd_pcm_t *pcm_mic; - snd_pcm_t *pcm_efx; - snd_pcm_t *pcm_fx8010; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm *pcm_mic; + struct snd_pcm *pcm_efx; + struct snd_pcm *pcm_multi; + struct snd_pcm *pcm_p16v; spinlock_t synth_lock; void *synth; - int (*get_synth_voice)(emu10k1_t *emu); + int (*get_synth_voice)(struct snd_emu10k1 *emu); spinlock_t reg_lock; spinlock_t emu_lock; spinlock_t voice_lock; - struct semaphore ptb_lock; - emu10k1_voice_t voices[64]; - emu10k1_pcm_mixer_t pcm_mixer[32]; - snd_kcontrol_t *ctl_send_routing; - snd_kcontrol_t *ctl_send_volume; - snd_kcontrol_t *ctl_attn; - - void (*hwvol_interrupt)(emu10k1_t *emu, unsigned int status); - void (*capture_interrupt)(emu10k1_t *emu, unsigned int status); - void (*capture_mic_interrupt)(emu10k1_t *emu, unsigned int status); - void (*capture_efx_interrupt)(emu10k1_t *emu, unsigned int status); - void (*timer_interrupt)(emu10k1_t *emu); - void (*spdif_interrupt)(emu10k1_t *emu, unsigned int status); - void (*dsp_interrupt)(emu10k1_t *emu); - - snd_pcm_substream_t *pcm_capture_substream; - snd_pcm_substream_t *pcm_capture_mic_substream; - snd_pcm_substream_t *pcm_capture_efx_substream; + struct snd_emu10k1_voice voices[NUM_G]; + struct snd_emu10k1_voice p16v_voices[4]; + struct snd_emu10k1_voice p16v_capture_voice; + int p16v_device_offset; + u32 p16v_capture_source; + u32 p16v_capture_channel; + struct snd_emu10k1_pcm_mixer pcm_mixer[32]; + struct snd_emu10k1_pcm_mixer efx_pcm_mixer[NUM_EFX_PLAYBACK]; + struct snd_kcontrol *ctl_send_routing; + struct snd_kcontrol *ctl_send_volume; + struct snd_kcontrol *ctl_attn; + struct snd_kcontrol *ctl_efx_send_routing; + struct snd_kcontrol *ctl_efx_send_volume; + struct snd_kcontrol *ctl_efx_attn; + + void (*hwvol_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*capture_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*capture_mic_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*capture_efx_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*spdif_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*dsp_interrupt)(struct snd_emu10k1 *emu); + + struct snd_pcm_substream *pcm_capture_substream; + struct snd_pcm_substream *pcm_capture_mic_substream; + struct snd_pcm_substream *pcm_capture_efx_substream; + struct snd_pcm_substream *pcm_playback_efx_substream; + + struct snd_timer *timer; - emu10k1_midi_t midi; - emu10k1_midi_t midi2; /* for audigy */ + struct snd_emu10k1_midi midi; + struct snd_emu10k1_midi midi2; /* for audigy */ unsigned int efx_voices_mask[2]; + unsigned int next_free_voice; + +#ifdef CONFIG_PM + unsigned int *saved_ptr; + unsigned int *saved_gpr; + unsigned int *tram_val_saved; + unsigned int *tram_addr_saved; + unsigned int *saved_icode; + unsigned int *p16v_saved; + unsigned int saved_a_iocfg, saved_hcfg; +#endif + }; -int snd_emu10k1_create(snd_card_t * card, +int snd_emu10k1_create(struct snd_card *card, struct pci_dev *pci, unsigned short extin_mask, unsigned short extout_mask, long max_cache_bytes, int enable_ir, - emu10k1_t ** remu); + uint subsystem, + struct snd_emu10k1 ** remu); -int snd_emu10k1_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_pcm_mic(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_pcm_efx(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_fx8010_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_mixer(emu10k1_t * emu); -int snd_emu10k1_fx8010_new(emu10k1_t *emu, int device, snd_hwdep_t ** rhwdep); +int snd_emu10k1_pcm(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_pcm_mic(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_pcm_efx(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_p16v_pcm(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_p16v_free(struct snd_emu10k1 * emu); +int snd_p16v_mixer(struct snd_emu10k1 * emu); +int snd_emu10k1_pcm_multi(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_fx8010_pcm(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_mixer(struct snd_emu10k1 * emu, int pcm_device, int multi_device); +int snd_emu10k1_timer(struct snd_emu10k1 * emu, int device); +int snd_emu10k1_fx8010_new(struct snd_emu10k1 *emu, int device, struct snd_hwdep ** rhwdep); irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id, struct pt_regs *regs); -/* initialization */ -void snd_emu10k1_voice_init(emu10k1_t * emu, int voice); -int snd_emu10k1_init_efx(emu10k1_t *emu); -void snd_emu10k1_free_efx(emu10k1_t *emu); -int snd_emu10k1_fx8010_tram_setup(emu10k1_t *emu, u32 size); +void snd_emu10k1_voice_init(struct snd_emu10k1 * emu, int voice); +int snd_emu10k1_init_efx(struct snd_emu10k1 *emu); +void snd_emu10k1_free_efx(struct snd_emu10k1 *emu); +int snd_emu10k1_fx8010_tram_setup(struct snd_emu10k1 *emu, u32 size); +int snd_emu10k1_done(struct snd_emu10k1 * emu); /* I/O functions */ -unsigned int snd_emu10k1_ptr_read(emu10k1_t * emu, unsigned int reg, unsigned int chn); -void snd_emu10k1_ptr_write(emu10k1_t *emu, unsigned int reg, unsigned int chn, unsigned int data); -void snd_emu10k1_efx_write(emu10k1_t *emu, unsigned int pc, unsigned int data); -unsigned int snd_emu10k1_efx_read(emu10k1_t *emu, unsigned int pc); -void snd_emu10k1_intr_enable(emu10k1_t *emu, unsigned int intrenb); -void snd_emu10k1_intr_disable(emu10k1_t *emu, unsigned int intrenb); -void snd_emu10k1_voice_intr_enable(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_intr_disable(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_intr_ack(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_set_loop_stop(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_clear_loop_stop(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_wait(emu10k1_t *emu, unsigned int wait); -static inline unsigned int snd_emu10k1_wc(emu10k1_t *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } -unsigned short snd_emu10k1_ac97_read(ac97_t *ac97, unsigned short reg); -void snd_emu10k1_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short data); +unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); +void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); +unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); +void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); +int snd_emu10k1_spi_write(struct snd_emu10k1 * emu, unsigned int data); +unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); +void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); +void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb); +void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); +static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } +unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); +void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data); unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate); -unsigned char snd_emu10k1_sum_vol_attn(unsigned int value); + +#ifdef CONFIG_PM +void snd_emu10k1_suspend_regs(struct snd_emu10k1 *emu); +void snd_emu10k1_resume_init(struct snd_emu10k1 *emu); +void snd_emu10k1_resume_regs(struct snd_emu10k1 *emu); +int snd_emu10k1_efx_alloc_pm_buffer(struct snd_emu10k1 *emu); +void snd_emu10k1_efx_free_pm_buffer(struct snd_emu10k1 *emu); +void snd_emu10k1_efx_suspend(struct snd_emu10k1 *emu); +void snd_emu10k1_efx_resume(struct snd_emu10k1 *emu); +int snd_p16v_alloc_pm_buffer(struct snd_emu10k1 *emu); +void snd_p16v_free_pm_buffer(struct snd_emu10k1 *emu); +void snd_p16v_suspend(struct snd_emu10k1 *emu); +void snd_p16v_resume(struct snd_emu10k1 *emu); +#endif /* memory allocation */ -snd_util_memblk_t *snd_emu10k1_alloc_pages(emu10k1_t *emu, snd_pcm_substream_t *substream); -int snd_emu10k1_free_pages(emu10k1_t *emu, snd_util_memblk_t *blk); -snd_util_memblk_t *snd_emu10k1_synth_alloc(emu10k1_t *emu, unsigned int size); -int snd_emu10k1_synth_free(emu10k1_t *emu, snd_util_memblk_t *blk); -int snd_emu10k1_synth_bzero(emu10k1_t *emu, snd_util_memblk_t *blk, int offset, int size); -int snd_emu10k1_synth_copy_from_user(emu10k1_t *emu, snd_util_memblk_t *blk, int offset, const char *data, int size); -int snd_emu10k1_memblk_map(emu10k1_t *emu, emu10k1_memblk_t *blk); +struct snd_util_memblk *snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream); +int snd_emu10k1_free_pages(struct snd_emu10k1 *emu, struct snd_util_memblk *blk); +struct snd_util_memblk *snd_emu10k1_synth_alloc(struct snd_emu10k1 *emu, unsigned int size); +int snd_emu10k1_synth_free(struct snd_emu10k1 *emu, struct snd_util_memblk *blk); +int snd_emu10k1_synth_bzero(struct snd_emu10k1 *emu, struct snd_util_memblk *blk, int offset, int size); +int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_memblk *blk, int offset, const char __user *data, int size); +int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk); /* voice allocation */ -int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice); -int snd_emu10k1_voice_free(emu10k1_t *emu, emu10k1_voice_t *pvoice); +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, struct snd_emu10k1_voice **rvoice); +int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); /* MIDI uart */ -int snd_emu10k1_midi(emu10k1_t * emu); -int snd_emu10k1_audigy_midi(emu10k1_t * emu); +int snd_emu10k1_midi(struct snd_emu10k1 * emu); +int snd_emu10k1_audigy_midi(struct snd_emu10k1 * emu); /* proc interface */ -int snd_emu10k1_proc_init(emu10k1_t * emu); +int snd_emu10k1_proc_init(struct snd_emu10k1 * emu); + +/* fx8010 irq handler */ +int snd_emu10k1_fx8010_register_irq_handler(struct snd_emu10k1 *emu, + snd_fx8010_irq_handler_t *handler, + unsigned char gpr_running, + void *private_data, + struct snd_emu10k1_fx8010_irq **r_irq); +int snd_emu10k1_fx8010_unregister_irq_handler(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_irq *irq); #endif /* __KERNEL__ */ @@ -1100,7 +1302,10 @@ /* GPRs */ #define FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x0f */ #define EXTIN(x) (0x10 + (x)) /* x = 0x00 - 0x0f */ -#define EXTOUT(x) (0x20 + (x)) /* x = 0x00 - 0x0f */ +#define EXTOUT(x) (0x20 + (x)) /* x = 0x00 - 0x0f physical outs -> FXWC low 16 bits */ +#define FXBUS2(x) (0x30 + (x)) /* x = 0x00 - 0x0f copies of fx buses for capture -> FXWC high 16 bits */ + /* NB: 0x31 and 0x32 are shared with Center/LFE on SB live 5.1 */ + #define C_00000000 0x40 #define C_00000001 0x41 #define C_00000002 0x42 @@ -1135,9 +1340,20 @@ #define ITRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x00 + (x)) /* x = 0x00 - 0x7f */ #define ETRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x80 + (x)) /* x = 0x00 - 0x1f */ -#define A_FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x3f? */ -#define A_EXTIN(x) (0x40 + (x)) /* x = 0x00 - 0x1f? */ -#define A_EXTOUT(x) (0x60 + (x)) /* x = 0x00 - 0x1f? */ +#define A_ITRAM_DATA(x) (TANKMEMDATAREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */ +#define A_ETRAM_DATA(x) (TANKMEMDATAREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */ +#define A_ITRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */ +#define A_ETRAM_ADDR(x) (TANKMEMADDRREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */ +#define A_ITRAM_CTL(x) (A_TANKMEMCTLREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */ +#define A_ETRAM_CTL(x) (A_TANKMEMCTLREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */ + +#define A_FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x3f FX buses */ +#define A_EXTIN(x) (0x40 + (x)) /* x = 0x00 - 0x0f physical ins */ +#define A_P16VIN(x) (0x50 + (x)) /* x = 0x00 - 0x0f p16v ins (A2 only) "EMU32 inputs" */ +#define A_EXTOUT(x) (0x60 + (x)) /* x = 0x00 - 0x1f physical outs -> A_FXWC1 0x79-7f unknown */ +#define A_FXBUS2(x) (0x80 + (x)) /* x = 0x00 - 0x1f extra outs used for EFX capture -> A_FXWC2 */ +#define A_EMU32OUTH(x) (0xa0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_10 - _1F" - ??? */ +#define A_EMU32OUTL(x) (0xb0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_1 - _F" - ??? */ #define A_GPR(x) (A_FXGPREGBASE + (x)) /* cc_reg constants */ @@ -1161,6 +1377,8 @@ #define FXBUS_PCM_RIGHT_FRONT 0x09 #define FXBUS_MIDI_REVERB 0x0c #define FXBUS_MIDI_CHORUS 0x0d +#define FXBUS_PCM_LEFT_SIDE 0x0e +#define FXBUS_PCM_RIGHT_SIDE 0x0f #define FXBUS_PT_LEFT 0x14 #define FXBUS_PT_RIGHT 0x15 @@ -1185,8 +1403,8 @@ #define EXTOUT_AC97_R 0x01 /* AC'97 playback channel - right */ #define EXTOUT_TOSLINK_L 0x02 /* LiveDrive - TOSLink Optical - left */ #define EXTOUT_TOSLINK_R 0x03 /* LiveDrive - TOSLink Optical - right */ -#define EXTOUT_CENTER 0x04 /* SB Live 5.1 - center */ -#define EXTOUT_LFE 0x05 /* SB Live 5.1 - LFE */ +#define EXTOUT_AC97_CENTER 0x04 /* SB Live 5.1 - center */ +#define EXTOUT_AC97_LFE 0x05 /* SB Live 5.1 - LFE */ #define EXTOUT_HEADPHONE_L 0x06 /* LiveDrive - Headphone - left */ #define EXTOUT_HEADPHONE_R 0x07 /* LiveDrive - Headphone - right */ #define EXTOUT_REAR_L 0x08 /* Rear channel - left */ @@ -1194,6 +1412,8 @@ #define EXTOUT_ADC_CAP_L 0x0a /* ADC Capture buffer - left */ #define EXTOUT_ADC_CAP_R 0x0b /* ADC Capture buffer - right */ #define EXTOUT_MIC_CAP 0x0c /* MIC Capture buffer */ +#define EXTOUT_AC97_REAR_L 0x0d /* SB Live 5.1 (c) 2003 - Rear Left */ +#define EXTOUT_AC97_REAR_R 0x0e /* SB Live 5.1 (c) 2003 - Rear Right */ #define EXTOUT_ACENTER 0x11 /* Analog Center */ #define EXTOUT_ALFE 0x12 /* Analog LFE */ @@ -1224,8 +1444,8 @@ #define A_EXTOUT_AFRONT_R 0x09 /* right */ #define A_EXTOUT_ACENTER 0x0a /* analog center */ #define A_EXTOUT_ALFE 0x0b /* analog LFE */ -/* 0x0c ?? */ -/* 0x0d ?? */ +#define A_EXTOUT_ASIDE_L 0x0c /* analog side left - Audigy 2 ZS */ +#define A_EXTOUT_ASIDE_R 0x0d /* right - Audigy 2 ZS */ #define A_EXTOUT_AREAR_L 0x0e /* analog rear left */ #define A_EXTOUT_AREAR_R 0x0f /* right */ #define A_EXTOUT_AC97_L 0x10 /* AC97 left (front) */ @@ -1259,8 +1479,11 @@ #define A_C_00100000 0xd5 #define A_GPR_ACCU 0xd6 /* ACCUM, accumulator */ #define A_GPR_COND 0xd7 /* CCR, condition register */ -/* 0xd8 = noise1 */ -/* 0xd9 = noise2 */ +#define A_GPR_NOISE0 0xd8 /* noise source */ +#define A_GPR_NOISE1 0xd9 /* noise source */ +#define A_GPR_IRQ 0xda /* IRQ register */ +#define A_GPR_DBAC 0xdb /* TRAM Delay Base Address Counter - internal */ +#define A_GPR_DBACE 0xde /* TRAM Delay Base Address Counter - external */ /* definitions for debug register */ #define EMU10K1_DBG_ZC 0x80000000 /* zero tram counter */ @@ -1280,15 +1503,14 @@ #define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */ #endif -typedef struct { - unsigned int card; /* card type */ +struct snd_emu10k1_fx8010_info { unsigned int internal_tram_size; /* in samples */ unsigned int external_tram_size; /* in samples */ char fxbus_names[16][32]; /* names of FXBUSes */ char extin_names[16][32]; /* names of external inputs */ char extout_names[32][32]; /* names of external outputs */ unsigned int gpr_controls; /* count of GPR controls */ -} emu10k1_fx8010_info_t; +}; #define EMU10K1_GPR_TRANSLATION_NONE 0 #define EMU10K1_GPR_TRANSLATION_TABLE100 1 @@ -1296,77 +1518,84 @@ #define EMU10K1_GPR_TRANSLATION_TREBLE 3 #define EMU10K1_GPR_TRANSLATION_ONOFF 4 -typedef struct { - snd_ctl_elem_id_t id; /* full control ID definition */ +struct snd_emu10k1_fx8010_control_gpr { + struct snd_ctl_elem_id id; /* full control ID definition */ unsigned int vcount; /* visible count */ unsigned int count; /* count of GPR (1..16) */ - unsigned char gpr[32]; /* GPR number(s) */ + unsigned short gpr[32]; /* GPR number(s) */ unsigned int value[32]; /* initial values */ unsigned int min; /* minimum range */ unsigned int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ -} emu10k1_fx8010_control_gpr_t; +}; -typedef struct { +struct snd_emu10k1_fx8010_code { char name[128]; - unsigned long gpr_valid[0x100/(sizeof(unsigned long)*8)]; /* bitmask of valid initializers */ - unsigned int gpr_map[0x100]; /* initializers */ + DECLARE_BITMAP(gpr_valid, 0x200); /* bitmask of valid initializers */ + u_int32_t __user *gpr_map; /* initializers */ unsigned int gpr_add_control_count; /* count of GPR controls to add/replace */ - emu10k1_fx8010_control_gpr_t *gpr_add_controls; /* GPR controls to add/replace */ + struct snd_emu10k1_fx8010_control_gpr __user *gpr_add_controls; /* GPR controls to add/replace */ unsigned int gpr_del_control_count; /* count of GPR controls to remove */ - snd_ctl_elem_id_t *gpr_del_controls; /* IDs of GPR controls to remove */ + struct snd_ctl_elem_id __user *gpr_del_controls; /* IDs of GPR controls to remove */ unsigned int gpr_list_control_count; /* count of GPR controls to list */ unsigned int gpr_list_control_total; /* total count of GPR controls */ - emu10k1_fx8010_control_gpr_t *gpr_list_controls; /* listed GPR controls */ + struct snd_emu10k1_fx8010_control_gpr __user *gpr_list_controls; /* listed GPR controls */ - unsigned long tram_valid[0xa0/(sizeof(unsigned long)*8)]; /* bitmask of valid initializers */ - unsigned int tram_data_map[0xa0]; /* data initializers */ - unsigned int tram_addr_map[0xa0]; /* map initializers */ + DECLARE_BITMAP(tram_valid, 0x100); /* bitmask of valid initializers */ + u_int32_t __user *tram_data_map; /* data initializers */ + u_int32_t __user *tram_addr_map; /* map initializers */ - unsigned long code_valid[512/(sizeof(unsigned long)*8)]; /* bitmask of valid instructions */ - unsigned int code[512][2]; /* one instruction - 64 bits */ -} emu10k1_fx8010_code_t; + DECLARE_BITMAP(code_valid, 1024); /* bitmask of valid instructions */ + u_int32_t __user *code; /* one instruction - 64 bits */ +}; -typedef struct { +struct snd_emu10k1_fx8010_tram { unsigned int address; /* 31.bit == 1 -> external TRAM */ unsigned int size; /* size in samples (4 bytes) */ unsigned int *samples; /* pointer to samples (20-bit) */ /* NULL->clear memory */ -} emu10k1_fx8010_tram_t; +}; -typedef struct { +struct snd_emu10k1_fx8010_pcm_rec { unsigned int substream; /* substream number */ unsigned int res1; /* reserved */ unsigned int channels; /* 16-bit channels count, zero = remove this substream */ unsigned int tram_start; /* ring buffer position in TRAM (in samples) */ unsigned int buffer_size; /* count of buffered samples */ - unsigned char gpr_size; /* GPR containing size of ringbuffer in samples (host) */ - unsigned char gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ - unsigned char gpr_count; /* GPR containing count of samples between two interrupts (host) */ - unsigned char gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ - unsigned char gpr_trigger; /* GPR containing trigger (activate) information (host) */ - unsigned char gpr_running; /* GPR containing info if PCM is running (FX8010) */ + unsigned short gpr_size; /* GPR containing size of ringbuffer in samples (host) */ + unsigned short gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ + unsigned short gpr_count; /* GPR containing count of samples between two interrupts (host) */ + unsigned short gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ + unsigned short gpr_trigger; /* GPR containing trigger (activate) information (host) */ + unsigned short gpr_running; /* GPR containing info if PCM is running (FX8010) */ unsigned char pad; /* reserved */ unsigned char etram[32]; /* external TRAM address & data (one per channel) */ unsigned int res2; /* reserved */ -} emu10k1_fx8010_pcm_t; +}; -#define SNDRV_EMU10K1_IOCTL_INFO _IOR ('H', 0x10, emu10k1_fx8010_info_t) -#define SNDRV_EMU10K1_IOCTL_CODE_POKE _IOW ('H', 0x11, emu10k1_fx8010_code_t) -#define SNDRV_EMU10K1_IOCTL_CODE_PEEK _IOWR('H', 0x12, emu10k1_fx8010_code_t) +#define SNDRV_EMU10K1_IOCTL_INFO _IOR ('H', 0x10, struct snd_emu10k1_fx8010_info) +#define SNDRV_EMU10K1_IOCTL_CODE_POKE _IOW ('H', 0x11, struct snd_emu10k1_fx8010_code) +#define SNDRV_EMU10K1_IOCTL_CODE_PEEK _IOWR('H', 0x12, struct snd_emu10k1_fx8010_code) #define SNDRV_EMU10K1_IOCTL_TRAM_SETUP _IOW ('H', 0x20, int) -#define SNDRV_EMU10K1_IOCTL_TRAM_POKE _IOW ('H', 0x21, emu10k1_fx8010_tram_t) -#define SNDRV_EMU10K1_IOCTL_TRAM_PEEK _IOWR('H', 0x22, emu10k1_fx8010_tram_t) -#define SNDRV_EMU10K1_IOCTL_PCM_POKE _IOW ('H', 0x30, emu10k1_fx8010_pcm_t) -#define SNDRV_EMU10K1_IOCTL_PCM_PEEK _IOWR('H', 0x31, emu10k1_fx8010_pcm_t) +#define SNDRV_EMU10K1_IOCTL_TRAM_POKE _IOW ('H', 0x21, struct snd_emu10k1_fx8010_tram) +#define SNDRV_EMU10K1_IOCTL_TRAM_PEEK _IOWR('H', 0x22, struct snd_emu10k1_fx8010_tram) +#define SNDRV_EMU10K1_IOCTL_PCM_POKE _IOW ('H', 0x30, struct snd_emu10k1_fx8010_pcm_rec) +#define SNDRV_EMU10K1_IOCTL_PCM_PEEK _IOWR('H', 0x31, struct snd_emu10k1_fx8010_pcm_rec) #define SNDRV_EMU10K1_IOCTL_STOP _IO ('H', 0x80) #define SNDRV_EMU10K1_IOCTL_CONTINUE _IO ('H', 0x81) #define SNDRV_EMU10K1_IOCTL_ZERO_TRAM_COUNTER _IO ('H', 0x82) #define SNDRV_EMU10K1_IOCTL_SINGLE_STEP _IOW ('H', 0x83, int) #define SNDRV_EMU10K1_IOCTL_DBG_READ _IOR ('H', 0x84, int) + +/* typedefs for compatibility to user-space */ +typedef struct snd_emu10k1_fx8010_info emu10k1_fx8010_info_t; +typedef struct snd_emu10k1_fx8010_control_gpr emu10k1_fx8010_control_gpr_t; +typedef struct snd_emu10k1_fx8010_code emu10k1_fx8010_code_t; +typedef struct snd_emu10k1_fx8010_tram emu10k1_fx8010_tram_t; +typedef struct snd_emu10k1_fx8010_pcm_rec emu10k1_fx8010_pcm_t; #endif /* __SOUND_EMU10K1_H */ --- sys/gnu/dev/sound/pci/emu10k1.h.orig Tue Jul 10 04:51:37 2007 +++ sys/gnu/dev/sound/pci/emu10k1.h Thu Jan 1 07:30:00 1970 @@ -1,740 +0,0 @@ -/*- - ********************************************************************** - * emu10k1.h, derived from 8010.h - * Copyright 1999-2001 Creative Labs, Inc. - * - ********************************************************************** - * - * Date Author Summary of changes - * ---- ------ ------------------ - * October 20, 1999 Bertrand Lee base code release - * November 2, 1999 Alan Cox Cleaned of 8bit chars, DOS - * line endings - * December 8, 1999 Jon Taylor Added lots of new register info - * May 16, 2001 Daniel Bertrand Added unofficial DBG register info - * Oct-Nov 2001 D.B. Added unofficial Audigy registers - * - ********************************************************************** - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the Free - * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, - * USA. - * - * - ********************************************************************** - * $FreeBSD: src/sys/gnu/dev/sound/pci/emu10k1.h,v 1.8 2005/01/06 18:27:30 imp Exp $ - */ - - -#ifndef _8010_H -#define _8010_H - - /* -#include - */ - -// Driver version: -#define MAJOR_VER 0 -#define MINOR_VER 20 -#define DRIVER_VERSION "0.20a" - - -// Audigy specify registers are prefixed with 'A_' - -/************************************************************************************************/ -/* PCI function 0 registers, address = + PCIBASE0 */ -/************************************************************************************************/ - -#define PTR 0x00 /* Indexed register set pointer register */ - /* NOTE: The CHANNELNUM and ADDRESS words can */ - /* be modified independently of each other. */ -#define PTR_CHANNELNUM_MASK 0x0000003f /* For each per-channel register, indicates the */ - /* channel number of the register to be */ - /* accessed. For non per-channel registers the */ - /* value should be set to zero. */ -#define PTR_ADDRESS_MASK 0x07ff0000 /* Register index */ - -#define DATA 0x04 /* Indexed register set data register */ - -#define IPR 0x08 /* Global interrupt pending register */ - /* Clear pending interrupts by writing a 1 to */ - /* the relevant bits and zero to the other bits */ - -/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ -#define A_IPR_MIDITRANSBUFEMPTY2 0x10000000 /* MIDI UART transmit buffer empty */ -#define A_IPR_MIDIRECVBUFEMPTY2 0x08000000 /* MIDI UART receive buffer empty */ - -#define IPR_SAMPLERATETRACKER 0x01000000 /* Sample rate tracker lock status change */ -#define IPR_FXDSP 0x00800000 /* Enable FX DSP interrupts */ -#define IPR_FORCEINT 0x00400000 /* Force Sound Blaster interrupt */ -#define IPR_PCIERROR 0x00200000 /* PCI bus error */ -#define IPR_VOLINCR 0x00100000 /* Volume increment button pressed */ -#define IPR_VOLDECR 0x00080000 /* Volume decrement button pressed */ -#define IPR_MUTE 0x00040000 /* Mute button pressed */ -#define IPR_MICBUFFULL 0x00020000 /* Microphone buffer full */ -#define IPR_MICBUFHALFFULL 0x00010000 /* Microphone buffer half full */ -#define IPR_ADCBUFFULL 0x00008000 /* ADC buffer full */ -#define IPR_ADCBUFHALFFULL 0x00004000 /* ADC buffer half full */ -#define IPR_EFXBUFFULL 0x00002000 /* Effects buffer full */ -#define IPR_EFXBUFHALFFULL 0x00001000 /* Effects buffer half full */ -#define IPR_GPSPDIFSTATUSCHANGE 0x00000800 /* GPSPDIF channel status change */ -#define IPR_CDROMSTATUSCHANGE 0x00000400 /* CD-ROM channel status change */ -#define IPR_INTERVALTIMER 0x00000200 /* Interval timer terminal count */ -#define IPR_MIDITRANSBUFEMPTY 0x00000100 /* MIDI UART transmit buffer empty */ -#define IPR_MIDIRECVBUFEMPTY 0x00000080 /* MIDI UART receive buffer empty */ -#define IPR_CHANNELLOOP 0x00000040 /* One or more channel loop interrupts pending */ -#define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */ - /* Highest set channel in CLIPL or CLIPH. When */ - /* IP is written with CL set, the bit in CLIPL */ - /* or CLIPH corresponding to the CIN value */ - /* written will be cleared. */ -#define A_IPR_MIDITRANSBUFEMPTY1 IPR_MIDITRANSBUFEMPTY /* MIDI UART transmit buffer empty */ -#define A_IPR_MIDIRECVBUFEMPTY1 IPR_MIDIRECVBUFEMPTY /* MIDI UART receive buffer empty */ - - - -#define INTE 0x0c /* Interrupt enable register */ -#define INTE_VIRTUALSB_MASK 0xc0000000 /* Virtual Soundblaster I/O port capture */ -#define INTE_VIRTUALSB_220 0x00000000 /* Capture at I/O base address 0x220-0x22f */ -#define INTE_VIRTUALSB_240 0x40000000 /* Capture at I/O base address 0x240 */ -#define INTE_VIRTUALSB_260 0x80000000 /* Capture at I/O base address 0x260 */ -#define INTE_VIRTUALSB_280 0xc0000000 /* Capture at I/O base address 0x280 */ -#define INTE_VIRTUALMPU_MASK 0x30000000 /* Virtual MPU I/O port capture */ -#define INTE_VIRTUALMPU_300 0x00000000 /* Capture at I/O base address 0x300-0x301 */ -#define INTE_VIRTUALMPU_310 0x10000000 /* Capture at I/O base address 0x310 */ -#define INTE_VIRTUALMPU_320 0x20000000 /* Capture at I/O base address 0x320 */ -#define INTE_VIRTUALMPU_330 0x30000000 /* Capture at I/O base address 0x330 */ -#define INTE_MASTERDMAENABLE 0x08000000 /* Master DMA emulation at 0x000-0x00f */ -#define INTE_SLAVEDMAENABLE 0x04000000 /* Slave DMA emulation at 0x0c0-0x0df */ -#define INTE_MASTERPICENABLE 0x02000000 /* Master PIC emulation at 0x020-0x021 */ -#define INTE_SLAVEPICENABLE 0x01000000 /* Slave PIC emulation at 0x0a0-0x0a1 */ -#define INTE_VSBENABLE 0x00800000 /* Enable virtual Soundblaster */ -#define INTE_ADLIBENABLE 0x00400000 /* Enable AdLib emulation at 0x388-0x38b */ -#define INTE_MPUENABLE 0x00200000 /* Enable virtual MPU */ -#define INTE_FORCEINT 0x00100000 /* Continuously assert INTAN */ - -#define INTE_MRHANDENABLE 0x00080000 /* Enable the "Mr. Hand" logic */ - /* NOTE: There is no reason to use this under */ - /* Linux, and it will cause odd hardware */ - /* behavior and possibly random segfaults and */ - /* lockups if enabled. */ - -/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ -#define A_INTE_MIDITXENABLE2 0x00020000 /* Enable MIDI transmit-buffer-empty interrupts */ -#define A_INTE_MIDIRXENABLE2 0x00010000 /* Enable MIDI receive-buffer-empty interrupts */ - - -#define INTE_SAMPLERATETRACKER 0x00002000 /* Enable sample rate tracker interrupts */ - /* NOTE: This bit must always be enabled */ -#define INTE_FXDSPENABLE 0x00001000 /* Enable FX DSP interrupts */ -#define INTE_PCIERRORENABLE 0x00000800 /* Enable PCI bus error interrupts */ -#define INTE_VOLINCRENABLE 0x00000400 /* Enable volume increment button interrupts */ -#define INTE_VOLDECRENABLE 0x00000200 /* Enable volume decrement button interrupts */ -#define INTE_MUTEENABLE 0x00000100 /* Enable mute button interrupts */ -#define INTE_MICBUFENABLE 0x00000080 /* Enable microphone buffer interrupts */ -#define INTE_ADCBUFENABLE 0x00000040 /* Enable ADC buffer interrupts */ -#define INTE_EFXBUFENABLE 0x00000020 /* Enable Effects buffer interrupts */ -#define INTE_GPSPDIFENABLE 0x00000010 /* Enable GPSPDIF status interrupts */ -#define INTE_CDSPDIFENABLE 0x00000008 /* Enable CDSPDIF status interrupts */ -#define INTE_INTERVALTIMERENB 0x00000004 /* Enable interval timer interrupts */ -#define INTE_MIDITXENABLE 0x00000002 /* Enable MIDI transmit-buffer-empty interrupts */ -#define INTE_MIDIRXENABLE 0x00000001 /* Enable MIDI receive-buffer-empty interrupts */ - -/* The next two interrupts are for the midi port on the Audigy (A_MPU2) */ -#define A_INTE_MIDITXENABLE1 INTE_MIDITXENABLE -#define A_INTE_MIDIRXENABLE1 INTE_MIDIRXENABLE - -#define WC 0x10 /* Wall Clock register */ -#define WC_SAMPLECOUNTER_MASK 0x03FFFFC0 /* Sample periods elapsed since reset */ -#define WC_SAMPLECOUNTER 0x14060010 -#define WC_CURRENTCHANNEL 0x0000003F /* Channel [0..63] currently being serviced */ - /* NOTE: Each channel takes 1/64th of a sample */ - /* period to be serviced. */ - -#define HCFG 0x14 /* Hardware config register */ - /* NOTE: There is no reason to use the legacy */ - /* SoundBlaster emulation stuff described below */ - /* under Linux, and all kinds of weird hardware */ - /* behavior can result if you try. Don't. */ -#define HCFG_LEGACYFUNC_MASK 0xe0000000 /* Legacy function number */ -#define HCFG_LEGACYFUNC_MPU 0x00000000 /* Legacy MPU */ -#define HCFG_LEGACYFUNC_SB 0x40000000 /* Legacy SB */ -#define HCFG_LEGACYFUNC_AD 0x60000000 /* Legacy AD */ -#define HCFG_LEGACYFUNC_MPIC 0x80000000 /* Legacy MPIC */ -#define HCFG_LEGACYFUNC_MDMA 0xa0000000 /* Legacy MDMA */ -#define HCFG_LEGACYFUNC_SPCI 0xc0000000 /* Legacy SPCI */ -#define HCFG_LEGACYFUNC_SDMA 0xe0000000 /* Legacy SDMA */ -#define HCFG_IOCAPTUREADDR 0x1f000000 /* The 4 LSBs of the captured I/O address. */ -#define HCFG_LEGACYWRITE 0x00800000 /* 1 = write, 0 = read */ -#define HCFG_LEGACYWORD 0x00400000 /* 1 = word, 0 = byte */ -#define HCFG_LEGACYINT 0x00200000 /* 1 = legacy event captured. Write 1 to clear. */ - /* NOTE: The rest of the bits in this register */ - /* _are_ relevant under Linux. */ -#define HCFG_CODECFORMAT_MASK 0x00070000 /* CODEC format */ -#define HCFG_CODECFORMAT_AC97 0x00000000 /* AC97 CODEC format -- Primary Output */ -#define HCFG_CODECFORMAT_I2S 0x00010000 /* I2S CODEC format -- Secondary (Rear) Output */ -#define HCFG_GPINPUT0 0x00004000 /* External pin112 */ -#define HCFG_GPINPUT1 0x00002000 /* External pin110 */ - -#define HCFG_GPOUTPUT_MASK 0x00001c00 /* External pins which may be controlled */ -#define HCFG_GPOUT0 0x00001000 /* set to enable digital out on 5.1 cards */ - -#define HCFG_JOYENABLE 0x00000200 /* Internal joystick enable */ -#define HCFG_PHASETRACKENABLE 0x00000100 /* Phase tracking enable */ - /* 1 = Force all 3 async digital inputs to use */ - /* the same async sample rate tracker (ZVIDEO) */ -#define HCFG_AC3ENABLE_MASK 0x0x0000e0 /* AC3 async input control - Not implemented */ -#define HCFG_AC3ENABLE_ZVIDEO 0x00000080 /* Channels 0 and 1 replace ZVIDEO */ -#define HCFG_AC3ENABLE_CDSPDIF 0x00000040 /* Channels 0 and 1 replace CDSPDIF */ -#define HCFG_AC3ENABLE_GPSPDIF 0x00000020 /* Channels 0 and 1 replace GPSPDIF */ -#define HCFG_AUTOMUTE 0x00000010 /* When set, the async sample rate convertors */ - /* will automatically mute their output when */ - /* they are not rate-locked to the external */ - /* async audio source */ -#define HCFG_LOCKSOUNDCACHE 0x00000008 /* 1 = Cancel bustmaster accesses to soundcache */ - /* NOTE: This should generally never be used. */ -#define HCFG_LOCKTANKCACHE_MASK 0x00000004 /* 1 = Cancel bustmaster accesses to tankcache */ - /* NOTE: This should generally never be used. */ -#define HCFG_LOCKTANKCACHE 0x01020014 -#define HCFG_MUTEBUTTONENABLE 0x00000002 /* 1 = Master mute button sets AUDIOENABLE = 0. */ - /* NOTE: This is a 'cheap' way to implement a */ - /* master mute function on the mute button, and */ - /* in general should not be used unless a more */ - /* sophisticated master mute function has not */ - /* been written. */ -#define HCFG_AUDIOENABLE 0x00000001 /* 0 = CODECs transmit zero-valued samples */ - /* Should be set to 1 when the EMU10K1 is */ - /* completely initialized. */ - -//For Audigy, MPU port move to 0x70-0x74 ptr register - -#define MUDATA 0x18 /* MPU401 data register (8 bits) */ - -#define MUCMD 0x19 /* MPU401 command register (8 bits) */ -#define MUCMD_RESET 0xff /* RESET command */ -#define MUCMD_ENTERUARTMODE 0x3f /* Enter_UART_mode command */ - /* NOTE: All other commands are ignored */ - -#define MUSTAT MUCMD /* MPU401 status register (8 bits) */ -#define MUSTAT_IRDYN 0x80 /* 0 = MIDI data or command ACK */ -#define MUSTAT_ORDYN 0x40 /* 0 = MUDATA can accept a command or data */ - -#define A_IOCFG 0x18 /* GPIO on Audigy card (16bits) */ -#define A_GPINPUT_MASK 0xff00 -#define A_GPOUTPUT_MASK 0x00ff - -#define TIMER 0x1a /* Timer terminal count register (16-bit) */ - /* NOTE: After the rate is changed, a maximum */ - /* of 1024 sample periods should be allowed */ - /* before the new rate is guaranteed accurate. */ -#define TIMER_RATE_MASK 0x03ff /* Timer interrupt rate in sample periods */ - /* 0 == 1024 periods, [1..4] are not useful */ - -#define AC97DATA 0x1c /* AC97 register set data register (16 bit) */ - -#define AC97ADDRESS 0x1e /* AC97 register set address register (8 bit) */ -#define AC97ADDRESS_READY 0x80 /* Read-only bit, reflects CODEC READY signal */ -#define AC97ADDRESS_ADDRESS 0x7f /* Address of indexed AC97 register */ - -/********************************************************************************************************/ -/* Emu10k1 pointer-offset register set, accessed through the PTR and DATA registers */ -/********************************************************************************************************/ - -#define CPF 0x00 /* Current pitch and fraction register */ -#define CPF_CURRENTPITCH_MASK 0xffff0000 /* Current pitch (linear, 0x4000 == unity pitch shift) */ -#define CPF_CURRENTPITCH 0x10100000 -#define CPF_STEREO_MASK 0x00008000 /* 1 = Even channel interleave, odd channel locked */ -#define CPF_STOP_MASK 0x00004000 /* 1 = Current pitch forced to 0 */ -#define CPF_FRACADDRESS_MASK 0x00003fff /* Linear fractional address of the current channel */ - -#define PTRX 0x01 /* Pitch target and send A/B amounts register */ -#define PTRX_PITCHTARGET_MASK 0xffff0000 /* Pitch target of specified channel */ -#define PTRX_PITCHTARGET 0x10100001 -#define PTRX_FXSENDAMOUNT_A_MASK 0x0000ff00 /* Linear level of channel output sent to FX send bus A */ -#define PTRX_FXSENDAMOUNT_A 0x08080001 -#define PTRX_FXSENDAMOUNT_B_MASK 0x000000ff /* Linear level of channel output sent to FX send bus B */ -#define PTRX_FXSENDAMOUNT_B 0x08000001 - -#define CVCF 0x02 /* Current volume and filter cutoff register */ -#define CVCF_CURRENTVOL_MASK 0xffff0000 /* Current linear volume of specified channel */ -#define CVCF_CURRENTVOL 0x10100002 -#define CVCF_CURRENTFILTER_MASK 0x0000ffff /* Current filter cutoff frequency of specified channel */ -#define CVCF_CURRENTFILTER 0x10000002 - -#define VTFT 0x03 /* Volume target and filter cutoff target register */ -#define VTFT_VOLUMETARGET_MASK 0xffff0000 /* Volume target of specified channel */ -#define VTFT_FILTERTARGET_MASK 0x0000ffff /* Filter cutoff target of specified channel */ - -#define Z1 0x05 /* Filter delay memory 1 register */ - -#define Z2 0x04 /* Filter delay memory 2 register */ - -#define PSST 0x06 /* Send C amount and loop start address register */ -#define PSST_FXSENDAMOUNT_C_MASK 0xff000000 /* Linear level of channel output sent to FX send bus C */ - -#define PSST_FXSENDAMOUNT_C 0x08180006 - -#define PSST_LOOPSTARTADDR_MASK 0x00ffffff /* Loop start address of the specified channel */ -#define PSST_LOOPSTARTADDR 0x18000006 - -#define DSL 0x07 /* Send D amount and loop start address register */ -#define DSL_FXSENDAMOUNT_D_MASK 0xff000000 /* Linear level of channel output sent to FX send bus D */ - -#define DSL_FXSENDAMOUNT_D 0x08180007 - -#define DSL_LOOPENDADDR_MASK 0x00ffffff /* Loop end address of the specified channel */ -#define DSL_LOOPENDADDR 0x18000007 - -#define CCCA 0x08 /* Filter Q, interp. ROM, byte size, cur. addr register */ -#define CCCA_RESONANCE 0xf0000000 /* Lowpass filter resonance (Q) height */ -#define CCCA_INTERPROMMASK 0x0e000000 /* Selects passband of interpolation ROM */ - /* 1 == full band, 7 == lowpass */ - /* ROM 0 is used when pitch shifting downward or less */ - /* then 3 semitones upward. Increasingly higher ROM */ - /* numbers are used, typically in steps of 3 semitones, */ - /* as upward pitch shifting is performed. */ -#define CCCA_INTERPROM_0 0x00000000 /* Select interpolation ROM 0 */ -#define CCCA_INTERPROM_1 0x02000000 /* Select interpolation ROM 1 */ -#define CCCA_INTERPROM_2 0x04000000 /* Select interpolation ROM 2 */ -#define CCCA_INTERPROM_3 0x06000000 /* Select interpolation ROM 3 */ -#define CCCA_INTERPROM_4 0x08000000 /* Select interpolation ROM 4 */ -#define CCCA_INTERPROM_5 0x0a000000 /* Select interpolation ROM 5 */ -#define CCCA_INTERPROM_6 0x0c000000 /* Select interpolation ROM 6 */ -#define CCCA_INTERPROM_7 0x0e000000 /* Select interpolation ROM 7 */ -#define CCCA_8BITSELECT 0x01000000 /* 1 = Sound memory for this channel uses 8-bit samples */ -#define CCCA_CURRADDR_MASK 0x00ffffff /* Current address of the selected channel */ -#define CCCA_CURRADDR 0x18000008 - -#define CCR 0x09 /* Cache control register */ -#define CCR_CACHEINVALIDSIZE 0x07190009 -#define CCR_CACHEINVALIDSIZE_MASK 0xfe000000 /* Number of invalid samples cache for this channel */ -#define CCR_CACHELOOPFLAG 0x01000000 /* 1 = Cache has a loop service pending */ -#define CCR_INTERLEAVEDSAMPLES 0x00800000 /* 1 = A cache service will fetch interleaved samples */ -#define CCR_WORDSIZEDSAMPLES 0x00400000 /* 1 = A cache service will fetch word sized samples */ -#define CCR_READADDRESS 0x06100009 -#define CCR_READADDRESS_MASK 0x003f0000 /* Location of cache just beyond current cache service */ -#define CCR_LOOPINVALSIZE 0x0000fe00 /* Number of invalid samples in cache prior to loop */ - /* NOTE: This is valid only if CACHELOOPFLAG is set */ -#define CCR_LOOPFLAG 0x00000100 /* Set for a single sample period when a loop occurs */ -#define CCR_CACHELOOPADDRHI 0x000000ff /* DSL_LOOPSTARTADDR's hi byte if CACHELOOPFLAG is set */ - -#define CLP 0x0a /* Cache loop register (valid if CCR_CACHELOOPFLAG = 1) */ - /* NOTE: This register is normally not used */ -#define CLP_CACHELOOPADDR 0x0000ffff /* Cache loop address (DSL_LOOPSTARTADDR [0..15]) */ - -#define FXRT 0x0b /* Effects send routing register */ - /* NOTE: It is illegal to assign the same routing to */ - /* two effects sends. */ -#define FXRT_CHANNELA 0x000f0000 /* Effects send bus number for channel's effects send A */ -#define FXRT_CHANNELB 0x00f00000 /* Effects send bus number for channel's effects send B */ -#define FXRT_CHANNELC 0x0f000000 /* Effects send bus number for channel's effects send C */ -#define FXRT_CHANNELD 0xf0000000 /* Effects send bus number for channel's effects send D */ - -#define MAPA 0x0c /* Cache map A */ - -#define MAPB 0x0d /* Cache map B */ - -#define MAP_PTE_MASK 0xffffe000 /* The 19 MSBs of the PTE indexed by the PTI */ -#define MAP_PTI_MASK 0x00001fff /* The 13 bit index to one of the 8192 PTE dwords */ - -#define ENVVOL 0x10 /* Volume envelope register */ -#define ENVVOL_MASK 0x0000ffff /* Current value of volume envelope state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define ATKHLDV 0x11 /* Volume envelope hold and attack register */ -#define ATKHLDV_PHASE0 0x00008000 /* 0 = Begin attack phase */ -#define ATKHLDV_HOLDTIME_MASK 0x00007f00 /* Envelope hold time (127-n == n*88.2msec) */ -#define ATKHLDV_ATTACKTIME_MASK 0x0000007f /* Envelope attack time, log encoded */ - /* 0 = infinite, 1 = 10.9msec, ... 0x7f = 5.5msec */ - -#define DCYSUSV 0x12 /* Volume envelope sustain and decay register */ -#define DCYSUSV_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */ -#define DCYSUSV_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */ -#define DCYSUSV_CHANNELENABLE_MASK 0x00000080 /* 1 = Inhibit envelope engine from writing values in */ - /* this channel and from writing to pitch, filter and */ - /* volume targets. */ -#define DCYSUSV_DECAYTIME_MASK 0x0000007f /* Volume envelope decay time, log encoded */ - /* 0 = 43.7msec, 1 = 21.8msec, 0x7f = 22msec */ - -#define LFOVAL1 0x13 /* Modulation LFO value */ -#define LFOVAL_MASK 0x0000ffff /* Current value of modulation LFO state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define ENVVAL 0x14 /* Modulation envelope register */ -#define ENVVAL_MASK 0x0000ffff /* Current value of modulation envelope state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define ATKHLDM 0x15 /* Modulation envelope hold and attack register */ -#define ATKHLDM_PHASE0 0x00008000 /* 0 = Begin attack phase */ -#define ATKHLDM_HOLDTIME 0x00007f00 /* Envelope hold time (127-n == n*42msec) */ -#define ATKHLDM_ATTACKTIME 0x0000007f /* Envelope attack time, log encoded */ - /* 0 = infinite, 1 = 11msec, ... 0x7f = 5.5msec */ - -#define DCYSUSM 0x16 /* Modulation envelope decay and sustain register */ -#define DCYSUSM_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */ -#define DCYSUSM_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */ -#define DCYSUSM_DECAYTIME_MASK 0x0000007f /* Envelope decay time, log encoded */ - /* 0 = 43.7msec, 1 = 21.8msec, 0x7f = 22msec */ - -#define LFOVAL2 0x17 /* Vibrato LFO register */ -#define LFOVAL2_MASK 0x0000ffff /* Current value of vibrato LFO state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define IP 0x18 /* Initial pitch register */ -#define IP_MASK 0x0000ffff /* Exponential initial pitch shift */ - /* 4 bits of octave, 12 bits of fractional octave */ -#define IP_UNITY 0x0000e000 /* Unity pitch shift */ - -#define IFATN 0x19 /* Initial filter cutoff and attenuation register */ -#define IFATN_FILTERCUTOFF_MASK 0x0000ff00 /* Initial filter cutoff frequency in exponential units */ - /* 6 most significant bits are semitones */ - /* 2 least significant bits are fractions */ -#define IFATN_FILTERCUTOFF 0x08080019 -#define IFATN_ATTENUATION_MASK 0x000000ff /* Initial attenuation in 0.375dB steps */ -#define IFATN_ATTENUATION 0x08000019 - - -#define PEFE 0x1a /* Pitch envelope and filter envelope amount register */ -#define PEFE_PITCHAMOUNT_MASK 0x0000ff00 /* Pitch envlope amount */ - /* Signed 2's complement, +/- one octave peak extremes */ -#define PEFE_PITCHAMOUNT 0x0808001a -#define PEFE_FILTERAMOUNT_MASK 0x000000ff /* Filter envlope amount */ - /* Signed 2's complement, +/- six octaves peak extremes */ -#define PEFE_FILTERAMOUNT 0x0800001a -#define FMMOD 0x1b /* Vibrato/filter modulation from LFO register */ -#define FMMOD_MODVIBRATO 0x0000ff00 /* Vibrato LFO modulation depth */ - /* Signed 2's complement, +/- one octave extremes */ -#define FMMOD_MOFILTER 0x000000ff /* Filter LFO modulation depth */ - /* Signed 2's complement, +/- three octave extremes */ - - -#define TREMFRQ 0x1c /* Tremolo amount and modulation LFO frequency register */ -#define TREMFRQ_DEPTH 0x0000ff00 /* Tremolo depth */ - /* Signed 2's complement, with +/- 12dB extremes */ -#define TREMFRQ_FREQUENCY 0x000000ff /* Tremolo LFO frequency */ - /* ??Hz steps, maximum of ?? Hz. */ - -#define FM2FRQ2 0x1d /* Vibrato amount and vibrato LFO frequency register */ -#define FM2FRQ2_DEPTH 0x0000ff00 /* Vibrato LFO vibrato depth */ - /* Signed 2's complement, +/- one octave extremes */ -#define FM2FRQ2_FREQUENCY 0x000000ff /* Vibrato LFO frequency */ - /* 0.039Hz steps, maximum of 9.85 Hz. */ - -#define TEMPENV 0x1e /* Tempory envelope register */ -#define TEMPENV_MASK 0x0000ffff /* 16-bit value */ - /* NOTE: All channels contain internal variables; do */ - /* not write to these locations. */ - -#define CD0 0x20 /* Cache data 0 register */ -#define CD1 0x21 /* Cache data 1 register */ -#define CD2 0x22 /* Cache data 2 register */ -#define CD3 0x23 /* Cache data 3 register */ -#define CD4 0x24 /* Cache data 4 register */ -#define CD5 0x25 /* Cache data 5 register */ -#define CD6 0x26 /* Cache data 6 register */ -#define CD7 0x27 /* Cache data 7 register */ -#define CD8 0x28 /* Cache data 8 register */ -#define CD9 0x29 /* Cache data 9 register */ -#define CDA 0x2a /* Cache data A register */ -#define CDB 0x2b /* Cache data B register */ -#define CDC 0x2c /* Cache data C register */ -#define CDD 0x2d /* Cache data D register */ -#define CDE 0x2e /* Cache data E register */ -#define CDF 0x2f /* Cache data F register */ - -#define PTB 0x40 /* Page table base register */ -#define PTB_MASK 0xfffff000 /* Physical address of the page table in host memory */ - -#define TCB 0x41 /* Tank cache base register */ -#define TCB_MASK 0xfffff000 /* Physical address of the bottom of host based TRAM */ - -#define ADCCR 0x42 /* ADC sample rate/stereo control register */ -#define ADCCR_RCHANENABLE 0x00000010 /* Enables right channel for writing to the host */ -#define ADCCR_LCHANENABLE 0x00000008 /* Enables left channel for writing to the host */ - /* NOTE: To guarantee phase coherency, both channels */ - /* must be disabled prior to enabling both channels. */ -#define A_ADCCR_RCHANENABLE 0x00000020 -#define A_ADCCR_LCHANENABLE 0x00000010 - -#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate convertor output rate */ -#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate convertor output rate */ - -#define ADCCR_SAMPLERATE_48 0x00000000 /* 48kHz sample rate */ -#define ADCCR_SAMPLERATE_44 0x00000001 /* 44.1kHz sample rate */ -#define ADCCR_SAMPLERATE_32 0x00000002 /* 32kHz sample rate */ -#define ADCCR_SAMPLERATE_24 0x00000003 /* 24kHz sample rate */ -#define ADCCR_SAMPLERATE_22 0x00000004 /* 22.05kHz sample rate */ -#define ADCCR_SAMPLERATE_16 0x00000005 /* 16kHz sample rate */ -#define ADCCR_SAMPLERATE_11 0x00000006 /* 11.025kHz sample rate */ -#define ADCCR_SAMPLERATE_8 0x00000007 /* 8kHz sample rate */ - -#define A_ADCCR_SAMPLERATE_12 0x00000006 /* 12kHz sample rate */ -#define A_ADCCR_SAMPLERATE_11 0x00000007 /* 11.025kHz sample rate */ -#define A_ADCCR_SAMPLERATE_8 0x00000008 /* 8kHz sample rate */ - -#define FXWC 0x43 /* FX output write channels register */ - /* When set, each bit enables the writing of the */ - /* corresponding FX output channel (internal registers */ - /* 0x20-0x3f) into host memory. This mode of recording */ - /* is 16bit, 48KHz only. All 32 channels can be enabled */ - /* simultaneously. */ -#define TCBS 0x44 /* Tank cache buffer size register */ -#define TCBS_MASK 0x00000007 /* Tank cache buffer size field */ -#define TCBS_BUFFSIZE_16K 0x00000000 -#define TCBS_BUFFSIZE_32K 0x00000001 -#define TCBS_BUFFSIZE_64K 0x00000002 -#define TCBS_BUFFSIZE_128K 0x00000003 -#define TCBS_BUFFSIZE_256K 0x00000004 -#define TCBS_BUFFSIZE_512K 0x00000005 -#define TCBS_BUFFSIZE_1024K 0x00000006 -#define TCBS_BUFFSIZE_2048K 0x00000007 - -#define MICBA 0x45 /* AC97 microphone buffer address register */ -#define MICBA_MASK 0xfffff000 /* 20 bit base address */ - -#define ADCBA 0x46 /* ADC buffer address register */ -#define ADCBA_MASK 0xfffff000 /* 20 bit base address */ - -#define FXBA 0x47 /* FX Buffer Address */ -#define FXBA_MASK 0xfffff000 /* 20 bit base address */ - -#define MICBS 0x49 /* Microphone buffer size register */ - -#define ADCBS 0x4a /* ADC buffer size register */ - -#define FXBS 0x4b /* FX buffer size register */ - -/* The following mask values define the size of the ADC, MIX and FX buffers in bytes */ -#define ADCBS_BUFSIZE_NONE 0x00000000 -#define ADCBS_BUFSIZE_384 0x00000001 -#define ADCBS_BUFSIZE_448 0x00000002 -#define ADCBS_BUFSIZE_512 0x00000003 -#define ADCBS_BUFSIZE_640 0x00000004 -#define ADCBS_BUFSIZE_768 0x00000005 -#define ADCBS_BUFSIZE_896 0x00000006 -#define ADCBS_BUFSIZE_1024 0x00000007 -#define ADCBS_BUFSIZE_1280 0x00000008 -#define ADCBS_BUFSIZE_1536 0x00000009 -#define ADCBS_BUFSIZE_1792 0x0000000a -#define ADCBS_BUFSIZE_2048 0x0000000b -#define ADCBS_BUFSIZE_2560 0x0000000c -#define ADCBS_BUFSIZE_3072 0x0000000d -#define ADCBS_BUFSIZE_3584 0x0000000e -#define ADCBS_BUFSIZE_4096 0x0000000f -#define ADCBS_BUFSIZE_5120 0x00000010 -#define ADCBS_BUFSIZE_6144 0x00000011 -#define ADCBS_BUFSIZE_7168 0x00000012 -#define ADCBS_BUFSIZE_8192 0x00000013 -#define ADCBS_BUFSIZE_10240 0x00000014 -#define ADCBS_BUFSIZE_12288 0x00000015 -#define ADCBS_BUFSIZE_14366 0x00000016 -#define ADCBS_BUFSIZE_16384 0x00000017 -#define ADCBS_BUFSIZE_20480 0x00000018 -#define ADCBS_BUFSIZE_24576 0x00000019 -#define ADCBS_BUFSIZE_28672 0x0000001a -#define ADCBS_BUFSIZE_32768 0x0000001b -#define ADCBS_BUFSIZE_40960 0x0000001c -#define ADCBS_BUFSIZE_49152 0x0000001d -#define ADCBS_BUFSIZE_57344 0x0000001e -#define ADCBS_BUFSIZE_65536 0x0000001f - - -#define CDCS 0x50 /* CD-ROM digital channel status register */ - -#define GPSCS 0x51 /* General Purpose SPDIF channel status register*/ - -#define DBG 0x52 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */ - -/* definitions for debug register - taken from the alsa drivers */ -#define DBG_ZC 0x80000000 /* zero tram counter */ -#define DBG_SATURATION_OCCURED 0x02000000 /* saturation control */ -#define DBG_SATURATION_ADDR 0x01ff0000 /* saturation address */ -#define DBG_SINGLE_STEP 0x00008000 /* single step mode */ -#define DBG_STEP 0x00004000 /* start single step */ -#define DBG_CONDITION_CODE 0x00003e00 /* condition code */ -#define DBG_SINGLE_STEP_ADDR 0x000001ff /* single step address */ - - -#define REG53 0x53 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */ - -#define A_DBG 0x53 -#define A_DBG_SINGLE_STEP 0x00020000 /* Set to zero to start dsp */ -#define A_DBG_ZC 0x40000000 /* zero tram counter */ -#define A_DBG_STEP_ADDR 0x000003ff -#define A_DBG_SATURATION_OCCURED 0x20000000 -#define A_DBG_SATURATION_ADDR 0x0ffc0000 - -#define SPCS0 0x54 /* SPDIF output Channel Status 0 register */ - -#define SPCS1 0x55 /* SPDIF output Channel Status 1 register */ - -#define SPCS2 0x56 /* SPDIF output Channel Status 2 register */ - -#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */ -#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */ -#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */ -#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */ -#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */ -#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */ -#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */ -#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */ -#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */ -#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */ -#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */ -#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */ -#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */ -#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */ -#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */ -#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */ -#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */ -#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */ -#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */ -#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */ -#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */ -#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */ -#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */ - -/* The 32-bit CLIx and SOLx registers all have one bit per channel control/status */ -#define CLIEL 0x58 /* Channel loop interrupt enable low register */ - -#define CLIEH 0x59 /* Channel loop interrupt enable high register */ - -#define CLIPL 0x5a /* Channel loop interrupt pending low register */ - -#define CLIPH 0x5b /* Channel loop interrupt pending high register */ - -#define SOLEL 0x5c /* Stop on loop enable low register */ - -#define SOLEH 0x5d /* Stop on loop enable high register */ - -#define SPBYPASS 0x5e /* SPDIF BYPASS mode register */ -#define SPBYPASS_ENABLE 0x00000001 /* Enable SPDIF bypass mode */ - -#define AC97SLOT 0x5f /* additional AC97 slots enable bits */ -#define AC97SLOT_CNTR 0x10 /* Center enable */ -#define AC97SLOT_LFE 0x20 /* LFE enable */ - -#define CDSRCS 0x60 /* CD-ROM Sample Rate Converter status register */ - -#define GPSRCS 0x61 /* General Purpose SPDIF sample rate cvt status */ - -#define ZVSRCS 0x62 /* ZVideo sample rate converter status */ - /* NOTE: This one has no SPDIFLOCKED field */ - /* Assumes sample lock */ - -/* These three bitfields apply to CDSRCS, GPSRCS, and (except as noted) ZVSRCS. */ -#define SRCS_SPDIFLOCKED 0x02000000 /* SPDIF stream locked */ -#define SRCS_RATELOCKED 0x01000000 /* Sample rate locked */ -#define SRCS_ESTSAMPLERATE 0x0007ffff /* Do not modify this field. */ - - -/* Note that these values can vary +/- by a small amount */ -#define SRCS_SPDIFRATE_44 0x0003acd9 -#define SRCS_SPDIFRATE_48 0x00040000 -#define SRCS_SPDIFRATE_96 0x00080000 - -#define MICIDX 0x63 /* Microphone recording buffer index register */ -#define MICIDX_MASK 0x0000ffff /* 16-bit value */ -#define MICIDX_IDX 0x10000063 - -#define A_ADCIDX 0x63 -#define A_ADCIDX_IDX 0x10000063 - -#define ADCIDX 0x64 /* ADC recording buffer index register */ -#define ADCIDX_MASK 0x0000ffff /* 16 bit index field */ -#define ADCIDX_IDX 0x10000064 - -#define FXIDX 0x65 /* FX recording buffer index register */ -#define FXIDX_MASK 0x0000ffff /* 16-bit value */ -#define FXIDX_IDX 0x10000065 - -/* This is the MPU port on the card (via the game port) */ -#define A_MUDATA1 0x70 -#define A_MUCMD1 0x71 -#define A_MUSTAT1 A_MUCMD1 - -/* This is the MPU port on the Audigy Drive */ -#define A_MUDATA2 0x72 -#define A_MUCMD2 0x73 -#define A_MUSTAT2 A_MUCMD2 - -/* The next two are the Audigy equivalent of FXWC */ -/* the Audigy can record any output (16bit, 48kHz, up to 64 channel simultaneously) */ -/* Each bit selects a channel for recording */ -#define A_FXWC1 0x74 /* Selects 0x7f-0x60 for FX recording */ -#define A_FXWC2 0x75 /* Selects 0x9f-0x80 for FX recording */ - -#define A_SPDIF_SAMPLERATE 0x76 /* Set the sample rate of SPDIF output */ -#define A_SPDIF_48000 0x00000080 -#define A_SPDIF_44100 0x00000000 -#define A_SPDIF_96000 0x00000040 - -#define A_FXRT2 0x7c -#define A_FXRT_CHANNELE 0x0000003f /* Effects send bus number for channel's effects send E */ -#define A_FXRT_CHANNELF 0x00003f00 /* Effects send bus number for channel's effects send F */ -#define A_FXRT_CHANNELG 0x003f0000 /* Effects send bus number for channel's effects send G */ -#define A_FXRT_CHANNELH 0x3f000000 /* Effects send bus number for channel's effects send H */ - -#define A_SENDAMOUNTS 0x7d -#define A_FXSENDAMOUNT_E_MASK 0xff000000 -#define A_FXSENDAMOUNT_F_MASK 0x00ff0000 -#define A_FXSENDAMOUNT_G_MASK 0x0000ff00 -#define A_FXSENDAMOUNT_H_MASK 0x000000ff - -/* The send amounts for this one are the same as used with the emu10k1 */ -#define A_FXRT1 0x7e -#define A_FXRT_CHANNELA 0x0000003f -#define A_FXRT_CHANNELB 0x00003f00 -#define A_FXRT_CHANNELC 0x003f0000 -#define A_FXRT_CHANNELD 0x3f000000 - - -/* Each FX general purpose register is 32 bits in length, all bits are used */ -#define FXGPREGBASE 0x100 /* FX general purpose registers base */ -#define A_FXGPREGBASE 0x400 /* Audigy GPRs, 0x400 to 0x5ff */ -/* Tank audio data is logarithmically compressed down to 16 bits before writing to TRAM and is */ -/* decompressed back to 20 bits on a read. There are a total of 160 locations, the last 32 */ -/* locations are for external TRAM. */ -#define TANKMEMDATAREGBASE 0x200 /* Tank memory data registers base */ -#define TANKMEMDATAREG_MASK 0x000fffff /* 20 bit tank audio data field */ - -/* Combined address field and memory opcode or flag field. 160 locations, last 32 are external */ -#define TANKMEMADDRREGBASE 0x300 /* Tank memory address registers base */ -#define TANKMEMADDRREG_ADDR_MASK 0x000fffff /* 20 bit tank address field */ -#define TANKMEMADDRREG_CLEAR 0x00800000 /* Clear tank memory */ -#define TANKMEMADDRREG_ALIGN 0x00400000 /* Align read or write relative to tank access */ -#define TANKMEMADDRREG_WRITE 0x00200000 /* Write to tank memory */ -#define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */ - -#define MICROCODEBASE 0x400 /* Microcode data base address */ - -/* Each DSP microcode instruction is mapped into 2 doublewords */ -/* NOTE: When writing, always write the LO doubleword first. Reads can be in either order. */ -#define LOWORD_OPX_MASK 0x000ffc00 /* Instruction operand X */ -#define LOWORD_OPY_MASK 0x000003ff /* Instruction operand Y */ -#define HIWORD_OPCODE_MASK 0x00f00000 /* Instruction opcode */ -#define HIWORD_RESULT_MASK 0x000ffc00 /* Instruction result */ -#define HIWORD_OPA_MASK 0x000003ff /* Instruction operand A */ - - -/* Audigy Soundcard have a different instruction format */ -#define AUDIGY_CODEBASE 0x600 -#define A_LOWORD_OPY_MASK 0x000007ff -#define A_LOWORD_OPX_MASK 0x007ff000 -#define A_HIWORD_OPCODE_MASK 0x0f000000 -#define A_HIWORD_RESULT_MASK 0x007ff000 -#define A_HIWORD_OPA_MASK 0x000007ff - - -#endif /* _8010_H */ --- sys/gnu/dev/sound/pci/p16v-alsa.h.orig Thu Jan 1 07:30:00 1970 +++ sys/gnu/dev/sound/pci/p16v-alsa.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,301 @@ +/*- + * Copyright (c) by James Courtier-Dutton + * Driver p16v chips + * Version: 0.21 + * + * FEATURES currently supported: + * Output fixed at S32_LE, 2 channel to hw:0,0 + * Rates: 44.1, 48, 96, 192. + * + * Changelog: + * 0.8 + * Use separate card based buffer for periods table. + * 0.9 + * Use 2 channel output streams instead of 8 channel. + * (8 channel output streams might be good for ASIO type output) + * Corrected speaker output, so Front -> Front etc. + * 0.10 + * Fixed missed interrupts. + * 0.11 + * Add Sound card model number and names. + * Add Analog volume controls. + * 0.12 + * Corrected playback interrupts. Now interrupt per period, instead of half period. + * 0.13 + * Use single trigger for multichannel. + * 0.14 + * Mic capture now works at fixed: S32_LE, 96000Hz, Stereo. + * 0.15 + * Force buffer_size / period_size == INTEGER. + * 0.16 + * Update p16v.c to work with changed alsa api. + * 0.17 + * Update p16v.c to work with changed alsa api. Removed boot_devs. + * 0.18 + * Merging with snd-emu10k1 driver. + * 0.19 + * One stereo channel at 24bit now works. + * 0.20 + * Added better register defines. + * 0.21 + * Split from p16v.c + * + * + * BUGS: + * Some stability problems when unloading the snd-p16v kernel module. + * -- + * + * TODO: + * SPDIF out. + * Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz. + * Currently capture fixed at 48000Hz. + * + * -- + * GENERAL INFO: + * Model: SB0240 + * P16V Chip: CA0151-DBS + * Audigy 2 Chip: CA0102-IAT + * AC97 Codec: STAC 9721 + * ADC: Philips 1361T (Stereo 24bit) + * DAC: CS4382-K (8-channel, 24bit, 192Khz) + * + * This code was initally based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* $FreeBSD: src/sys/gnu/dev/sound/pci/p16v-alsa.h,v 1.1 2006/07/15 19:36:28 netchild Exp $ */ + +/********************************************************************************************************/ +/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */ +/********************************************************************************************************/ + +/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE. + * The sample rate is also controlled by the same registers that control the rate of the EMU10K2 sample rate converters. + */ + +/* Initally all registers from 0x00 to 0x3f have zero contents. */ +#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */ + /* One list entry: 4 bytes for DMA address, + * 4 bytes for period_size << 16. + * One list entry is 8 bytes long. + * One list entry for each period in the buffer. + */ +#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */ +#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */ +#define PLAYBACK_UNKNOWN3 0x03 /* Not used */ +#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA addresss */ +#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */ +#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */ +#define PLAYBACK_FIFO_END_ADDRESS 0x07 /* Playback FIFO end address */ +#define PLAYBACK_FIFO_POINTER 0x08 /* Playback FIFO pointer and number of valid sound samples in cache */ +#define PLAYBACK_UNKNOWN9 0x09 /* Not used */ +#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */ +#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */ +#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */ +#define CAPTURE_FIFO_POINTER 0x13 /* Capture FIFO pointer and number of valid sound samples in cache */ +#define CAPTURE_P16V_VOLUME1 0x14 /* Low: Capture volume 0xXXXX3030 */ +#define CAPTURE_P16V_VOLUME2 0x15 /* High:Has no effect on capture volume */ +#define CAPTURE_P16V_SOURCE 0x16 /* P16V source select. Set to 0x0700E4E5 for AC97 CAPTURE */ + /* [0:1] Capture input 0 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [3:2] Capture input 1 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [5:4] Capture input 2 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [7:6] Capture input 3 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [9:8] Playback input 0 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [11:10] Playback input 1 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [13:12] Playback input 2 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [15:14] Playback input 3 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [19:16] Playback mixer output enable. 1 bit per channel. + * [23:20] Capture mixer output enable. 1 bit per channel. + * [26:24] FX engine channel capture 0 = 0x60-0x67. + * 1 = 0x68-0x6f. + * 2 = 0x70-0x77. + * 3 = 0x78-0x7f. + * 4 = 0x80-0x87. + * 5 = 0x88-0x8f. + * 6 = 0x90-0x97. + * 7 = 0x98-0x9f. + * [31:27] Not used. + */ + + /* 0x1 = capture on. + * 0x100 = capture off. + * 0x200 = capture off. + * 0x1000 = capture off. + */ +#define CAPTURE_RATE_STATUS 0x17 /* Capture sample rate. Read only */ + /* [15:0] Not used. + * [18:16] Channel 0 Detected sample rate. 0 - 44.1khz + * 1 - 48 khz + * 2 - 96 khz + * 3 - 192 khz + * 7 - undefined rate. + * [19] Channel 0. 1 - Valid, 0 - Not Valid. + * [22:20] Channel 1 Detected sample rate. + * [23] Channel 1. 1 - Valid, 0 - Not Valid. + * [26:24] Channel 2 Detected sample rate. + * [27] Channel 2. 1 - Valid, 0 - Not Valid. + * [30:28] Channel 3 Detected sample rate. + * [31] Channel 3. 1 - Valid, 0 - Not Valid. + */ +/* 0x18 - 0x1f unused */ +#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played. Read only */ +/* 0x21 - 0x3f unused */ +#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */ + /* Playback (0x1< 77770000 so it must be some sort of route. + * bit 0x1 starts DMA playback on channel_id 0 + */ +/* 0x41,42 take values from 0 - 0xffffffff, but have no effect on playback */ +/* 0x43,0x48 do not remember settings */ +/* 0x41-45 unused */ +#define WATERMARK 0x46 /* Test bit to indicate cache level usage */ + /* Values it can have while playing on channel 0. + * 0000f000, 0000f004, 0000f008, 0000f00c. + * Readonly. + */ +/* 0x47-0x4f unused */ +/* 0x50-0x5f Capture cache data */ +#define SRCSel 0x60 /* SRCSel. Default 0x4. Bypass P16V 0x14 */ + /* [0] 0 = 10K2 audio, 1 = SRC48 mixer output. + * [2] 0 = 10K2 audio, 1 = SRCMulti SPDIF mixer output. + * [4] 0 = 10K2 audio, 1 = SRCMulti I2S mixer output. + */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* SRC48 and SRCMULTI sample rate select and output select. */ + /* 0xffffffff -> 0xC0000015 + * 0xXXXXXXX4 = Enable Front Left/Right + * Enable PCMs + */ + +/* 0x61 -> 0x6c are Volume controls */ +#define PLAYBACK_VOLUME_MIXER1 0x61 /* SRC48 Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER2 0x62 /* SRC48 High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER3 0x63 /* SRCMULTI SPDIF Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER4 0x64 /* SRCMULTI SPDIF High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER5 0x65 /* SRCMULTI I2S Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER6 0x66 /* SRCMULTI I2S High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER7 0x67 /* P16V Low to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER8 0x68 /* P16V High to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER9 0x69 /* P16V Low to SRCMULTI I2S mixer input volume control. */ + /* 0xXXXX3030 = PCM0 Volume (Front). + * 0x3030XXXX = PCM1 Volume (Center) + */ +#define PLAYBACK_VOLUME_MIXER10 0x6a /* P16V High to SRCMULTI I2S mixer input volume control. */ + /* 0x3030XXXX = PCM3 Volume (Rear). */ +#define PLAYBACK_VOLUME_MIXER11 0x6b /* E10K2 Low to SRC48 mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER12 0x6c /* E10K2 High to SRC48 mixer input volume control. */ + +#define SRC48_ENABLE 0x6d /* SRC48 input audio enable */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* [23:16] The corresponding P16V channel to SRC48 enabled if == 1. + * [31:24] The corresponding E10K2 channel to SRC48 enabled. + */ +#define SRCMULTI_ENABLE 0x6e /* SRCMulti input audio enable. Default 0xffffffff */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* [7:0] The corresponding P16V channel to SRCMulti_I2S enabled if == 1. + * [15:8] The corresponding E10K2 channel to SRCMulti I2S enabled. + * [23:16] The corresponding P16V channel to SRCMulti SPDIF enabled. + * [31:24] The corresponding E10K2 channel to SRCMulti SPDIF enabled. + */ + /* Bypass P16V 0xff00ff00 + * Bitmap. 0 = Off, 1 = On. + * P16V playback outputs: + * 0xXXXXXXX1 = PCM0 Left. (Front) + * 0xXXXXXXX2 = PCM0 Right. + * 0xXXXXXXX4 = PCM1 Left. (Center/LFE) + * 0xXXXXXXX8 = PCM1 Right. + * 0xXXXXXX1X = PCM2 Left. (Unknown) + * 0xXXXXXX2X = PCM2 Right. + * 0xXXXXXX4X = PCM3 Left. (Rear) + * 0xXXXXXX8X = PCM3 Right. + */ +#define AUDIO_OUT_ENABLE 0x6f /* Default: 000100FF */ + /* [3:0] Does something, but not documented. Probably capture enable. + * [7:4] Playback channels enable. not documented. + * [16] AC97 output enable if == 1 + * [30] 0 = SRCMulti_I2S input from fxengine 0x68-0x6f. + * 1 = SRCMulti_I2S input from SRC48 output. + * [31] 0 = SRCMulti_SPDIF input from fxengine 0x60-0x67. + * 1 = SRCMulti_SPDIF input from SRC48 output. + */ + /* 0xffffffff -> C00100FF */ + /* 0 -> Not playback sound, irq still running */ + /* 0xXXXXXX10 = PCM0 Left/Right On. (Front) + * 0xXXXXXX20 = PCM1 Left/Right On. (Center/LFE) + * 0xXXXXXX40 = PCM2 Left/Right On. (Unknown) + * 0xXXXXXX80 = PCM3 Left/Right On. (Rear) + */ +#define PLAYBACK_SPDIF_SELECT 0x70 /* Default: 12030F00 */ + /* 0xffffffff -> 3FF30FFF */ + /* 0x00000001 pauses stream/irq fail. */ + /* All other bits do not effect playback */ +#define PLAYBACK_SPDIF_SRC_SELECT 0x71 /* Default: 0000E4E4 */ + /* 0xffffffff -> F33FFFFF */ + /* All bits do not effect playback */ +#define PLAYBACK_SPDIF_USER_DATA0 0x72 /* SPDIF out user data 0 */ +#define PLAYBACK_SPDIF_USER_DATA1 0x73 /* SPDIF out user data 1 */ +/* 0x74-0x75 unknown */ +#define CAPTURE_SPDIF_CONTROL 0x76 /* SPDIF in control setting */ +#define CAPTURE_SPDIF_STATUS 0x77 /* SPDIF in status */ +#define CAPURE_SPDIF_USER_DATA0 0x78 /* SPDIF in user data 0 */ +#define CAPURE_SPDIF_USER_DATA1 0x79 /* SPDIF in user data 1 */ +#define CAPURE_SPDIF_USER_DATA2 0x7a /* SPDIF in user data 2 */ + --- sys/gnu/dev/sound/pci/p17v-alsa.h.orig Thu Jan 1 07:30:00 1970 +++ sys/gnu/dev/sound/pci/p17v-alsa.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,113 @@ +/*- + * Copyright (c) by James Courtier-Dutton + * Driver p17v chips + * Version: 0.01 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* $FreeBSD: src/sys/gnu/dev/sound/pci/p17v-alsa.h,v 1.1 2006/07/15 19:36:28 netchild Exp $ */ + +/******************************************************************************/ +/* Audigy2Value Tina (P17V) pointer-offset register set, + * accessed through the PTR20 and DATA24 registers */ +/******************************************************************************/ + +/* 00 - 07: Not used */ +#define P17V_PLAYBACK_FIFO_PTR 0x08 /* Current playback fifo pointer + * and number of sound samples in cache. + */ +/* 09 - 12: Not used */ +#define P17V_CAPTURE_FIFO_PTR 0x13 /* Current capture fifo pointer + * and number of sound samples in cache. + */ +/* 14 - 17: Not used */ +#define P17V_PB_CHN_SEL 0x18 /* P17v playback channel select */ +#define P17V_SE_SLOT_SEL_L 0x19 /* Sound Engine slot select low */ +#define P17V_SE_SLOT_SEL_H 0x1a /* Sound Engine slot select high */ +/* 1b - 1f: Not used */ +/* 20 - 2f: Not used */ +/* 30 - 3b: Not used */ +#define P17V_SPI 0x3c /* SPI interface register */ +#define P17V_I2C_ADDR 0x3d /* I2C Address */ +#define P17V_I2C_0 0x3e /* I2C Data */ +#define P17V_I2C_1 0x3f /* I2C Data */ + +#define P17V_START_AUDIO 0x40 /* Start Audio bit */ +/* 41 - 47: Reserved */ +#define P17V_START_CAPTURE 0x48 /* Start Capture bit */ +#define P17V_CAPTURE_FIFO_BASE 0x49 /* Record FIFO base address */ +#define P17V_CAPTURE_FIFO_SIZE 0x4a /* Record FIFO buffer size */ +#define P17V_CAPTURE_FIFO_INDEX 0x4b /* Record FIFO capture index */ +#define P17V_CAPTURE_VOL_H 0x4c /* P17v capture volume control */ +#define P17V_CAPTURE_VOL_L 0x4d /* P17v capture volume control */ +/* 4e - 4f: Not used */ +/* 50 - 5f: Not used */ +#define P17V_SRCSel 0x60 /* SRC48 and SRCMulti sample rate select + * and output select + */ +#define P17V_MIXER_AC97_10K1_VOL_L 0x61 /* 10K to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_10K1_VOL_H 0x62 /* 10K to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_P17V_VOL_L 0x63 /* P17V to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_P17V_VOL_H 0x64 /* P17V to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_SRP_REC_VOL_L 0x65 /* SRP Record to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_SRP_REC_VOL_H 0x66 /* SRP Record to Mixer_AC97 input volume control */ +/* 67 - 68: Reserved */ +#define P17V_MIXER_Spdif_10K1_VOL_L 0x69 /* 10K to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_10K1_VOL_H 0x6A /* 10K to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_P17V_VOL_L 0x6B /* P17V to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_P17V_VOL_H 0x6C /* P17V to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_SRP_REC_VOL_L 0x6D /* SRP Record to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_SRP_REC_VOL_H 0x6E /* SRP Record to Mixer_Spdif input volume control */ +/* 6f - 70: Reserved */ +#define P17V_MIXER_I2S_10K1_VOL_L 0x71 /* 10K to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_10K1_VOL_H 0x72 /* 10K to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_P17V_VOL_L 0x73 /* P17V to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_P17V_VOL_H 0x74 /* P17V to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_SRP_REC_VOL_L 0x75 /* SRP Record to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_SRP_REC_VOL_H 0x76 /* SRP Record to Mixer_I2S input volume control */ +/* 77 - 78: Reserved */ +#define P17V_MIXER_AC97_ENABLE 0x79 /* Mixer AC97 input audio enable */ +#define P17V_MIXER_SPDIF_ENABLE 0x7A /* Mixer SPDIF input audio enable */ +#define P17V_MIXER_I2S_ENABLE 0x7B /* Mixer I2S input audio enable */ +#define P17V_AUDIO_OUT_ENABLE 0x7C /* Audio out enable */ +#define P17V_MIXER_ATT 0x7D /* SRP Mixer Attenuation Select */ +#define P17V_SRP_RECORD_SRR 0x7E /* SRP Record channel source Select */ +#define P17V_SOFT_RESET_SRP_MIXER 0x7F /* SRP and mixer soft reset */ + +#define P17V_AC97_OUT_MASTER_VOL_L 0x80 /* AC97 Output master volume control */ +#define P17V_AC97_OUT_MASTER_VOL_H 0x81 /* AC97 Output master volume control */ +#define P17V_SPDIF_OUT_MASTER_VOL_L 0x82 /* SPDIF Output master volume control */ +#define P17V_SPDIF_OUT_MASTER_VOL_H 0x83 /* SPDIF Output master volume control */ +#define P17V_I2S_OUT_MASTER_VOL_L 0x84 /* I2S Output master volume control */ +#define P17V_I2S_OUT_MASTER_VOL_H 0x85 /* I2S Output master volume control */ +/* 86 - 87: Not used */ +#define P17V_I2S_CHANNEL_SWAP_PHASE_INVERSE 0x88 /* I2S out mono channel swap + * and phase inverse */ +#define P17V_SPDIF_CHANNEL_SWAP_PHASE_INVERSE 0x89 /* SPDIF out mono channel swap + * and phase inverse */ +/* 8A: Not used */ +#define P17V_SRP_P17V_ESR 0x8B /* SRP_P17V estimated sample rate and rate lock */ +#define P17V_SRP_REC_ESR 0x8C /* SRP_REC estimated sample rate and rate lock */ +#define P17V_SRP_BYPASS 0x8D /* srps channel bypass and srps bypass */ +/* 8E - 92: Not used */ +#define P17V_I2S_SRC_SEL 0x93 /* I2SIN mode sel */ + + + + + + --- sys/modules/sound/driver/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/Makefile Thu Jul 12 12:04:19 2007 @@ -1,12 +1,14 @@ -# $FreeBSD: src/sys/modules/sound/driver/Makefile,v 1.16.2.2 2007/05/13 21:11:40 ariff Exp $ +# $FreeBSD: src/sys/modules/sound/driver/Makefile,v 1.24 2006/10/01 11:18:56 ariff Exp $ .if ${MACHINE_ARCH} == "sparc64" SUBDIR = audiocs es137x .else -SUBDIR = als4000 ad1816 atiixp cmi cs4281 csa ds1 emu10k1 envy24 +SUBDIR = ad1816 als4000 atiixp cmi cs4281 csa ds1 emu10k1 emu10kx envy24 SUBDIR += envy24ht es137x ess fm801 hda ich maestro maestro3 mss neomagic SUBDIR += sb16 sb8 sbc solo spicds t4dwave via8233 via82c686 vibes SUBDIR += driver uaudio .endif + +SUBDIR += null .include --- sys/modules/sound/driver/atiixp/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/atiixp/Makefile Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -# $FreeBSD: src/sys/modules/sound/driver/atiixp/Makefile,v 1.1.2.1 2005/12/30 19:55:55 netchild Exp $ +# $FreeBSD: src/sys/modules/sound/driver/atiixp/Makefile,v 1.1 2005/11/27 03:29:59 ariff Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci --- sys/modules/sound/driver/cmi/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/cmi/Makefile Thu Jul 12 12:04:19 2007 @@ -1,9 +1,10 @@ -# $FreeBSD: src/sys/modules/sound/driver/cmi/Makefile,v 1.3 2003/02/07 13:56:31 nyan Exp $ +# $FreeBSD: src/sys/modules/sound/driver/cmi/Makefile,v 1.4 2006/05/27 16:32:04 netchild Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci KMOD= snd_cmi SRCS= device_if.h bus_if.h pci_if.h +SRCS+= mpufoi_if.h SRCS+= cmi.c .include --- sys/modules/sound/driver/emu10k1/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/emu10k1/Makefile Thu Jul 12 12:04:19 2007 @@ -1,10 +1,11 @@ -# $FreeBSD: src/sys/modules/sound/driver/emu10k1/Makefile,v 1.4 2004/01/11 10:30:56 obrien Exp $ +# $FreeBSD: src/sys/modules/sound/driver/emu10k1/Makefile,v 1.5 2006/05/27 16:32:04 netchild Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci \ ${.CURDIR}/../../../../gnu/dev/sound/pci KMOD= snd_emu10k1 SRCS= device_if.h bus_if.h pci_if.h emu10k1-alsa%diked.h +SRCS+= mpufoi_if.h SRCS+= emu10k1.c CLEANFILES+= emu10k1-alsa%diked.h --- sys/modules/sound/driver/emu10kx/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/emu10kx/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,51 @@ +# $FreeBSD: src/sys/modules/sound/driver/emu10kx/Makefile,v 1.3 2007/01/07 19:43:59 netchild Exp $ +.PATH: ${.CURDIR}/../../../../dev/sound/pci \ + ${.CURDIR}/../../../../gnu/dev/sound/pci + +WARNS?= 2 ## because sound is WARNS=2 only +## WARNS=3 fails on _class.refs in -pcm.c +## WARNS=4 fails on min/max in sound headers +## otherwise it should be WARNS=6 clean +KMOD= snd_emu10kx + +SRCS= device_if.h bus_if.h pci_if.h +SRCS+= isa_if.h channel_if.h ac97_if.h mixer_if.h mpufoi_if.h +SRCS+= vnode_if.h opt_emu10kx.h +# Master, PCM and MIDI devices +SRCS+= emu10kx.c +SRCS+= emu10kx-pcm.c +SRCS+= emu10kx-midi.c +# de-GPLed Makefiles +SRCS+= emu10k1-alsa%diked.h +SRCS+= p16v-alsa%diked.h +SRCS+= p17v-alsa%diked.h + +emu10k1-alsa%diked.h: emu10k1-alsa.h + grep -v '#include' ${.OODATE} | $(CC) -E -D__KERNEL__ -dM - \ + | awk -F"[ (]" '/define/ \ + { print "#ifndef " $$2 ; print ; print "#endif" }' \ + >${.TARGET} +p16v-alsa%diked.h: p16v-alsa.h + grep -v '#include' ${.OODATE} | $(CC) -E -D__KERNEL__ -dM - \ + | awk -F"[ (]" '/define/ \ + { print "#ifndef " $$2 ; print ; print "#endif" }' \ + >${.TARGET} +p17v-alsa%diked.h: p17v-alsa.h + grep -v '#include' ${.OODATE} | $(CC) -E -D__KERNEL__ -dM - \ + | awk -F"[ (]" '/define/ \ + { print "#ifndef " $$2 ; print ; print "#endif" }' \ + >${.TARGET} + +CLEANFILES+= emu10k1-alsa%diked.h +CLEANFILES+= p16v-alsa%diked.h +CLEANFILES+= p17v-alsa%diked.h + +.if !defined(KERNBUILDDIR) +opt_emu10kx.h: + echo "#define SND_EMU10KX_MULTICHANNEL" > opt_emu10kx.h + echo "#undef SND_EMU10KX_MCH_RECORDING" >> opt_emu10kx.h + echo "#undef SND_EMU10KX_DEBUG_OUTPUTS" >> opt_emu10kx.h + echo "#undef SND_EMU10KX_DEBUG" >> opt_emu10kx.h +.endif + +.include --- sys/modules/sound/driver/envy24/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/envy24/Makefile Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -# $FreeBSD: src/sys/modules/sound/driver/envy24/Makefile,v 1.3.2.1 2007/05/13 21:03:46 ariff Exp $ +# $FreeBSD: src/sys/modules/sound/driver/envy24/Makefile,v 1.3 2006/09/30 18:12:32 netchild Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci --- sys/modules/sound/driver/envy24ht/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/envy24ht/Makefile Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -# $FreeBSD: src/sys/modules/sound/driver/envy24ht/Makefile,v 1.2.2.1 2007/05/13 21:03:46 ariff Exp $ +# $FreeBSD: src/sys/modules/sound/driver/envy24ht/Makefile,v 1.2 2006/09/30 18:12:32 netchild Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci --- sys/modules/sound/driver/ess/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/ess/Makefile Thu Jul 12 12:04:19 2007 @@ -1,9 +1,9 @@ -# $FreeBSD: src/sys/modules/sound/driver/ess/Makefile,v 1.3 2002/01/23 03:32:36 cg Exp $ +# $FreeBSD: src/sys/modules/sound/driver/ess/Makefile,v 1.4 2006/05/12 18:05:35 ariff Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/isa KMOD= snd_ess SRCS= device_if.h bus_if.h isa_if.h pci_if.h -SRCS+= ess.c es1888.c +SRCS+= ess.c .include --- sys/modules/sound/driver/hda/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/hda/Makefile Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -# $FreeBSD: src/sys/modules/sound/driver/hda/Makefile,v 1.1.2.1 2007/05/13 21:09:24 ariff Exp $ +# $FreeBSD: src/sys/modules/sound/driver/hda/Makefile,v 1.1 2006/10/01 11:13:00 ariff Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci/hda --- sys/modules/sound/driver/null/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/null/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../../dev/sound + +KMOD= snd_null +SRCS= device_if.h bus_if.h +SRCS+= null.c + +.include --- sys/modules/sound/driver/spicds/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/driver/spicds/Makefile Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -# $FreeBSD: src/sys/modules/sound/driver/spicds/Makefile,v 1.2.2.1 2007/05/13 21:03:46 ariff Exp $ +# $FreeBSD: src/sys/modules/sound/driver/spicds/Makefile,v 1.2 2006/09/30 18:12:33 netchild Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci --- sys/modules/sound/sound/Makefile.orig Tue Jul 10 04:51:37 2007 +++ sys/modules/sound/sound/Makefile Thu Jul 12 12:04:19 2007 @@ -1,15 +1,20 @@ -# $FreeBSD: src/sys/modules/sound/sound/Makefile,v 1.16.2.2 2006/09/13 08:40:21 des Exp $ +# $FreeBSD: src/sys/modules/sound/sound/Makefile,v 1.21 2007/05/31 18:43:33 ariff Exp $ +.PATH: ${.CURDIR}/../../../dev/sound .PATH: ${.CURDIR}/../../../dev/sound/pcm +.PATH: ${.CURDIR}/../../../dev/sound/midi .PATH: ${.CURDIR}/../../../dev/sound/isa KMOD= sound SRCS= device_if.h bus_if.h isa_if.h pci_if.h opt_isa.h SRCS+= ac97_if.h channel_if.h feeder_if.h mixer_if.h SRCS+= ac97_if.c channel_if.c feeder_if.c mixer_if.c -SRCS+= ac97.c ac97_patch.c buffer.c channel.c dsp.c +SRCS+= mpu_if.h mpufoi_if.h synth_if.h +SRCS+= mpu_if.c mpufoi_if.c synth_if.c +SRCS+= ac97.c ac97_patch.c buffer.c channel.c clone.c dsp.c SRCS+= fake.c feeder.c feeder_fmt.c feeder_rate.c feeder_volume.c -SRCS+= mixer.c sndstat.c sound.c vchan.c +SRCS+= mixer.c sndstat.c sound.c unit.c vchan.c +SRCS+= midi.c mpu401.c sequencer.c EXPORT_SYMS= YES # XXX evaluate @@ -20,10 +25,17 @@ opt_isa.h: :> ${.TARGET} .else +.if !defined(KERNBUILDDIR) SRCS+= sndbuf_dma.c opt_isa.h: echo "#define DEV_ISA 1" > ${.TARGET} +.else +DEV_ISA!= sed -n '/DEV_ISA/p' ${KERNBUILDDIR}/opt_isa.h +.if !empty(DEV_ISA) +SRCS+= sndbuf_dma.c +.endif +.endif .endif .include --- share/man/man4/pcm.4.orig Wed Feb 1 02:31:34 2006 +++ share/man/man4/pcm.4 Thu Nov 30 01:07:02 2006 @@ -23,9 +23,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/pcm.4,v 1.39.2.6 2007/05/23 19:45:49 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/pcm.4,v 1.52 2007/06/23 14:34:30 joel Exp $ .\" -.Dd November 26, 2006 +.Dd June 23, 2007 .Dt SOUND 4 .Os .Sh NAME @@ -152,7 +152,7 @@ .Va hint.pcm.0.line Ns = Ns Qq Li 0 . This will mute the input channel per default. .Ss VCHANs -Each device can optionally support more playback channels +Each device can optionally support more playback and recording channels than physical hardware provides by using .Dq virtual channels or @@ -162,42 +162,56 @@ .Xr sysctl 8 interface but can only be manipulated while the device is inactive. .Ss Runtime Configuration -The following +There are a number of .Xr sysctl 8 -variables are available: +variables available. +.Va hw.snd.* +tunables are global settings and +.Va dev.pcm.* +are device specific. .Bl -tag -width ".Va hw.snd.report_soft_formats" -offset indent -.It Va hw.snd.pcm%d.buffersize -Configure the amount of -.Tn DMA -bufferspace available for a device. -.It Va hw.snd.targetirqrate -Set the default block size such that continuous -playback will achieve this -.Tn IRQ -rate. -This value can be tuned to improve application performance. -Increase this value when the sound lags and decrease -it if sound stutters or breaks up. -.It Va hw.snd.unit -When using -.Xr devfs 5 , -the default device for -.Pa /dev/dsp . -Equivalent to a symlink from -.Pa /dev/dsp -to -.Pa /dev/dsp Ns Va ${hw.snd.unit} . +.It Va hw.snd.latency_profile +Define sets of buffering latency conversion tables for the +.Va hw.snd.latency +tunable. +A value of 0 will use a low and aggressive latency profile which can result +in possible underruns if the application cannot keep up with a rapid irq +rate, especially during high workload. +The default value is 1, which is considered a moderate/safe latency profile. +.It Va hw.snd.latency +Configure the buffering latency. +Only affects applications that do not explicitly request +blocksize / fragments. +This tunable provides finer granularity than the +.Va hw.snd.latency_profile +tunable. +Possible values range between 0 (lowest latency) and 10 (highest latency). .It Va hw.snd.report_soft_formats Controls the internal format conversion if it is available transparently to the application software. When disabled or not available, the application will only be able to select formats the device natively supports. +.It Va hw.snd.compat_linux_mmap +Enable to allow PROT_EXEC page mappings. +All Linux applications using sound and +.Xr mmap 2 +require this. +.It Va hw.snd.feeder_rate_round +Sample rate rounding threshold, to avoid large prime division at the +cost of accuracy. +All requested sample rates will be rounded to the nearest threshold value. +Possible values range between 0 (disabled) and 500. +Default is 25. +.It Va hw.snd.feeder_rate_max +Maximum allowable sample rate. +.It Va hw.snd.feeder_rate_min +Minimum allowable sample rate. .It Va hw.snd.verbose Level of verbosity for the .Pa /dev/sndstat device. Higher values include more output and the highest level, -three, should be used when reporting problems. +four, should be used when reporting problems. Other options include: .Bl -tag -width 2n .It 0 @@ -211,11 +225,13 @@ buffer overruns and buffer underruns. .It 3 File names and versions of the currently loaded sound modules. +.It 4 +Various messages intended for debugging. .El .It Va hw.snd.maxautovchans Global .Tn VCHAN -setting that only affects devices with only one playback channel available. +setting that only affects devices with at least one playback or recording channel available. The sound system will dynamically create up this many .Tn VCHANs . Set to @@ -223,7 +239,21 @@ if no .Tn VCHANS are desired. -.It Va hw.snd.pcm%d.vchans +Maximum value is 256. +.It Va hw.snd.default_unit +Default sound card for systems with multiple sound cards. +When using +.Xr devfs 5 , +the default device for +.Pa /dev/dsp . +Equivalent to a symlink from +.Pa /dev/dsp +to +.Pa /dev/dsp Ns Va ${hw.snd.default_unit} . +.It Va hw.snd.default_auto +Enable to automatically assign default sound unit to the most recent +attached device. +.It Va dev.pcm.%d.[play|rec].vchans The current number of .Tn VCHANs allocated per device. @@ -234,11 +264,29 @@ will disable .Tn VCHANs for this device. +.It Va dev.pcm.%d.[play|rec].vchanrate +Sample rate speed for +.Tn VCHAN +mixing. +All playback paths will be converted to this sample rate before the mixing +process begins. +.It Va dev.pcm.%d.[play|rec].vchanformat +Format for +.Tn VCHAN +mixing. +All playback paths will be converted to this format before the mixing +process begins. +.It Va dev.pcm.%d.polling +Experimental polling mode support where the driver operates by querying the +device state on each tick using a +.Xr callout 9 +mechanism. +Disabled by default and currently only available for a few device drivers. .El .Ss Recording Channels On devices that have more than one recording source (ie: mic and line), there is a corresponding -.Pa /dev/dspr%d.%d +.Pa /dev/dsp%d.r%d device. .Ss Statistics Channel statistics are only kept while the device is open. @@ -278,8 +326,14 @@ Like .Pa /dev/dsp , but 16 bits per sample. -.It Pa /dev/dspr%d.%d -Should be connected to a record codec. +.It Pa /dev/dsp%d.p%d +Playback channel. +.It Pa /dev/dsp%d.r%d +Record channel. +.It Pa /dev/dsp%d.vp%d +Virtual playback channel. +.It Pa /dev/dsp%d.vr%d +Virtual recording channel. .It Pa /dev/sndstat Current .Nm @@ -299,10 +353,20 @@ device is probed and attached, these messages can be viewed with the .Xr dmesg 8 utility. +.Pp +The above device nodes are only created on demand through the dynamic +.Xr devfs 5 +clone handler. +Users are strongly discouraged to access them directly. +For specific sound card access, please instead use +.Pa /dev/dsp +or +.Pa /dev/dsp%d . .Sh DIAGNOSTICS .Bl -diag -.It ac97: dac not ready -AC97 codec is not likely to be accompanied with the sound card. +.It pcm%d:play:%d:dsp%d.p%d: play interrupt timeout, channel dead +The hardware does not generate interrupts to serve incoming (play) +or outgoing (record) data. .It unsupported subdevice XX A device node is not created properly. .El @@ -316,6 +380,7 @@ .Xr snd_csa 4 , .Xr snd_ds1 4 , .Xr snd_emu10k1 4 , +.Xr snd_emu10kx 4 , .Xr snd_envy24 4 , .Xr snd_envy24ht 4 , .Xr snd_es137x 4 , --- share/man/man4/snd_ad1816.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_ad1816.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ad1816.4,v 1.4.2.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ad1816.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_AD1816 4 --- share/man/man4/snd_als4000.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_als4000.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_als4000.4,v 1.4.2.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_als4000.4,v 1.7 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_ALS4000 4 --- share/man/man4/snd_atiixp.4.orig Tue Jan 31 08:10:09 2006 +++ share/man/man4/snd_atiixp.4 Thu Nov 30 01:07:02 2006 @@ -22,9 +22,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_atiixp.4,v 1.2.2.2 2006/01/31 00:10:09 ariff Exp $ +.\" $FreeBSD: src/share/man/man4/snd_atiixp.4,v 1.5 2007/05/12 06:33:52 brueffer Exp $ .\" -.Dd November 28, 2005 +.Dd November 29, 2006 .Dt SND_ATIIXP 4 .Os .Sh NAME @@ -52,6 +52,21 @@ to attach to ATI IXP audio devices. This driver supports 16bit playback and recording, and 32bit native playback and recording. +.Ss Runtime Configuration +The following +.Xr sysctl 8 +variables are available in addition to those available to all +.Xr sound 4 +devices: +.Bl -tag -width ".Va dev.pcm.%d.polling" -offset indent +.It Va dev.pcm.%d.polling +Experimental polling mode, where the driver operates by querying the device +state on each tick using +.Xr callout 9 . +Polling is disabled by default. +Do not enable it unless you are facing weird interrupt problems or if the +device cannot generate interrupts at all. +.El .Sh HARDWARE The .Nm --- share/man/man4/snd_cmi.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_cmi.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_cmi.4,v 1.4.2.2 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_cmi.4,v 1.7 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_CMI 4 --- share/man/man4/snd_cs4281.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_cs4281.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_cs4281.4,v 1.4.2.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_cs4281.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_CS4281 4 --- share/man/man4/snd_csa.4.orig Wed Jun 21 12:28:05 2006 +++ share/man/man4/snd_csa.4 Mon Jun 19 01:53:04 2006 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_csa.4,v 1.13.2.3 2006/06/21 04:28:05 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_csa.4,v 1.16 2006/06/18 17:53:04 brueffer Exp $ .\" .Dd December 15, 2005 .Dt SND_CSA 4 --- share/man/man4/snd_ds1.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_ds1.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ds1.4,v 1.4.2.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ds1.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_DS1 4 --- share/man/man4/snd_emu10k1.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_emu10k1.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_emu10k1.4,v 1.5.2.2 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_emu10k1.4,v 1.8 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_EMU10K1 4 --- share/man/man4/snd_emu10kx.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_emu10kx.4 Fri Dec 15 00:40:57 2006 @@ -0,0 +1,217 @@ +.\" +.\" Copyright (c) 2003,2006 Yuriy Tsibizov, +.\" 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 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 AUTHOR 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. +.\" +.\" $Id: snd_emu10kx.4,v 1.19 2006/06/07 11:18:57 chibis Exp $ +.\" $FreeBSD: src/share/man/man4/snd_emu10kx.4,v 1.4 2006/12/14 16:40:57 mpp Exp $ +.\" +.Dd July 15, 2006 +.Dt SND_EMU10KX 4 +.Os +.Sh NAME +.Nm snd_emu10kx +.Nd Creative SoundBlaster Live! and Audigy sound cards device driver +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_emu10kx" +.Pp +For additional options: +.Cd "options EMU10KX_MULTICHANNEL" +.Cd "options EMU10KX_DEBUG" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_emu10kx_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver +.Xr sound 4 +to attach to the Creative sound cards based on EMU10K1, CA0100, CA0101, CA0102 +and CA0108 DSPs. +.Pp +The +.Nm +sound cards have a PCM part, which is accessible through one to five +.Xr pcm 4 +devices (see +.Sx MULTICHANNEL PLAYBACK +for details), and MPU401-compatible MIDI I/O controller, which is accessible +through the midi device. +Wave table synthesizer is not supported. +.Sh HARDWARE +The +.Nm +driver supports the following sound cards: +.Pp +.Bl -bullet -compact +.It +Creative Sound Blaster Live!\& (EMU10K1 Chipset). +Both PCM and MIDI interfaces are available. +.It +Creative Sound Blaster Audigy (CA0100 and CA0101 Chipset). +PCM and two MIDI interfaces available. +.It +Creative Sound Blaster Audigy 2 and Creative Sound Blaster Audigy 4 (CA0102 +Chipset). +PCM support is limited to 48kHz/16 bit stereo (192kHz/24 bit part +of this chipset is not supported). +.It +Creative Sound Blaster Audigy 2 Value (CA0108 Chipset). +PCM support is limited +to 48kHz/16 bit stereo (192kHz/24 bit part of this chipset is not supported). +There is no MIDI support for this card. +.El +.Pp +The +.Nm +driver does +.Em not +support the following sound cards (although they are named +similar to some supported ones): +.Pp +.Bl -bullet -compact +.It +Creative Sound Blaster Live!\& 24-Bit, identified by +.Fx +as +.Qq Li "emu10k1x Soundblaster Live! 5.1" . +.It +Creative Sound Blaster Audigy LS / ES, identified by +.Fx +as +.Qq Li "CA0106-DAT Audigy LS" . +.It +All other cards with -DAT chipsets. +.El +.Sh MULTICHANNEL PLAYBACK +It is possible to build this driver with multichannel playback capabilities. +If you enable the +.Dv EMU10KX_MULTICHANNEL +option in your kernel configuration (or +build it as a module) you will get up to five DSP devices, one for each sound +card output. +Only +.Dq FRONT +output can play and record sound from external +sources (like line or S/PDIF inputs). +.Sh OSS MIXER CONTROLS +These are controls available through the standard OSS programming interface. +You can use +.Xr mixer 8 +to change them. +.Pp +On EMU10K1-based cards the OSS mixer directly controls the AC97 codec on card. +On newer cards the OSS mixer controls some parameters of the AC97 codec and +some DSP-based mixer controls. +.Bl -inset +.It Qq vol +mixer control is overall sound volume. +.It Qq pcm +mixer control is PCM playback volume. +It controls only front output +volume in multichannel mode and all output volume in single channel mode. +.It Qq rec +mixer control acts very different on EMU10K1 and other cards. +On EMU10K1 cards it controls the AC97 codec recording level. +On non-EMU10K1 cards +it controls the amount of AC97 "stereo mix" entering the DSP. +AC97 recording level and AC97 recording source are fixed +on CA0100, CA0101, CA0102 and CA0108 cards. +AC97 recording level are always set to +maximum and recording source is always +.Dq Li "stereo mix" . +.El +.Pp +Other OSS mixer controls do not work. +.Sh PRIVATE DEVICE CONTROLS +You can control most of EMU10Kx operation and configuration parameters through +.Va dev.emu10kx. Ns Aq Ar X +sysctls. +These +.Xr sysctl 8 +values are temporary and should not be relied +upon. +.Sh DRIVER CONFIGURATION +.Ss Kernel Configuration Options +The following kernel configuration options control the +.Nm +driver. +.Bl -tag -width ".Dv EMU10KX_MULTICHANNEL" +.It Dv EMU10KX_MULTICHANNEL +This option enables +.Sx MULTICHANNEL PLAYBACK +for all instances of the +.Nm +driver. +.It Dv EMU10KX_DEBUG +This option enables additional debug messages. +.El +.Sh FILES +.Bl -tag -width ".Pa /dev/emu10kx?" -compact +.It Pa /dev/emu10kx? +.Nm +management interface +.El +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 7.0 . +.Sh AUTHORS +.An -nosplit +The PCM part of the driver is based on the +.Xr snd_emu10k1 4 +SB Live!\& driver by +.An "Cameron Grant" . +The MIDI interface is based on the +.Xr snd_emu10k1 4 +MIDI interface code by +.An "Mathew Kanner" . +The +.Nm +device driver and this manual page were written by +.An Yuriy Tsibizov . +.Sh BUGS +8kHz/8bit/mono recording does not work. +8kHz recording was removed from the driver capabilities. +.Pp +The driver does not detect lost S/PDIF signal and produces noise when S/PDIF +is not connected and S/PDIF volume is not zero. +.Pp +The PCM driver cannot detect the presence of Live!Drive or AudigyDrive +breakout boxes +and tries to use them (and list their connectors in the mixer). +.Pp +The MIDI driver cannot detect the presence of Live!Drive or AudigyDrive +breakout boxes and tries to enable the IR receiver on them anyway. --- share/man/man4/snd_envy24.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_envy24.4 Sun Oct 1 01:19:22 2006 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_envy24.4,v 1.3.2.1 2007/05/15 18:40:22 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_envy24.4,v 1.3 2006/09/30 17:19:22 netchild Exp $ .\" .Dd September 30, 2006 .Dt SND_ENVY24 4 --- share/man/man4/snd_envy24ht.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_envy24ht.4 Sun Oct 1 02:04:57 2006 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_envy24ht.4,v 1.1.2.2 2007/06/13 13:46:01 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_envy24ht.4,v 1.2 2007/05/28 15:57:22 joel Exp $ .\" .Dd May 28, 2007 .Dt SND_ENVY24HT 4 --- share/man/man4/snd_es137x.4.orig Sat Dec 31 03:55:55 2005 +++ share/man/man4/snd_es137x.4 Thu Nov 30 01:07:02 2006 @@ -22,9 +22,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_es137x.4,v 1.4.2.2 2005/12/30 19:55:55 netchild Exp $ +.\" $FreeBSD: src/share/man/man4/snd_es137x.4,v 1.9 2006/11/29 17:07:02 joel Exp $ .\" -.Dd December 15, 2005 +.Dd November 29, 2006 .Dt SND_ES137X 4 .Os .Sh NAME @@ -53,15 +53,26 @@ .Ss Runtime Configuration The following .Xr sysctl 8 -variable are available in addition to those available to all +variables are available in addition to those available to all .Xr sound 4 devices: -.Bl -tag -width ".Va hw.snd.pcm%d.spdif_enabled" -offset indent +.Bl -tag -width ".Va hw.snd.pcm%d.latency_timer" -offset indent +.It Va hw.snd.pcm%d.latency_timer +Controls the PCI latency timer setting. +Increasing this value will solve most popping and crackling issues +(especially on VIA motherboards). .It Va hw.snd.pcm%d.spdif_enabled Enables S/PDIF output on the primary playback channel. This .Xr sysctl 8 variable is available only if the device is known to support S/PDIF output. +.It Va dev.pcm.%d.polling +Experimental polling mode, where the driver operates by querying the device +state on each tick using +.Xr callout 9 . +Polling is disabled by default. +Do not enable it unless you are facing weird interrupt problems or if the +device cannot generate interrupts at all. .El .Sh HARDWARE The --- share/man/man4/snd_ess.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_ess.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ess.4,v 1.4.2.2 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ess.4,v 1.7 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_ESS 4 --- share/man/man4/snd_fm801.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_fm801.4 Thu Dec 1 20:58:50 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_fm801.4,v 1.1.4.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_fm801.4,v 1.3 2005/12/01 12:58:50 joel Exp $ .\" .Dd December 1, 2005 .Dt SND_FM801 4 --- share/man/man4/snd_gusc.4.orig Wed Jun 21 12:28:05 2006 +++ share/man/man4/snd_gusc.4 Mon Jun 19 01:53:04 2006 @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_gusc.4,v 1.12.2.2 2006/06/21 04:28:05 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_gusc.4,v 1.14 2006/06/18 17:53:04 brueffer Exp $ .\" .Dd December 15, 2005 .Dt SND_GUSC 4 --- share/man/man4/snd_hda.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_hda.4 Thu Jan 18 23:27:15 2007 @@ -22,9 +22,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_hda.4,v 1.10.2.1 2007/05/15 18:40:22 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_hda.4,v 1.11 2007/06/12 15:26:41 joel Exp $ .\" -.Dd May 5, 2007 +.Dd June 12, 2007 .Dt SND_HDA 4 .Os .Sh NAME @@ -169,13 +169,15 @@ .Pp .Bl -bullet -compact .It -Analog Device AD1981HD +Analog Devices AD1981HD .It -Analog Device AD1983 +Analog Devices AD1983 .It -Analog Device AD1986A +Analog Devices AD1986A .It -Analog Device AD1988 +Analog Devices AD1988 +.It +Analog Devices AD1988B .It CMedia CMI9880 .It @@ -186,6 +188,8 @@ Realtek ALC260 .It Realtek ALC262 +.It +Realtek ALC660 .It Realtek ALC861 .It --- share/man/man4/snd_ich.4.orig Wed Jun 21 12:28:05 2006 +++ share/man/man4/snd_ich.4 Mon Jun 19 01:53:04 2006 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ich.4,v 1.6.2.2 2006/06/21 04:28:05 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ich.4,v 1.9 2006/06/18 17:53:04 brueffer Exp $ .\" .Dd December 15, 2005 .Dt SND_ICH 4 --- share/man/man4/snd_maestro.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_maestro.4 Fri Dec 16 04:25:41 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_maestro.4,v 1.2.2.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_maestro.4,v 1.3 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_MAESTRO 4 --- share/man/man4/snd_maestro3.4.orig Tue Jan 10 16:25:15 2006 +++ share/man/man4/snd_maestro3.4 Mon Jan 9 20:51:45 2006 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_maestro3.4,v 1.7.2.2 2006/01/10 08:25:15 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_maestro3.4,v 1.9 2006/01/09 12:51:45 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_MAESTRO3 4 --- share/man/man4/snd_mss.4.orig Sat Dec 31 06:59:07 2005 +++ share/man/man4/snd_mss.4 Thu Dec 1 20:58:50 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_mss.4,v 1.3.2.1 2005/12/30 22:59:07 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_mss.4,v 1.3 2005/12/01 12:58:50 joel Exp $ .\" .Dd December 1, 2005 .Dt SND_MSS 4 --- share/man/man4/snd_neomagic.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_neomagic.4 Thu Dec 1 20:58:50 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_neomagic.4,v 1.3.4.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_neomagic.4,v 1.5 2005/12/01 12:58:50 joel Exp $ .\" .Dd December 1, 2005 .Dt SND_NEOMAGIC 4 --- share/man/man4/snd_sbc.4.orig Wed Jun 21 12:28:05 2006 +++ share/man/man4/snd_sbc.4 Mon Jun 19 01:53:04 2006 @@ -23,13 +23,15 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_sbc.4,v 1.14.2.3 2006/06/21 04:28:05 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_sbc.4,v 1.19 2007/02/17 11:31:58 joel Exp $ .\" -.Dd December 15, 2005 +.Dd February 17, 2007 .Dt SND_SBC 4 .Os .Sh NAME -.Nm snd_sbc +.Nm snd_sbc , +.Nm snd_sb16 , +.Nm snd_sb8 .Nd Creative Sound Blaster ISA and compatible bridge device driver .Sh SYNOPSIS To compile this driver into the kernel, place the following lines in your @@ -37,13 +39,17 @@ .Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_sbc" +.Cd "device snd_sb16" +.Cd "device snd_sb8" .Ed .Pp Alternatively, to load the driver as a module at boot time, place the -following line in +following lines in .Xr loader.conf 5 : .Bd -literal -offset indent snd_sbc_load="YES" +snd_sb16_load="YES" +snd_sb8_load="YES" .Ed .Pp Non-PnP cards require the following lines in @@ -60,7 +66,8 @@ .Nm bridge driver allows the generic audio driver .Xr sound 4 -to attach to Creative Sound Blaster ISA compatible audio cards. +to attach to Creative Sound Blaster ISA (mostly SB16 or SB8, known as +SoundBlaster Pro) compatible audio cards. .Pp The value of flags specifies the secondary DMA channel. If the secondary --- share/man/man4/snd_solo.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_solo.4 Tue Nov 29 02:47:00 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_solo.4,v 1.4.2.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_solo.4,v 1.5 2005/11/28 18:47:00 joel Exp $ .\" .Dd November 28, 2005 .Dt SND_SOLO 4 --- share/man/man4/snd_spicds.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_spicds.4 Mon Nov 13 16:56:42 2006 @@ -22,9 +22,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_spicds.4,v 1.2.2.1 2007/05/15 18:40:22 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_spicds.4,v 1.3 2007/05/28 16:00:08 joel Exp $ .\" -.Dd September 30, 2006 +.Dd May 28, 2007 .Dt SND_SPICDS 4 .Os .Sh NAME @@ -58,6 +58,8 @@ AK4358 .It AK4381 +.It +AK4396 .It AK4524 .It --- share/man/man4/snd_t4dwave.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_t4dwave.4 Thu Dec 1 20:58:50 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_t4dwave.4,v 1.1.2.2 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_t4dwave.4,v 1.3 2005/12/01 12:58:50 joel Exp $ .\" .Dd December 1, 2005 .Dt SND_T4DWAVE 4 --- share/man/man4/snd_uaudio.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_uaudio.4 Fri Dec 16 04:25:41 2005 @@ -34,7 +34,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_uaudio.4,v 1.5.4.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_uaudio.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" .Dd December 15, 2005 .Dt SND_UAUDIO 4 --- share/man/man4/snd_via8233.4.orig Thu Apr 27 02:45:39 2006 +++ share/man/man4/snd_via8233.4 Thu Nov 30 01:07:02 2006 @@ -22,9 +22,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_via8233.4,v 1.2.2.2 2006/04/26 18:45:39 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_via8233.4,v 1.7 2007/05/12 06:41:41 brueffer Exp $ .\" -.Dd April 25, 2006 +.Dd November 29, 2006 .Dt SND_VIA8233 4 .Os .Sh NAME @@ -52,6 +52,21 @@ to attach to the VIA VT8233 audio devices. These audio chipsets are integrated in the southbridge on many VIA based motherboards. +.Ss Runtime Configuration +The following +.Xr sysctl 8 +variables are available in addition to those available to all +.Xr sound 4 +devices: +.Bl -tag -width ".Va dev.pcm.%d.polling" -offset indent +.It Va dev.pcm.%d.polling +Experimental polling mode, where the driver operates by querying the device +state on each tick using +.Xr callout 9 . +Polling is disabled by default. +Do not enable it unless you are facing weird interrupt problems or if the +device cannot generate interrupts at all. +.El .Sh HARDWARE The .Nm --- share/man/man4/snd_via82c686.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_via82c686.4 Thu Dec 1 20:58:50 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_via82c686.4,v 1.1.4.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_via82c686.4,v 1.3 2005/12/01 12:58:50 joel Exp $ .\" .Dd December 1, 2005 .Dt SND_VIA82C686 4 --- share/man/man4/snd_vibes.4.orig Mon Jan 9 20:48:39 2006 +++ share/man/man4/snd_vibes.4 Thu Dec 1 20:58:51 2005 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_vibes.4,v 1.1.4.1 2006/01/09 12:48:39 joel Exp $ +.\" $FreeBSD: src/share/man/man4/snd_vibes.4,v 1.3 2005/12/01 12:58:51 joel Exp $ .\" .Dd December 1, 2005 .Dt SND_VIBES 4 --- sys/cam/scsi/scsi_da.c.orig Sun Jan 29 10:52:54 2006 +++ sys/cam/scsi/scsi_da.c Sun Jan 29 10:54:07 2006 @@ -148,6 +148,13 @@ /* SPI, FC devices */ { /* + * Apacer Audio Steno AV220 USB MP3 Player + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "" , "MP3 Flash Drive 1.02", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT + }, + { + /* * Fujitsu M2513A MO drives. * Tested devices: M2513A2 firmware versions 1200 & 1300. * (dip switch selects whether T_DIRECT or T_OPTICAL device) --- usr.sbin/burncd/burncd.c.orig Wed Nov 30 14:24:48 2005 +++ usr.sbin/burncd/burncd.c Wed Nov 30 14:34:36 2005 @@ -47,6 +47,16 @@ #define BLOCKS 16 +#define WAVE_HEADER_SIZE 44 +#define RIFF_MAGIC "RIFF" +#define WAVE_MAGIC "WAVE" +#define FMT_MAGIC "fmt " +#define DATA_MAGIC "data" +#define WAVE_VALID_EXT ".wav" +#define WAVE_VALID_RATE ((u_int32_t)44100) +#define WAVE_VALID_SAMPLE ((u_int16_t)16) +#define WAVE_VALID_CHANNELS ((u_int16_t)2) + struct track_info { int file; char file_name[MAXPATHLEN + 1]; @@ -59,7 +69,8 @@ static struct track_info tracks[100]; static int global_fd_for_cleanup, quiet, verbose, saved_block_size, notracks; -void add_track(char *, int, int, int); +u_int is_wavefile(char *, int, int); +void add_track(char *, int, int, int, int); void do_DAO(int fd, int, int); void do_TAO(int fd, int, int, int); void do_format(int, int, char *); @@ -76,6 +87,7 @@ int dao = 0, eject = 0, fixate = 0, list = 0, multi = 0, preemp = 0; int nogap = 0, speed = 4 * 177, test_write = 0, force = 0; int block_size = 0, block_type = 0, cdopen = 0, dvdrw = 0; + int wave_check = 0; const char *dev; if ((dev = getenv("CDROM")) == NULL) @@ -170,6 +182,11 @@ if (!strcasecmp(argv[arg], "msinfo")) { struct ioc_read_toc_single_entry entry; struct ioc_toc_header header; + struct cdr_track dummy_track; + + bzero(&dummy_track, sizeof(dummy_track)); + if (ioctl(fd, CDRIOCINITTRACK, &dummy_track) < 0) + err(EX_IOERR, "ioctl(CDRIOCINITTRACK)"); if (ioctl(fd, CDIOREADTOCHEADER, &header) < 0) err(EX_IOERR, "ioctl(CDIOREADTOCHEADER)"); @@ -181,7 +198,9 @@ if (ioctl(fd, CDRIOCNEXTWRITEABLEADDR, &addr) < 0) err(EX_IOERR, "ioctl(CDRIOCNEXTWRITEABLEADDR)"); fprintf(stdout, "%d,%d\n", - ntohl(entry.entry.addr.lba), addr); + (entry.entry.control & 4) + ? ntohl(entry.entry.addr.lba) + : 0, addr); break; } @@ -226,29 +245,40 @@ arg++; continue; } - if (!strcasecmp(argv[arg], "audio") || !strcasecmp(argv[arg], "raw")) { + if (!strcasecmp(argv[arg], "raw")) { block_type = CDR_DB_RAW; block_size = 2352; + wave_check = 0; + continue; + } + if (!strcasecmp(argv[arg], "audio")) { + block_type = CDR_DB_RAW; + block_size = 2352; + wave_check = 1; continue; } if (!strcasecmp(argv[arg], "data") || !strcasecmp(argv[arg], "mode1")) { block_type = CDR_DB_ROM_MODE1; block_size = 2048; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "mode2")) { block_type = CDR_DB_ROM_MODE2; block_size = 2336; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "xamode1")) { block_type = CDR_DB_XA_MODE1; block_size = 2048; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "xamode2")) { block_type = CDR_DB_XA_MODE2_F2; block_size = 2324; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "vcd")) { @@ -256,12 +286,14 @@ block_size = 2352; dao = 1; nogap = 1; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "dvdrw")) { block_type = CDR_DB_ROM_MODE1; block_size = 2048; dvdrw = 1; + wave_check = 0; continue; } @@ -279,7 +311,8 @@ continue; if ((eol = strchr(file_buf, '\n'))) *eol = '\0'; - add_track(file_buf, block_size, block_type, nogap); + add_track(file_buf, block_size, block_type, nogap, + wave_check); } if (feof(fp)) fclose(fp); @@ -287,7 +320,8 @@ err(EX_IOERR, "fgets(%s)", file_buf); } else - add_track(argv[arg], block_size, block_type, nogap); + add_track(argv[arg], block_size, block_type, nogap, + wave_check); } if (notracks) { if (dvdrw && notracks > 1) @@ -323,8 +357,42 @@ exit(EX_OK); } +#define BYTES2ULONG(b) ((u_int32_t) \ + (((b)[0] & 0xff) | \ + ((b)[1] << 8 & 0xff00) | \ + ((b)[2] << 16 & 0xff0000) | \ + ((b)[3] << 24 & 0xff000000))) + +#define BYTES2USHORT(b) ((u_int16_t) \ + (((b)[0] & 0xff) | \ + ((b)[1] << 8 & 0xff00))) + +u_int +is_wavefile(char *name, int file, int block_type) +{ + char *ext; + u_int8_t hdr[WAVE_HEADER_SIZE]; + + ext = strrchr(name, '.'); + if (block_type == CDR_DB_RAW && ext != NULL + && !strcasecmp(ext, WAVE_VALID_EXT)) { + if (read(file, &hdr, WAVE_HEADER_SIZE) == WAVE_HEADER_SIZE + && !strncmp((char *)hdr, RIFF_MAGIC, strlen(RIFF_MAGIC)) + && !strncmp((char *)hdr + 8, WAVE_MAGIC, strlen(WAVE_MAGIC)) + && !strncmp((char *)hdr + 12, FMT_MAGIC, strlen(FMT_MAGIC)) + && !strncmp((char *)hdr + 36, DATA_MAGIC, strlen(DATA_MAGIC)) + && BYTES2USHORT(hdr + 22) == WAVE_VALID_CHANNELS + && BYTES2ULONG(hdr + 24) == WAVE_VALID_RATE + && BYTES2USHORT(hdr + 34) == WAVE_VALID_SAMPLE) + return WAVE_HEADER_SIZE; + else + lseek(file, 0, SEEK_SET); + } + return 0; +} + void -add_track(char *name, int block_size, int block_type, int nogap) +add_track(char *name, int block_size, int block_type, int nogap, int wave_check) { struct stat sb; int file; @@ -347,18 +415,18 @@ if (file == STDIN_FILENO) tracks[notracks].file_size = -1; else - tracks[notracks].file_size = sb.st_size; + tracks[notracks].file_size = (u_int)sb.st_size + - ((wave_check && (u_int)sb.st_size > WAVE_HEADER_SIZE) + ? is_wavefile(name, file, block_type) + : 0); tracks[notracks].block_size = block_size; tracks[notracks].block_type = block_type; - if (nogap && notracks) + if (notracks && (nogap || + (tracks[notracks - (notracks > 0)].block_type == block_type))) tracks[notracks].pregap = 0; - else { - if (tracks[notracks - (notracks > 0)].block_type == block_type) - tracks[notracks].pregap = 150; - else - tracks[notracks].pregap = 255; - } + else + tracks[notracks].pregap = 150; if (verbose) { int pad = 0; @@ -383,6 +451,7 @@ struct cdr_cue_entry cue[100]; int format = CDR_SESS_CDROM; int addr, i, j = 0; + char buf[2352*BLOCKS]; int bt2ctl[16] = { 0x0, -1, -1, -1, -1, -1, -1, -1, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, -1, -1 }; @@ -395,6 +464,12 @@ if (verbose) fprintf(stderr, "next writeable LBA %d\n", addr); + if (addr != -150) { + addr = -150; + if (verbose) + fprintf(stderr, "resetting next writable LBA!\n"); + } + cue_ent(&cue[j++], bt2ctl[tracks[0].block_type], 0x01, 0x00, 0x0, (bt2df[tracks[0].block_type] & 0xf0) | (tracks[0].block_type < 8 ? 0x01 : 0x04), 0x00, addr); @@ -407,40 +482,21 @@ if (tracks[i].block_type >= CDR_DB_XA_MODE1) format = CDR_SESS_CDROM_XA; - if (i == 0) { - addr += tracks[i].pregap; - tracks[i].addr = addr; - - cue_ent(&cue[j++], bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x1, bt2df[tracks[i].block_type], + if (tracks[i].pregap) { + cue_ent(&cue[j++],bt2ctl[tracks[i].block_type], + 0x01, i+1, 0x0, + bt2df[tracks[i].block_type], 0x00, addr); + addr += tracks[i].pregap; } - else { - if (tracks[i].pregap) { - if (tracks[i].block_type > 0x7) { - cue_ent(&cue[j++],bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x0, - (bt2df[tracks[i].block_type] & 0xf0) | - (tracks[i].block_type < 8 ? 0x01 :0x04), - 0x00, addr); - } - else - cue_ent(&cue[j++],bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x0, - bt2df[tracks[i].block_type], - 0x00, addr); - } - tracks[i].addr = tracks[i - 1].addr + - roundup_blocks(&tracks[i - 1]); - - cue_ent(&cue[j++], bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x1, bt2df[tracks[i].block_type], - 0x00, addr + tracks[i].pregap); - - if (tracks[i].block_type > 0x7) - addr += tracks[i].pregap; - } + tracks[i].addr = addr; + if (verbose) + fprintf(stderr, "track %d: addr=%d pregap=%d\n", + i+1, tracks[i].addr, tracks[i].pregap); + cue_ent(&cue[j++], bt2ctl[tracks[i].block_type], + 0x01, i+1, 0x1, bt2df[tracks[i].block_type], + 0x00, addr); addr += roundup_blocks(&tracks[i]); } @@ -468,7 +524,45 @@ if (ioctl(fd, CDRIOCSENDCUE, &sheet) < 0) err(EX_IOERR, "ioctl(CDRIOCSENDCUE)"); + bzero(buf, sizeof(buf)); for (i = 0; i < notracks; i++) { + if (tracks[i].pregap > 0) { + int total, write_size, res, retry_pregap; + + if (ioctl(fd, CDRIOCSETBLOCKSIZE, &tracks[i].block_size) < 0) + err(EX_IOERR, "ioctl(CDRIOCSETBLOCKSIZE)"); + if (lseek(fd, (tracks[i].addr - tracks[i].pregap) * + tracks[i].block_size, SEEK_SET) == -1) + err(EX_IOERR, "lseek"); + total = tracks[i].pregap * tracks[i].block_size; + if (i == 0 && (tracks[i].addr - tracks[i].pregap) < 0) + retry_pregap = total; + else + retry_pregap = -1; + if (verbose) + fprintf(stderr, + "writing pregap addr = %d total = %d blocks / %d bytes\n", + tracks[i].addr - tracks[i].pregap, + tracks[i].pregap, total); + while (total > 0) { + write_size = MIN(tracks[i].block_size * BLOCKS, total); + if ((res = write(fd, buf, write_size)) != write_size) { + /* XXX workaround for FreeBSD 5+ GEOM !@#$%^&* */ + if (res == -1 && retry_pregap == total) { + if (lseek(fd, tracks[i].addr * tracks[i].block_size, + SEEK_SET) == -1) + err(EX_IOERR, "lseek"); + retry_pregap = -1; + continue; + } + fprintf(stderr, + "pregap: only wrote %d of %d bytes err=%d\n", + res, write_size, errno); + break; + } + total -= write_size; + } + } if (write_file(fd, &tracks[i])) err(EX_IOERR, "write_file"); } --- usr.sbin/cdcontrol/cdcontrol.c.orig Wed Feb 16 02:29:43 2005 +++ usr.sbin/cdcontrol/cdcontrol.c Wed Feb 16 02:33:13 2005 @@ -73,6 +73,7 @@ #define STATUS_AUDIO 0x1 #define STATUS_MEDIA 0x2 #define STATUS_VOLUME 0x4 +#define SESSION_SECTORS (152*75) struct cmdtab { int command; @@ -1004,6 +1005,12 @@ e[1].addr.msf.frame); else next = ntohl(e[1].addr.lba); + if (e[1].track < 100) { + if (!(e->control & 4) && (e[1].control & 4)) + next -= SESSION_SECTORS; + else if ((e->control & 4) != (e[1].control & 4)) + next -= 150; + } len = next - block; /* Take into account a start offset time. */ lba2msf (len - 150, &m, &s, &f); --- usr.sbin/acpi/acpiconf/acpiconf.c.orig Mon Dec 5 13:07:42 2005 +++ usr.sbin/acpi/acpiconf/acpiconf.c Wed Jan 11 02:32:01 2006 @@ -83,14 +83,13 @@ return (0); } -/* should be a acpi define, but doesn't appear to be */ -#define UNKNOWN_CAP 0xffffffff -#define UNKNOWN_VOLTAGE 0xffffffff - static int acpi_battinfo(int num) { union acpi_battery_ioctl_arg battio; + struct acpi_battinfo battinfo; + struct acpi_bif bif; + struct acpi_bst bst; const char *pwr_units; int hours, min; @@ -101,82 +100,111 @@ battio.unit = num; if (ioctl(acpifd, ACPIIO_BATT_GET_BIF, &battio) == -1) err(EX_IOERR, "get battery info (%d) failed", num); - if (battio.bif.units == 0) + bif = battio.bif; + if (bif.units == 0) pwr_units = "mW"; else pwr_units = "mA"; - if (battio.bif.dcap == UNKNOWN_CAP) + if (bif.dcap == ACPI_BATT_UNKNOWN) printf("Design capacity:\tunknown\n"); else - printf("Design capacity:\t%d %sh\n", battio.bif.dcap, + printf("Design capacity:\t%d %sh\n", bif.dcap, pwr_units); - if (battio.bif.lfcap == UNKNOWN_CAP) + if (bif.lfcap == ACPI_BATT_UNKNOWN) printf("Last full capacity:\tunknown\n"); else - printf("Last full capacity:\t%d %sh\n", battio.bif.lfcap, + printf("Last full capacity:\t%d %sh\n", bif.lfcap, pwr_units); - printf("Technology:\t\t%s\n", battio.bif.btech == 0 ? + printf("Technology:\t\t%s\n", bif.btech == 0 ? "primary (non-rechargeable)" : "secondary (rechargeable)"); - if (battio.bif.dvol == UNKNOWN_CAP) + if (bif.dvol == ACPI_BATT_UNKNOWN) printf("Design voltage:\t\tunknown\n"); else - printf("Design voltage:\t\t%d mV\n", battio.bif.dvol); - printf("Capacity (warn):\t%d %sh\n", battio.bif.wcap, pwr_units); - printf("Capacity (low):\t\t%d %sh\n", battio.bif.lcap, pwr_units); - printf("Low/warn granularity:\t%d %sh\n", battio.bif.gra1, pwr_units); - printf("Warn/full granularity:\t%d %sh\n", battio.bif.gra2, pwr_units); - printf("Model number:\t\t%s\n", battio.bif.model); - printf("Serial number:\t\t%s\n", battio.bif.serial); - printf("Type:\t\t\t%s\n", battio.bif.type); - printf("OEM info:\t\t%s\n", battio.bif.oeminfo); + printf("Design voltage:\t\t%d mV\n", bif.dvol); + printf("Capacity (warn):\t%d %sh\n", bif.wcap, pwr_units); + printf("Capacity (low):\t\t%d %sh\n", bif.lcap, pwr_units); + printf("Low/warn granularity:\t%d %sh\n", bif.gra1, pwr_units); + printf("Warn/full granularity:\t%d %sh\n", bif.gra2, pwr_units); + printf("Model number:\t\t%s\n", bif.model); + printf("Serial number:\t\t%s\n", bif.serial); + printf("Type:\t\t\t%s\n", bif.type); + printf("OEM info:\t\t%s\n", bif.oeminfo); /* Print current battery state information. */ battio.unit = num; if (ioctl(acpifd, ACPIIO_BATT_GET_BATTINFO, &battio) == -1) err(EX_IOERR, "get battery user info (%d) failed", num); - if (battio.battinfo.state != ACPI_BATT_STAT_NOT_PRESENT) { + battinfo = battio.battinfo; + if (battinfo.state != ACPI_BATT_STAT_NOT_PRESENT) { printf("State:\t\t\t"); - if (battio.battinfo.state == 0) + if (battinfo.state == 0) printf("high "); - if (battio.battinfo.state & ACPI_BATT_STAT_CRITICAL) + if (battinfo.state & ACPI_BATT_STAT_CRITICAL) printf("critical "); - if (battio.battinfo.state & ACPI_BATT_STAT_DISCHARG) + if (battinfo.state & ACPI_BATT_STAT_DISCHARG) printf("discharging "); - if (battio.battinfo.state & ACPI_BATT_STAT_CHARGING) + if (battinfo.state & ACPI_BATT_STAT_CHARGING) printf("charging "); printf("\n"); - if (battio.battinfo.cap == -1) + if (battinfo.cap == -1) printf("Remaining capacity:\tunknown\n"); else printf("Remaining capacity:\t%d%%\n", - battio.battinfo.cap); - if (battio.battinfo.min == -1) + battinfo.cap); + if (battinfo.min == -1) printf("Remaining time:\t\tunknown\n"); else { - hours = battio.battinfo.min / 60; - min = battio.battinfo.min % 60; + hours = battinfo.min / 60; + min = battinfo.min % 60; printf("Remaining time:\t\t%d:%02d\n", hours, min); } - if (battio.battinfo.rate == -1) + if (battinfo.rate == -1) printf("Present rate:\t\tunknown\n"); else printf("Present rate:\t\t%d %s\n", - battio.battinfo.rate, pwr_units); - } else - printf("State:\t\t\tnot present\n"); + battinfo.rate, pwr_units); + } /* Print battery voltage information. */ battio.unit = num; if (ioctl(acpifd, ACPIIO_BATT_GET_BST, &battio) == -1) err(EX_IOERR, "get battery status (%d) failed", num); - if (battio.bst.state != ACPI_BATT_STAT_NOT_PRESENT) { - if (battio.bst.volt == UNKNOWN_VOLTAGE) + bst = battio.bst; + if (bst.state != ACPI_BATT_STAT_NOT_PRESENT) { + if (battinfo.state == ACPI_BATT_STAT_NOT_PRESENT) { + printf("State:\t\t\t"); + if (bst.state == 0) + printf("high "); + if (bst.state & ACPI_BATT_STAT_CRITICAL) + printf("critical "); + if (bst.state & ACPI_BATT_STAT_DISCHARG) + printf("discharging "); + if (bst.state & ACPI_BATT_STAT_CHARGING) + printf("charging "); + printf("\n"); + if (bst.rate == ACPI_BATT_UNKNOWN) + printf("Present rate:\t\tunknown\n"); + else + printf("Present rate:\t\t%d %s\n", + bst.rate, pwr_units); + if (bst.cap == ACPI_BATT_UNKNOWN || + bif.lfcap == ACPI_BATT_UNKNOWN) + printf("Remaining capacity:\tunknown\n"); + else + printf("Remaining capacity:\t%d%%\n", + (100 * bst.cap) / bif.lfcap); + } + if (bst.volt == ACPI_BATT_UNKNOWN) printf("Voltage:\t\tunknown\n"); else - printf("Voltage:\t\t%d mV\n", battio.bst.volt); + printf("Voltage:\t\t%d mV\n", bst.volt); } + if (battinfo.state == ACPI_BATT_STAT_NOT_PRESENT && + bst.state == ACPI_BATT_STAT_NOT_PRESENT) + printf("State:\t\t\tnot present\n"); + return (0); } @@ -191,7 +219,7 @@ main(int argc, char *argv[]) { char c, *prog; - int sleep_type; + int sleep_type, acline; prog = argv[0]; if (argc < 2) @@ -226,6 +254,17 @@ if (sleep_type != -1) { sleep(1); /* wait 1 sec. for key-release event */ acpi_sleep(sleep_type); + } else { + if (ioctl(acpifd, ACPIIO_ACAD_GET_STATUS, &acline) == -1) + err(EX_IOERR, "get ac status failed"); + printf("AC line:\t\t"); + if (acline == 0) + printf("off-line"); + else if (acline == 1) + printf("on-line"); + else + printf("unknown"); + printf("\n"); } close(acpifd); --- sys/compat/ndis/subr_ndis.c.orig 6 Nov 2005 03:52:24 -0000 1.94.2.2 +++ sys/compat/ndis/subr_ndis.c 16 Dec 2005 11:54:33 -0000 @@ -134,7 +134,7 @@ static void NdisOpenConfigurationKeyByName(ndis_status *, ndis_handle, unicode_string *, ndis_handle *); static ndis_status ndis_encode_parm(ndis_miniport_block *, - struct sysctl_oid *, ndis_parm_type, ndis_config_parm **); + struct sysctl_oid *, ndis_parm_type, ndis_config_parm **, int); static ndis_status ndis_decode_parm(ndis_miniport_block *, ndis_config_parm *, char *); static void NdisReadConfiguration(ndis_status *, ndis_config_parm **, @@ -545,11 +545,12 @@ } static ndis_status -ndis_encode_parm(block, oid, type, parm) +ndis_encode_parm(block, oid, type, parm, literal) ndis_miniport_block *block; struct sysctl_oid *oid; ndis_parm_type type; ndis_config_parm **parm; + int literal; { ndis_config_parm *p; ndis_parmlist_entry *np; @@ -572,7 +573,7 @@ val = strtoul((char *)oid->oid_arg1, NULL, 10); us = &p->ncp_parmdata.ncp_stringdata; p->ncp_type = ndis_parm_string; - if (val) { + if (!literal && val) { snprintf(tmp, 32, "%x", val); RtlInitAnsiString(&as, tmp); } else { @@ -674,6 +675,7 @@ struct sysctl_oid *oidp; struct sysctl_ctx_entry *e; ansi_string as; + int literal; block = (ndis_miniport_block *)cfg; sc = device_get_softc(block->nmb_physdeviceobj->do_devext); @@ -707,7 +709,11 @@ return; } - *status = ndis_encode_parm(block, oidp, type, parm); + if (ndis_strcasecmp(keystr, "NetworkAddress") == 0) + literal = TRUE; + else + literal = FALSE; + *status = ndis_encode_parm(block, oidp, type, parm, literal); RtlFreeAnsiString(&as); return; } --- sys/dev/if_ndis/if_ndis.c.orig 6 Nov 2005 19:39:41 -0000 1.99.2.5 +++ sys/dev/if_ndis/if_ndis.c 16 Dec 2005 11:54:34 -0000 @@ -46,10 +46,7 @@ #include #include #include -#if __FreeBSD_version < 502113 #include -#endif - #include #include @@ -1862,6 +1859,13 @@ struct ndis_softc *sc = xsc; struct ifnet *ifp = sc->ifp; int i, len, error; + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + struct ieee80211com *ic; + struct sysctl_ctx_entry *e; + struct sysctl_oid *oidp; + u_char eaddr[ETHER_ADDR_LEN]; + char eaddr_str[(ETHER_ADDR_LEN * 2) + 1]; /* * Avoid reintializing the link unnecessarily. @@ -1882,6 +1886,39 @@ /* Init our MAC address */ + ifa = ifaddr_byindex(ifp->if_index); + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + bcopy(LLADDR(sdl), eaddr, ETHER_ADDR_LEN); + if (sc->ndis_80211) { + ic = &sc->ic; + IEEE80211_ADDR_COPY(ic->ic_myaddr, eaddr); + } + /* + * "NetworkAddress" value should be 12 bytes HEX. + */ + snprintf(eaddr_str, sizeof(eaddr_str), "%02x%02x%02x%02x%02x%02x", + eaddr[0], eaddr[1], eaddr[2], + eaddr[3], eaddr[4], eaddr[5]); +#if __FreeBSD_version < 502113 + TAILQ_FOREACH(e, &sc->ndis_ctx, link) { +#else + TAILQ_FOREACH(e, device_get_sysctl_ctx(sc->ndis_dev), link) { +#endif + oidp = e->entry; + if (ndis_strcasecmp(oidp->oid_name, "NetworkAddress") == 0) { + /* + * Copy the value, so NdisReadConfiguration() + * will find and use this configured value. + */ + strcpy((char *)oidp->oid_arg1, eaddr_str); + goto mac_addr_save_out; + } + } + /* Add sysctl entry for "NetworkAdress", in case it doesn't exist */ + ndis_add_sysctl(sc, "NetworkAddress", "(dynamically set key)", + eaddr_str, CTLFLAG_RW); +mac_addr_save_out: + /* Program the packet filter */ sc->ndis_filter = NDIS_PACKET_TYPE_DIRECTED; --- sys/dev/atkbdc/psm.c.orig Wed May 2 01:50:18 2007 +++ sys/dev/atkbdc/psm.c Wed May 2 01:37:51 2007 @@ -345,6 +345,8 @@ * WARNING: the order of probe is very important. Don't mess it * unless you know what you are doing. */ + { MOUSE_MODEL_SYNAPTICS, /* Synaptics Touchpad */ + 0xc0, MOUSE_SYNAPTICS_PACKETSIZE, enable_synaptics, }, { MOUSE_MODEL_NET, /* Genius NetMouse */ 0x08, MOUSE_PS2INTELLI_PACKETSIZE, enable_gmouse, }, { MOUSE_MODEL_NETSCROLL, /* Genius NetScroll */ @@ -731,6 +733,8 @@ /* other parameters */ for (i = 0; vendortype[i].probefunc != NULL; ++i) { + if (i == 0 && !synaptics_support) + continue; if ((*vendortype[i].probefunc)(sc)) { if (verbose >= 2) log(LOG_ERR, "psm%d: found %s\n", @@ -1169,6 +1173,8 @@ /* other parameters */ for (i = 0; vendortype[i].probefunc != NULL; ++i) { + if (i == 0 && !synaptics_support) + continue; if ((*vendortype[i].probefunc)(sc)) { if (verbose >= 2) printf("psm%d: found %s\n", @@ -2112,7 +2118,10 @@ sc->pkterrors++; ++sc->syncerrors; sc->lastinputerr = now; - if (sc->syncerrors >= sc->mode.packetsize * 2 || + if ((sc->config & PSM_CONFIG_SYNCHACK) != 0) { + VLOG(3, (LOG_DEBUG, "psmintr: synchack.\n")); + reinitialize(sc, FALSE); + } else if (sc->syncerrors >= sc->mode.packetsize * 2 || sc->pkterrors >= pkterrthresh) { /* --- sys/dev/acpica/acpi_timer.c.orig Wed Apr 12 01:04:38 2006 +++ sys/dev/acpica/acpi_timer.c Wed Apr 12 01:19:50 2006 @@ -182,7 +182,7 @@ j += acpi_timer_test(); if (bootverbose) printf(" -> %d\n", j); - if (j == 10) { + if (j == 10 || testenv("debug.acpi.timer_fast")) { acpi_timer_timecounter.tc_name = "ACPI-fast"; acpi_timer_timecounter.tc_get_timecount = acpi_timer_get_timecount; } else { --- libexec/rtld-elf/rtld.c.orig Sat Jun 17 08:00:04 2006 +++ libexec/rtld-elf/rtld.c Sat Jun 17 08:01:01 2006 @@ -216,6 +216,8 @@ int tls_dtv_generation = 1; /* Used to detect when dtv size changes */ int tls_max_index = 1; /* Largest module index allocated */ +__weak_reference(dlsym, _dlsym); + /* * Fill in a DoneList with an allocation large enough to hold all of * the currently-loaded objects. Keep this as a macro since it calls --- sys/dev/usb/usbdevs.orig Mon Jun 19 06:17:48 2006 +++ sys/dev/usb/usbdevs Mon Jun 19 06:19:50 2006 @@ -406,6 +406,7 @@ vendor BOCARESEARCH 0x0885 Boca Research vendor ANDREA 0x08a8 Andrea vendor BURRBROWN 0x08bb Burr-Brown Japan +vendor PRECISE 0x08c3 Precise Biometrics vendor 2WIRE 0x08c8 2Wire vendor AIPTEK 0x08ca AIPTEK vendor SMARTBRIDGES 0x08d1 SmartBridges @@ -1418,6 +1419,9 @@ /* PNY products */ product PNY ATTACHE2 0x0010 USB 2.0 Flash Drive + +/* Precise Biometrics */ +product PRECISE 200MC 0x0402 Precise 200 MC biometrics scanner /* Primax products */ product PRIMAX G2X300 0x0300 G2-200 scanner --- sys/dev/usb/uscanner.c.orig Mon Jun 19 06:20:17 2006 +++ sys/dev/usb/uscanner.c Mon Jun 19 06:20:53 2006 @@ -175,6 +175,9 @@ /* Nikon */ {{ USB_VENDOR_NIKON, USB_PRODUCT_NIKON_LS40 }, 0 }, + /* Precise */ + {{ USB_VENDOR_PRECISE, USB_PRODUCT_PRECISE_200MC }, 0 }, + /* Primax */ {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2X300 }, 0 }, {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E300 }, 0 }, --- sys/dev/acpica/acpi_ec.c.orig Wed Aug 9 04:14:53 2006 +++ sys/dev/acpica/acpi_ec.c Wed Aug 9 04:21:38 2006 @@ -136,7 +136,7 @@ *****************************************************************************/ #include -__FBSDID("$FreeBSD: src/sys/dev/acpica/acpi_ec.c,v 1.65.2.2 2006/05/11 17:41:00 njl Exp $"); +__FBSDID("$FreeBSD: src/sys/dev/acpica/acpi_ec.c,v 1.71 2007/03/20 00:58:19 njl Exp $"); #include "opt_acpi.h" #include @@ -187,15 +187,15 @@ * | | | +--------- Burst Mode Enabled? * | | +----------- SCI Event? * | +------------- SMI Event? - * +--------------- + * +--------------- * */ typedef UINT8 EC_STATUS; #define EC_FLAG_OUTPUT_BUFFER ((EC_STATUS) 0x01) #define EC_FLAG_INPUT_BUFFER ((EC_STATUS) 0x02) +#define EC_FLAG_DATA_IS_CMD ((EC_STATUS) 0x08) #define EC_FLAG_BURST_MODE ((EC_STATUS) 0x10) -#define EC_FLAG_SCI ((EC_STATUS) 0x20) /* * EC_EVENT: @@ -207,6 +207,10 @@ #define EC_EVENT_OUTPUT_BUFFER_FULL ((EC_EVENT) 0x01) #define EC_EVENT_INPUT_BUFFER_EMPTY ((EC_EVENT) 0x02) #define EC_EVENT_SCI ((EC_EVENT) 0x20) +#define EC_EVENT_SMI ((EC_EVENT) 0x40) + +/* Data byte returned after burst enable indicating it was successful. */ +#define EC_BURST_ACK 0x90 /* * Register access primitives @@ -265,8 +269,11 @@ bus_space_tag_t ec_csr_tag; bus_space_handle_t ec_csr_handle; + struct mtx ec_mtx; int ec_glk; int ec_glkhandle; + int ec_burstactive; + int ec_sci_pend; }; /* @@ -276,11 +283,14 @@ */ #define EC_LOCK_TIMEOUT 1000 -/* Default interval in microseconds for the status polling loop. */ +/* Default delay in microseconds between each run of the status polling loop. */ #define EC_POLL_DELAY 10 -/* Total time in ms spent in the poll loop waiting for a response. */ -#define EC_POLL_TIMEOUT 100 +/* Default time in microseconds spent polling before sleep waiting. */ +#define EC_POLL_TIME 500 + +/* Total time in ms spent waiting for a response from EC. */ +#define EC_TIMEOUT 500 #define EVENT_READY(event, status) \ (((event) == EC_EVENT_OUTPUT_BUFFER_FULL && \ @@ -288,25 +298,51 @@ ((event) == EC_EVENT_INPUT_BUFFER_EMPTY && \ ((status) & EC_FLAG_INPUT_BUFFER) == 0)) -static int ec_poll_timeout = EC_POLL_TIMEOUT; -TUNABLE_INT("hw.acpi.ec.poll_timeout", &ec_poll_timeout); - ACPI_SERIAL_DECL(ec, "ACPI embedded controller"); +SYSCTL_DECL(_debug_acpi); +SYSCTL_NODE(_debug_acpi, OID_AUTO, ec, CTLFLAG_RD, NULL, "EC debugging"); + +static int ec_burst_mode; +TUNABLE_INT("debug.acpi.ec.burst", &ec_burst_mode); +SYSCTL_INT(_debug_acpi_ec, OID_AUTO, burst, CTLFLAG_RW, &ec_burst_mode, 0, + "Enable use of burst mode (faster for nearly all systems)"); +static int ec_poll_time = EC_POLL_TIME; +TUNABLE_INT("debug.acpi.ec.poll_time", &ec_poll_time); +SYSCTL_INT(_debug_acpi_ec, OID_AUTO, poll_time, CTLFLAG_RW, &ec_poll_time, + EC_POLL_TIME, "Time spent polling vs. sleeping (CPU intensive)"); +static int ec_timeout = EC_TIMEOUT; +TUNABLE_INT("debug.acpi.ec.timeout", &ec_timeout); +SYSCTL_INT(_debug_acpi_ec, OID_AUTO, timeout, CTLFLAG_RW, &ec_timeout, + EC_TIMEOUT, "Total time spent waiting for a response (poll+sleep)"); +static int ec_stall = 0; +TUNABLE_INT("debug.acpi.ec.stall", &ec_stall); +SYSCTL_INT(_debug_acpi_ec, OID_AUTO, stall, CTLFLAG_RW, &ec_stall, + 0, "Stall timeout"); + static __inline ACPI_STATUS -EcLock(struct acpi_ec_softc *sc) +EcLock(struct acpi_ec_softc *sc, int serialize) { ACPI_STATUS status; - /* Always acquire the exclusive lock. */ + /* + * If caller is executing a series of commands, acquire the exclusive lock + * to serialize with other users. + * To sync with bottom-half interrupt handler, always acquire the mutex. + */ status = AE_OK; - ACPI_SERIAL_BEGIN(ec); + if (serialize) + ACPI_SERIAL_BEGIN(ec); + mtx_lock(&sc->ec_mtx); /* If _GLK is non-zero, also acquire the global lock. */ if (sc->ec_glk) { status = AcpiAcquireGlobalLock(EC_LOCK_TIMEOUT, &sc->ec_glkhandle); - if (ACPI_FAILURE(status)) - ACPI_SERIAL_END(ec); + if (ACPI_FAILURE(status)) { + mtx_unlock(&sc->ec_mtx); + if (serialize) + ACPI_SERIAL_END(ec); + } } return (status); @@ -317,7 +353,9 @@ { if (sc->ec_glk) AcpiReleaseGlobalLock(sc->ec_glkhandle); - ACPI_SERIAL_END(ec); + mtx_unlock(&sc->ec_mtx); + if (sx_xlocked(&ec_sxlock)) + ACPI_SERIAL_END(ec); } static uint32_t EcGpeHandler(void *Context); @@ -558,6 +596,7 @@ params = acpi_get_private(dev); sc->ec_dev = dev; sc->ec_handle = acpi_get_handle(dev); + mtx_init(&sc->ec_mtx, "ACPI EC lock", NULL, MTX_DEF); /* Retrieve previously probed values via device ivars. */ sc->ec_glk = params->glk; @@ -640,6 +679,7 @@ if (sc->ec_data_res) bus_release_resource(sc->ec_dev, SYS_RES_IOPORT, sc->ec_data_rid, sc->ec_data_res); + mtx_destroy(&sc->ec_mtx); return (ENXIO); } @@ -687,13 +727,13 @@ struct acpi_ec_softc *sc = (struct acpi_ec_softc *)Context; UINT8 Data; ACPI_STATUS Status; - EC_STATUS EcStatus; char qxx[5]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); KASSERT(Context != NULL, ("EcGpeQueryHandler called with NULL")); - Status = EcLock(sc); + /* Serialize user access with EcSpaceHandler(). */ + Status = EcLock(sc, TRUE); if (ACPI_FAILURE(Status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "GpeQuery lock error: %s\n", AcpiFormatException(Status)); @@ -701,19 +741,6 @@ } /* - * If the EC_SCI bit of the status register is not set, then pass - * it along to any potential waiters as it may be an IBE/OBF event. - */ - EcStatus = EC_GET_CSR(sc); - if ((EcStatus & EC_EVENT_SCI) == 0) { - CTR1(KTR_ACPI, "ec event was not SCI, status %#x", EcStatus); - sc->ec_csrvalue = EcStatus; - wakeup(&sc->ec_csrvalue); - EcUnlock(sc); - goto re_enable; - } - - /* * Send a query command to the EC to find out which _Qxx call it * wants to make. This command clears the SCI bit and also the * interrupt source since we are edge-triggered. @@ -726,6 +753,9 @@ goto re_enable; } Data = EC_GET_DATA(sc); + sc->ec_sci_pend = FALSE; + + /* Drop locks before evaluating _Qxx method since it may trigger GPEs. */ EcUnlock(sc); /* Ignore the value for "no outstanding event". (13.3.5) */ @@ -734,7 +764,7 @@ goto re_enable; /* Evaluate _Qxx to respond to the controller. */ - sprintf(qxx, "_Q%02x", Data); + snprintf(qxx, sizeof(qxx), "_Q%02x", Data); AcpiUtStrupr(qxx); Status = AcpiEvaluateObject(sc->ec_handle, qxx, NULL, NULL); if (ACPI_FAILURE(Status) && Status != AE_NOT_FOUND) { @@ -745,7 +775,7 @@ re_enable: /* Re-enable the GPE event so we'll get future requests. */ - Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_NOT_ISR); + Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_ISR); if (ACPI_FAILURE(Status)) printf("EcGpeQueryHandler: AcpiEnableEvent failed\n"); } @@ -760,27 +790,70 @@ { struct acpi_ec_softc *sc = Context; ACPI_STATUS Status; + EC_STATUS EcStatus; + int query_pend; KASSERT(Context != NULL, ("EcGpeHandler called with NULL")); /* * Disable further GPEs while we handle this one. Since we are directly * called by ACPI-CA and it may have unknown locks held, we specify the - * ACPI_ISR flag to keep it from acquiring any more mutexes (which could - * potentially sleep.) + * ACPI_ISR flag to keep it from acquiring any more mutexes (although + * sleeping would be ok since we're in an ithread.) */ AcpiDisableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_ISR); - /* Schedule the GPE query handler. */ - Status = AcpiOsQueueForExecution(OSD_PRIORITY_GPE, EcGpeQueryHandler, - Context); + /* For interrupt (GPE) handler, don't acquire serialization lock. */ + Status = EcLock(sc, FALSE); if (ACPI_FAILURE(Status)) { - printf("Queuing GPE query handler failed.\n"); + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "GpeQuery lock error: %s\n", AcpiFormatException(Status)); + return (-1); + } + + /* + * If burst was active, but the status bit was cleared, the EC had to + * exit burst mode for some reason. Record this for later. + */ + EcStatus = EC_GET_CSR(sc); + if (sc->ec_burstactive && (EcStatus & EC_FLAG_BURST_MODE) == 0) { + CTR0(KTR_ACPI, "ec burst disabled in query handler"); + sc->ec_burstactive = FALSE; + } + + /* + * If the EC_SCI bit of the status register is not set, then pass + * it along to any potential waiters as it may be an IBE/OBF event. + * If it is set, queue a query handler. + */ + query_pend = FALSE; + if ((EcStatus & EC_EVENT_SCI) == 0) { + CTR1(KTR_ACPI, "ec event was IBE/OBF, status %#x", EcStatus); + sc->ec_csrvalue = EcStatus; + wakeup(&sc->ec_csrvalue); + } else if (!sc->ec_sci_pend) { + /* SCI bit set and no pending query handler, so schedule one. */ + CTR0(KTR_ACPI, "ec queueing gpe handler"); + Status = AcpiOsQueueForExecution(OSD_PRIORITY_GPE, EcGpeQueryHandler, + Context); + if (ACPI_SUCCESS(Status)) { + sc->ec_sci_pend = TRUE; + query_pend = TRUE; + } else + printf("Queuing GPE query handler failed.\n"); + } + + /* + * If we didn't queue a query handler, which will eventually re-enable + * the GPE, re-enable it right now so we can get more events. + */ + if (!query_pend) { Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit, ACPI_ISR); if (ACPI_FAILURE(Status)) - printf("EcGpeHandler: AcpiEnableEvent failed\n"); + printf("EcGpeHandler: AcpiEnableGpe failed\n"); } + EcUnlock(sc); return (0); } @@ -824,7 +897,8 @@ EcAddr = Address; Status = AE_ERROR; - Status = EcLock(sc); + /* Grab serialization lock to hold across command sequence. */ + Status = EcLock(sc, TRUE); if (ACPI_FAILURE(Status)) return_ACPI_STATUS (Status); @@ -859,87 +933,105 @@ { EC_STATUS EcStatus; ACPI_STATUS Status; - int count, i, period, retval, slp_ival; + int count, i, retval, slp_ival; ACPI_SERIAL_ASSERT(ec); Status = AE_NO_HARDWARE_RESPONSE; - - /* - * Wait for 1 us before checking the CSR. Testing shows about - * 50% of requests complete in 1 us and 90% of them complete - * in 5 us or less. - */ - AcpiOsStall(1); + EcStatus = 0; /* - * Poll the EC status register for up to 1 ms in chunks of 10 us - * to detect completion of the last command. + * Poll for up to ec_poll_time microseconds since many ECs complete + * the command quickly, especially if in burst mode. */ - for (i = 0; i < 1000 / EC_POLL_DELAY; i++) { +#if 0 /* Enable this as a possible workaround if EC times out. */ + AcpiOsStall(EC_POLL_DELAY); +#endif + if (ec_stall > 0) + AcpiOsStall(ec_stall); + count = ec_poll_time / EC_POLL_DELAY; + if (count <= 0) + count = 1; + for (i = 0; i < count; i++) { EcStatus = EC_GET_CSR(sc); + if (sc->ec_burstactive && (EcStatus & EC_FLAG_BURST_MODE) == 0) { + CTR0(KTR_ACPI, "ec burst disabled in waitevent (poll)"); + sc->ec_burstactive = FALSE; + } if (EVENT_READY(Event, EcStatus)) { + CTR1(KTR_ACPI, "ec poll wait ready, status %#x", EcStatus); Status = AE_OK; break; } AcpiOsStall(EC_POLL_DELAY); } - period = i * EC_POLL_DELAY; /* * If we still don't have a response and we're up and running, wait up - * to ec_poll_timeout ms for completion, sleeping for chunks of 10 ms. + * to ec_timeout ms for completion, sleeping for chunks of 1 ms or the + * smallest resolution hz supports. */ slp_ival = 0; if (Status != AE_OK) { retval = ENXIO; - count = ec_poll_timeout / 10; - if (count == 0) - count = 1; - slp_ival = hz / 100; - if (slp_ival == 0) - slp_ival = 1; + if (!cold) { + slp_ival = hz / 1000; + if (slp_ival != 0) { + count = ec_timeout / slp_ival; + } else { + /* hz has less than 1000 Hz resolution so scale timeout. */ + slp_ival = 1; + count = ec_timeout / (1000 / hz); + } + } else + count = ec_timeout; for (i = 0; i < count; i++) { if (retval != 0) EcStatus = EC_GET_CSR(sc); else EcStatus = sc->ec_csrvalue; + if (sc->ec_burstactive && (EcStatus & EC_FLAG_BURST_MODE) == 0) { + CTR0(KTR_ACPI, "ec burst disabled in waitevent (slp)"); + sc->ec_burstactive = FALSE; + } if (EVENT_READY(Event, EcStatus)) { + CTR1(KTR_ACPI, "ec sleep wait ready, status %#x", EcStatus); Status = AE_OK; break; } - if (!cold) - retval = tsleep(&sc->ec_csrvalue, PZERO, "ecpoll", slp_ival); - else - AcpiOsStall(10000); + if (!cold) { + retval = msleep(&sc->ec_csrvalue, &sc->ec_mtx, PZERO, "ecpoll", + slp_ival); + } else + AcpiOsStall(1000); } } - /* Calculate new delay and log it. */ - if (slp_ival > 0) - period += i * 10000; - CTR2(KTR_ACPI, "ec got event %#x after %d us", EcStatus, period); - return (Status); } static ACPI_STATUS EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd) { - ACPI_STATUS Status; - EC_EVENT Event; + ACPI_STATUS status; + EC_EVENT event; + EC_STATUS ec_status; ACPI_SERIAL_ASSERT(ec); + /* Don't use burst mode if user disabled it. */ + if (!ec_burst_mode && cmd == EC_COMMAND_BURST_ENABLE) + return (AE_ERROR); + /* Decide what to wait for based on command type. */ switch (cmd) { case EC_COMMAND_READ: case EC_COMMAND_WRITE: case EC_COMMAND_BURST_DISABLE: - Event = EC_EVENT_INPUT_BUFFER_EMPTY; + event = EC_EVENT_INPUT_BUFFER_EMPTY; break; case EC_COMMAND_QUERY: case EC_COMMAND_BURST_ENABLE: - Event = EC_EVENT_OUTPUT_BUFFER_FULL; + event = EC_EVENT_OUTPUT_BUFFER_FULL; break; default: ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), @@ -948,50 +1040,64 @@ } /* Run the command and wait for the chosen event. */ + CTR1(KTR_ACPI, "ec running command %#x", cmd); EC_SET_CSR(sc, cmd); - Status = EcWaitEvent(sc, Event); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, event); + if (ACPI_SUCCESS(status)) { + /* If we succeeded, burst flag should now be present. */ + if (cmd == EC_COMMAND_BURST_ENABLE) { + ec_status = EC_GET_CSR(sc); + if ((ec_status & EC_FLAG_BURST_MODE) == 0) + status = AE_ERROR; + } + } else { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcCommand: no response to %#x\n", cmd); } - return (Status); + return (status); } static ACPI_STATUS EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) { - ACPI_STATUS Status; + ACPI_STATUS status; + UINT8 data; ACPI_SERIAL_ASSERT(ec); CTR1(KTR_ACPI, "ec read from %#x", Address); -#ifdef notyet /* If we can't start burst mode, continue anyway. */ - EcCommand(sc, EC_COMMAND_BURST_ENABLE); -#endif + status = EcCommand(sc, EC_COMMAND_BURST_ENABLE); + if (status == AE_OK) { + data = EC_GET_DATA(sc); + if (data == EC_BURST_ACK) { + CTR0(KTR_ACPI, "ec burst enabled"); + sc->ec_burstactive = TRUE; + } + } - Status = EcCommand(sc, EC_COMMAND_READ); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_READ); + if (ACPI_FAILURE(status)) + return (status); EC_SET_DATA(sc, Address); - Status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL); + if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcRead: Failed waiting for EC to send data.\n"); - return (Status); + return (status); } *Data = EC_GET_DATA(sc); -#ifdef notyet if (sc->ec_burstactive) { - Status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); + if (ACPI_FAILURE(status)) + return (status); + sc->ec_burstactive = FALSE; + CTR0(KTR_ACPI, "ec disabled burst ok"); } -#endif return (AE_OK); } @@ -999,43 +1105,49 @@ static ACPI_STATUS EcWrite(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) { - ACPI_STATUS Status; + ACPI_STATUS status; + UINT8 data; ACPI_SERIAL_ASSERT(ec); CTR2(KTR_ACPI, "ec write to %#x, data %#x", Address, *Data); -#ifdef notyet /* If we can't start burst mode, continue anyway. */ - EcCommand(sc, EC_COMMAND_BURST_ENABLE); -#endif + status = EcCommand(sc, EC_COMMAND_BURST_ENABLE); + if (status == AE_OK) { + data = EC_GET_DATA(sc); + if (data == EC_BURST_ACK) { + CTR0(KTR_ACPI, "ec burst enabled"); + sc->ec_burstactive = TRUE; + } + } - Status = EcCommand(sc, EC_COMMAND_WRITE); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_WRITE); + if (ACPI_FAILURE(status)) + return (status); EC_SET_DATA(sc, Address); - Status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); + if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcRead: Failed waiting for EC to process address\n"); - return (Status); + return (status); } EC_SET_DATA(sc, *Data); - Status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); - if (ACPI_FAILURE(Status)) { + status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY); + if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), "EcWrite: Failed waiting for EC to process data\n"); - return (Status); + return (status); } -#ifdef notyet if (sc->ec_burstactive) { - Status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); - if (ACPI_FAILURE(Status)) - return (Status); + status = EcCommand(sc, EC_COMMAND_BURST_DISABLE); + if (ACPI_FAILURE(status)) + return (status); + sc->ec_burstactive = FALSE; + CTR0(KTR_ACPI, "ec disabled burst ok"); } -#endif return (AE_OK); } --- sys/amd64/amd64/machdep.c.orig 30 Oct 2006 18:03:02 -0000 1.638.2.10 +++ sys/amd64/amd64/machdep.c 17 Nov 2006 02:25:51 -0000 @@ -532,6 +532,7 @@ * help lock contention somewhat, and this is critical for HTT. -Peter */ static int cpu_idle_hlt = 1; +TUNABLE_INT("machdep.cpu_idle_hlt", &cpu_idle_hlt); SYSCTL_INT(_machdep, OID_AUTO, cpu_idle_hlt, CTLFLAG_RW, &cpu_idle_hlt, 0, "Idle loop HLT enable"); --- sys/i386/i386/machdep.c.orig 11 Sep 2006 18:41:31 -0000 1.616.2.8 +++ sys/i386/i386/machdep.c 17 Nov 2006 02:25:52 -0000 @@ -1107,6 +1107,7 @@ * help lock contention somewhat, and this is critical for HTT. -Peter */ static int cpu_idle_hlt = 1; +TUNABLE_INT("machdep.cpu_idle_hlt", &cpu_idle_hlt); SYSCTL_INT(_machdep, OID_AUTO, cpu_idle_hlt, CTLFLAG_RW, &cpu_idle_hlt, 0, "Idle loop HLT enable"); --- sys/dev/ata/ata-chipset.c.orig Sun Jan 14 17:42:26 2007 +++ sys/dev/ata/ata-chipset.c Sun Jan 14 17:35:10 2007 @@ -1251,9 +1251,11 @@ {{ ATA_ATI_IXP200, 0x00, 0, 0, ATA_UDMA5, "IXP200" }, { ATA_ATI_IXP300, 0x00, 0, 0, ATA_UDMA6, "IXP300" }, { ATA_ATI_IXP400, 0x00, 0, 0, ATA_UDMA6, "IXP400" }, + { ATA_ATI_SB600, 0x00, 0, 0, ATA_UDMA6, "SB600" }, { ATA_ATI_IXP300_S1, 0x00, SIIMEMIO, 0, ATA_SA150, "IXP300" }, { ATA_ATI_IXP400_S1, 0x00, SIIMEMIO, 0, ATA_SA150, "IXP400" }, { ATA_ATI_IXP400_S2, 0x00, SIIMEMIO, 0, ATA_SA150, "IXP400" }, + { ATA_ATI_SB600_S1, 0x00, 0, AHCI, ATA_SA150, "SB600" }, { 0, 0, 0, 0, 0, 0}}; char buffer[64]; @@ -1265,7 +1267,10 @@ device_set_desc_copy(dev, buffer); ctlr->chip = idx; - /* the ATI SATA controller is actually a SiI 3112 controller*/ + /* + * the ATI SATA controller is actually a SiI 3112 controller, + * with exception of later SB600 which are mostly AHCI. + */ if (ctlr->chip->cfg1 & SIIMEMIO) ctlr->chipinit = ata_sii_chipinit; else @@ -1280,6 +1285,15 @@ if (ata_setup_interrupt(dev)) return ENXIO; + + if (ctlr->chip->cfg2 == AHCI) { + ctlr->r_type2 = SYS_RES_MEMORY; + ctlr->r_rid2 = PCIR_BAR(5); + if (!(ctlr->r_res2 = bus_alloc_resource_any(dev, ctlr->r_type2, + &ctlr->r_rid2, RF_ACTIVE))) + return ENXIO; + return ata_ahci_chipinit(dev); + } ctlr->setmode = ata_ati_setmode; return 0; --- sys/dev/ata/ata-pci.h.orig Sun Jan 14 17:42:31 2007 +++ sys/dev/ata/ata-pci.h Sun Jan 14 17:35:10 2007 @@ -102,6 +102,8 @@ #define ATA_ATI_IXP300_S1 0x436e1002 #define ATA_ATI_IXP400_S1 0x43791002 #define ATA_ATI_IXP400_S2 0x437a1002 +#define ATA_ATI_SB600 0x438c1002 +#define ATA_ATI_SB600_S1 0x43801002 #define ATA_CENATEK_ID 0x16ca #define ATA_CENATEK_ROCKET 0x000116ca --- sys/modules/ata/Makefile.orig Sun Jan 14 03:45:34 2007 +++ sys/modules/ata/Makefile Sun Jan 14 03:46:03 2007 @@ -8,6 +8,6 @@ SUBDIR += ataisa .endif SUBDIR += atapci -SUBDIR += atadisk atapicd atapifd atapist ataraid atapicam #atacam +SUBDIR += atadisk atapicd atapifd atapist ataraid atausb atapicam #atacam .include --- sys/modules/ata/atausb/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/ata/atausb/Makefile Sat Jan 13 18:33:12 2007 @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../dev/ata + +KMOD= atausb +SRCS= ata-usb.c +SRCS+= opt_ata.h opt_usb.h ata_if.h device_if.h bus_if.h pci_if.h + +.include --- usr.sbin/powerd/powerd.c.orig Mon Jan 16 01:50:37 2006 +++ usr.sbin/powerd/powerd.c Tue Mar 13 05:15:18 2007 @@ -50,6 +50,12 @@ #define DEFAULT_ACTIVE_PERCENT 65 #define DEFAULT_IDLE_PERCENT 90 #define DEFAULT_POLL_INTERVAL 500 /* Poll interval in milliseconds */ +#define DEFAULT_TZ_SUSPEND 10 /* TZ suspend in seconds */ + +#define TZ_ZEROC 2732 +#define TZ_MAXC 12732 +#define TZ_KELVTOC(x) (((x) - TZ_ZEROC) / 10), abs(((x) - TZ_ZEROC) % 10) +#define TZ_CTOKELV(x) ((int)((x) * 10 + TZ_ZEROC)) enum modes_t { MODE_MIN, @@ -77,6 +83,8 @@ static int set_freq(int freq); static void acline_init(void); static int acline_read(void); +static void tz_init(void); +static int tz_read(void); static void handle_sigs(int sig); static void parse_mode(char *arg, int *mode, int ch); static void usage(void); @@ -86,6 +94,8 @@ static int freq_mib[4]; static int levels_mib[4]; static int acline_mib[3]; +static int tz_psv_mib[5]; +static int tz_cur_mib[5]; /* Configuration */ static int cpu_running_mark; @@ -95,6 +105,8 @@ static int apm_fd; static int exit_requested; +static int tz_psv; + static int read_usage_times(long *idle, long *total) { @@ -225,6 +237,62 @@ } static void +tz_init() +{ + size_t len; + + if (tz_psv == 0) { + len = 5; + if (sysctlnametomib("hw.acpi.thermal.tz0._PSV", tz_psv_mib, + &len)) { + tz_psv = -1; + return; + } + + len = 0; + if (sysctl(tz_psv_mib, 5, NULL, &len, NULL, 0) || + len != sizeof(tz_psv)) { + tz_psv = -1; + return; + } + + if (sysctl(tz_psv_mib, 5, &tz_psv, &len, NULL, 0)) { + tz_psv = -1; + return; + } + } + + len = 5; + if (sysctlnametomib("hw.acpi.thermal.tz0.temperature", + tz_cur_mib, &len)) { + tz_psv = -1; + return; + } + + len = 0; + if (sysctl(tz_cur_mib, 5, NULL, &len, NULL, 0) || len != sizeof(int)) { + tz_psv = -1; + return; + } +} + +static int +tz_read() +{ + int tz_cur; + size_t len; + + if (tz_psv == -1) + return (-1); + + len = sizeof(tz_cur); + if (sysctl(tz_cur_mib, 5, &tz_cur, &len, NULL, 0)) + return (-1); + + return (tz_cur); +} + +static void parse_mode(char *arg, int *mode, int ch) { @@ -249,7 +317,7 @@ { fprintf(stderr, -"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-P pidfile]\n"); +"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%] [-t] [-z C] [-Z sec] [-P pidfile]\n"); exit(1); } @@ -261,6 +329,7 @@ long idle, total; int curfreq, *freqs, i, *mwatts, numfreqs; int ch, mode_ac, mode_battery, mode_none, acline, mode, vflag; + int curtz, curtzsuspend, tz_suspend; uint64_t mjoules_used; size_t len; @@ -272,12 +341,17 @@ mjoules_used = 0; vflag = 0; apm_fd = -1; + tz_psv = -1; + curtz = -1; + tz_suspend = DEFAULT_TZ_SUSPEND * 1000000; + curtzsuspend = 0; + /* User must be root to control frequencies. */ if (geteuid() != 0) errx(1, "must be root to run"); - while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:v")) != EOF) + while ((ch = getopt(argc, argv, "a:b:i:n:p:P:r:tvz:Z:")) != EOF) switch (ch) { case 'a': parse_mode(optarg, &mode_ac, ch); @@ -314,9 +388,27 @@ usage(); } break; + case 't': + tz_psv = 0; + break; case 'v': vflag = 1; break; + case 'z': + tz_psv = TZ_CTOKELV(strtod(optarg, NULL)); + if (tz_psv < TZ_ZEROC || tz_psv > TZ_MAXC) { + warnx("%s is out of temperature range", optarg); + usage(); + } + break; + case 'Z': + tz_suspend = atoi(optarg); + if (tz_suspend < 0 || tz_suspend > 1800) { + warnx("%s is out of tz suspend range", optarg); + usage(); + } + tz_suspend *= 1000000; + break; default: usage(); } @@ -335,6 +427,20 @@ if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len)) err(1, "lookup freq_levels"); + /* Thermal Zone throttling */ + if (tz_psv != -1) { + tz_init(); + curtz = tz_read(); + } + if (curtz == -1) + tz_psv = -1; + if (vflag && tz_psv != -1 && curtz != -1) { + printf("tz throttling: %d.%dC (current: %d.%dC) " + "suspend: %d seconds\n", + TZ_KELVTOC(tz_psv), TZ_KELVTOC(curtz), + tz_suspend / 1000000); + } + /* Check if we can read the idle time and supported freqs. */ if (read_usage_times(NULL, NULL)) err(1, "read_usage_times"); @@ -417,6 +523,38 @@ if (i < numfreqs && mwatts[i] != -1) mjoules_used += (mwatts[i] * (poll_ival / 1000)) / 1000; + } + + if (tz_psv != -1 && (curtz = tz_read()) != -1 && + curtz > tz_psv) { + for (i = 0; i < numfreqs; i++) { + if (freqs[i] == curfreq) + break; + } + i++; + if (i < numfreqs) { + if (vflag) { + printf("tz %d.%dC > %d.%dC, " + "decreasing clock speed from " + "%d MHz to %d MHz\n", + TZ_KELVTOC(curtz), + TZ_KELVTOC(tz_psv), curfreq, + freqs[i]); + } + if (set_freq(freqs[i]) != 0) + warn("error setting CPU frequency %d", + freqs[i]); + } + curtzsuspend = tz_suspend; + continue; + } + + if (curtzsuspend > 0) { + if (vflag) + printf("tz suspend: %d seconds left\n", + curtzsuspend / 1000000); + curtzsuspend -= poll_ival; + continue; } /* Always switch to the lowest frequency in min mode. */ --- sys/kern/kern_conf.c.orig Thu May 17 05:16:53 2007 +++ sys/kern/kern_conf.c Thu May 17 04:52:44 2007 @@ -758,7 +758,7 @@ u *= 10; u += name[i++] - '0'; } - if (u > 0xffffff) + if (u > CLONE_UNITMASK) return (0); *unit = u; if (namep)