From 06ec0382c9152eebe0d5fb634449d5c6926b222f Mon Sep 17 00:00:00 2001 From: mav Date: Thu, 19 Jan 2012 01:55:48 +0000 Subject: Two 192/24/8 playback streams overflow single mandatory output line (SDO) of HDA bus. Handle that from two directions: - Add support for "striping" (using several SDO lines), if supported. - Account HDA bus utilization and return error on new stream allocation attempt if remaining bandwidth is unsifficient. Most of HDA controllers have one SDO line with 46Mbps output bandwidth. NVIDIA GF210 has 2 lines - 92Mbps. NVIDIA GF520 has 4 lines - 184Mbps! MFC after: 2 months Sponsored by: iXsystems, Inc. --- sys/dev/sound/pci/hda/hdaa.c | 37 +++++++++++++++++---- sys/dev/sound/pci/hda/hdaa.h | 7 +++- sys/dev/sound/pci/hda/hdac.c | 64 +++++++++++++++++++++++++++++++----- sys/dev/sound/pci/hda/hdac_if.m | 1 + sys/dev/sound/pci/hda/hdac_private.h | 5 +++ sys/dev/sound/pci/hda/hdacc.c | 4 +-- 6 files changed, 101 insertions(+), 17 deletions(-) diff --git a/sys/dev/sound/pci/hda/hdaa.c b/sys/dev/sound/pci/hda/hdaa.c index 20720ad..1c3f5b9 100644 --- a/sys/dev/sound/pci/hda/hdaa.c +++ b/sys/dev/sound/pci/hda/hdaa.c @@ -1096,6 +1096,11 @@ hdaa_widget_parse(struct hdaa_widget *w) w->param.supp_pcm_size_rate = w->devinfo->supp_pcm_size_rate; } + if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { + w->wclass.conv.stripecap = hda_command(dev, + HDA_CMD_GET_STRIPE_CONTROL(0, w->nid)) >> 20; + } else + w->wclass.conv.stripecap = 1; } else { w->param.supp_stream_formats = 0; w->param.supp_pcm_size_rate = 0; @@ -1388,6 +1393,18 @@ hdaa_stream_format(struct hdaa_chan *ch) return (fmt); } +static int +hdaa_allowed_stripes(uint16_t fmt) +{ + static const int bits[8] = { 8, 16, 20, 24, 32, 32, 32, 32 }; + int size; + + size = bits[(fmt >> 4) & 0x03]; + size *= (fmt & 0x0f) + 1; + size *= ((fmt >> 11) & 0x07) + 1; + return (0xffffffffU >> (32 - fls(size / 8))); +} + static void hdaa_audio_setup(struct hdaa_chan *ch) { @@ -1462,6 +1479,10 @@ hdaa_audio_setup(struct hdaa_chan *ch) } hda_command(ch->devinfo->dev, HDA_CMD_SET_CONV_STREAM_CHAN(0, ch->io[i], c)); + if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) { + hda_command(ch->devinfo->dev, + HDA_CMD_SET_STRIPE_CONTROL(0, w->nid, ch->stripectl)); + } cchn = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (cchn > 1 && chn < totalchn) { cchn = min(cchn, totalchn - chn - 1); @@ -1472,9 +1493,9 @@ hdaa_audio_setup(struct hdaa_chan *ch) device_printf(ch->pdevinfo->dev, "PCMDIR_%s: Stream setup nid=%d: " "fmt=0x%04x, dfmt=0x%04x, chan=0x%04x, " - "chan_count=0x%02x\n", + "chan_count=0x%02x, stripe=%d\n", (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", - ch->io[i], fmt, dfmt, c, cchn); + ch->io[i], fmt, dfmt, c, cchn, ch->stripectl); ); for (j = 0; j < 16; j++) { if (as->dacs[ch->asindex][j] != ch->io[i]) @@ -1658,11 +1679,12 @@ static int hdaa_channel_start(struct hdaa_chan *ch) { struct hdaa_devinfo *devinfo = ch->devinfo; + uint32_t fmt; - ch->ptr = 0; - ch->prevptr = 0; + fmt = hdaa_stream_format(ch); + ch->stripectl = fls(ch->stripecap & hdaa_allowed_stripes(fmt)) - 1; ch->sid = HDAC_STREAM_ALLOC(device_get_parent(devinfo->dev), devinfo->dev, - ch->dir == PCMDIR_PLAY ? 1 : 0, hdaa_stream_format(ch), &ch->dmapos); + ch->dir == PCMDIR_PLAY ? 1 : 0, fmt, ch->stripectl, &ch->dmapos); if (ch->sid <= 0) return (EBUSY); hdaa_audio_setup(ch); @@ -4464,6 +4486,7 @@ hdaa_pcmchannel_setup(struct hdaa_chan *ch) ch->bit32 = 0; ch->pcmrates[0] = 48000; ch->pcmrates[1] = 0; + ch->stripecap = 0xff; ret = 0; channels = 0; @@ -4506,6 +4529,7 @@ hdaa_pcmchannel_setup(struct hdaa_chan *ch) pcmcap &= w->param.supp_pcm_size_rate; } ch->io[ret++] = as[ch->as].dacs[ch->asindex][i]; + ch->stripecap &= w->wclass.conv.stripecap; /* Do not count redirection pin/dac channels. */ if (i == 15 && as[ch->as].hpredir >= 0) continue; @@ -5001,7 +5025,8 @@ hdaa_dump_nodes(struct hdaa_devinfo *devinfo) if (HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(w->param.widget_cap)) printf(" PROC"); if (HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(w->param.widget_cap)) - printf(" STRIPE"); + printf(" STRIPE(x%d)", + 1 << (fls(w->wclass.conv.stripecap) - 1)); j = HDA_PARAM_AUDIO_WIDGET_CAP_CC(w->param.widget_cap); if (j == 1) printf(" STEREO"); diff --git a/sys/dev/sound/pci/hda/hdaa.h b/sys/dev/sound/pci/hda/hdaa.h index 7050ff8..3642d45 100644 --- a/sys/dev/sound/pci/hda/hdaa.h +++ b/sys/dev/sound/pci/hda/hdaa.h @@ -116,6 +116,9 @@ struct hdaa_widget { uint32_t cap; uint32_t ctrl; } pin; + struct { + uint8_t stripecap; + } conv; } wclass; }; @@ -201,7 +204,7 @@ struct hdaa_chan { struct hdaa_pcm_devinfo *pdevinfo; uint32_t spd, fmt, fmtlist[32], pcmrates[16]; uint32_t supp_stream_formats, supp_pcm_size_rate; - uint32_t ptr, prevptr, blkcnt, blksz; + uint32_t blkcnt, blksz; uint32_t *dmapos; uint32_t flags; int dir; @@ -212,6 +215,8 @@ struct hdaa_chan { int as; /* Number of association. */ int asindex; /* Index within association. */ nid_t io[16]; + uint8_t stripecap; /* AND of stripecap of all ios. */ + uint8_t stripectl; /* stripe to use to all ios. */ }; #define hdaa_codec_id(devinfo) \ diff --git a/sys/dev/sound/pci/hda/hdac.c b/sys/dev/sound/pci/hda/hdac.c index 9a8dcee..402070d 100644 --- a/sys/dev/sound/pci/hda/hdac.c +++ b/sys/dev/sound/pci/hda/hdac.c @@ -1339,10 +1339,10 @@ sysctl_hdac_pindump(SYSCTL_HANDLER_ARGS) } static int -hdac_data_rate(uint16_t fmt) +hdac_mdata_rate(uint16_t fmt) { - static const int bits[8] = { 8, 16, 20, 24, 32, 32, 32, 32 }; - int rate; + static const int mbits[8] = { 8, 16, 32, 32, 32, 32, 32, 32 }; + int rate, bits; if (fmt & (1 << 14)) rate = 44100; @@ -1350,8 +1350,24 @@ hdac_data_rate(uint16_t fmt) rate = 48000; rate *= ((fmt >> 11) & 0x07) + 1; rate /= ((fmt >> 8) & 0x07) + 1; - rate *= ((bits[(fmt >> 4) & 0x03]) * ((fmt & 0x0f) + 1) + 7) / 8; - return (rate); + bits = mbits[(fmt >> 4) & 0x03]; + bits *= (fmt & 0x0f) + 1; + return (rate * bits); +} + +static int +hdac_bdata_rate(uint16_t fmt, int output) +{ + static const int bbits[8] = { 8, 16, 20, 24, 32, 32, 32, 32 }; + int rate, bits; + + rate = 48000; + rate *= ((fmt >> 11) & 0x07) + 1; + bits = bbits[(fmt >> 4) & 0x03]; + bits *= (fmt & 0x0f) + 1; + if (!output) + bits = ((bits + 7) & ~0x07) + 10; + return (rate * bits); } static void @@ -1369,7 +1385,7 @@ hdac_poll_reinit(struct hdac_softc *sc) if (s->running == 0) continue; pollticks = ((uint64_t)hz * s->blksz) / - hdac_data_rate(s->format); + (hdac_mdata_rate(s->format) / 8); pollticks >>= 1; if (pollticks > hz) pollticks = hz; @@ -1790,11 +1806,12 @@ hdac_find_stream(struct hdac_softc *sc, int dir, int stream) } static int -hdac_stream_alloc(device_t dev, device_t child, int dir, int format, +hdac_stream_alloc(device_t dev, device_t child, int dir, int format, int stripe, uint32_t **dmapos) { struct hdac_softc *sc = device_get_softc(dev); - int stream, ss; + nid_t cad = (uintptr_t)device_get_ivars(child); + int stream, ss, bw, maxbw, prevbw; /* Look for empty stream. */ ss = hdac_find_stream(sc, dir, 0); @@ -1803,6 +1820,28 @@ hdac_stream_alloc(device_t dev, device_t child, int dir, int format, if (ss < 0) return (0); + /* Check bus bandwidth. */ + bw = hdac_bdata_rate(format, dir); + if (dir == 1) { + bw *= 1 << (sc->num_sdo - stripe); + prevbw = sc->sdo_bw_used; + maxbw = 48000 * 960 * (1 << sc->num_sdo); + } else { + prevbw = sc->codecs[cad].sdi_bw_used; + maxbw = 48000 * 464; + } + HDA_BOOTHVERBOSE( + device_printf(dev, "%dKbps of %dKbps bandwidth used%s\n", + (bw + prevbw) / 1000, maxbw / 1000, + bw + prevbw > maxbw ? " -- OVERFLOW!" : ""); + ); + if (bw + prevbw > maxbw) + return (0); + if (dir == 1) + sc->sdo_bw_used += bw; + else + sc->codecs[cad].sdi_bw_used += bw; + /* Allocate stream number */ if (ss >= sc->num_iss + sc->num_oss) stream = 15 - (ss - sc->num_iss + sc->num_oss); @@ -1814,7 +1853,9 @@ hdac_stream_alloc(device_t dev, device_t child, int dir, int format, sc->streams[ss].dev = child; sc->streams[ss].dir = dir; sc->streams[ss].stream = stream; + sc->streams[ss].bw = bw; sc->streams[ss].format = format; + sc->streams[ss].stripe = stripe; if (dmapos != NULL) { if (sc->pos_dma.dma_vaddr != NULL) *dmapos = (uint32_t *)(sc->pos_dma.dma_vaddr + ss * 8); @@ -1828,11 +1869,16 @@ static void hdac_stream_free(device_t dev, device_t child, int dir, int stream) { struct hdac_softc *sc = device_get_softc(dev); + nid_t cad = (uintptr_t)device_get_ivars(child); int ss; ss = hdac_find_stream(sc, dir, stream); KASSERT(ss >= 0, ("Free for not allocated stream (%d/%d)\n", dir, stream)); + if (dir == 1) + sc->sdo_bw_used -= sc->streams[ss].bw; + else + sc->codecs[cad].sdi_bw_used -= sc->streams[ss].bw; sc->streams[ss].stream = 0; sc->streams[ss].dev = NULL; } @@ -1875,6 +1921,8 @@ hdac_stream_start(device_t dev, device_t child, ctl &= ~HDAC_SDCTL2_DIR; ctl &= ~HDAC_SDCTL2_STRM_MASK; ctl |= stream << HDAC_SDCTL2_STRM_SHIFT; + ctl &= ~HDAC_SDCTL2_STRIPE_MASK; + ctl |= sc->streams[ss].stripe << HDAC_SDCTL2_STRIPE_SHIFT; HDAC_WRITE_1(&sc->mem, off + HDAC_SDCTL2, ctl); HDAC_WRITE_2(&sc->mem, off + HDAC_SDFMT, sc->streams[ss].format); diff --git a/sys/dev/sound/pci/hda/hdac_if.m b/sys/dev/sound/pci/hda/hdac_if.m index 4fb77bd..f87e2ed 100644 --- a/sys/dev/sound/pci/hda/hdac_if.m +++ b/sys/dev/sound/pci/hda/hdac_if.m @@ -44,6 +44,7 @@ METHOD int stream_alloc { device_t child; int dir; int format; + int stripe; uint32_t **dmapos; }; diff --git a/sys/dev/sound/pci/hda/hdac_private.h b/sys/dev/sound/pci/hda/hdac_private.h index 8532f83..b8798d7 100644 --- a/sys/dev/sound/pci/hda/hdac_private.h +++ b/sys/dev/sound/pci/hda/hdac_private.h @@ -155,6 +155,8 @@ struct hdac_stream { int stream; int blksz; int running; + int bw; + int stripe; uint16_t format; }; @@ -206,6 +208,8 @@ struct hdac_softc { int unsolq_st; uint32_t unsolq[HDAC_UNSOLQ_MAX]; + int sdo_bw_used; + struct hdac_stream *streams; struct { @@ -216,6 +220,7 @@ struct hdac_softc { uint8_t stepping_id; int pending; uint32_t response; + int sdi_bw_used; } codecs[HDAC_CODEC_MAX]; }; diff --git a/sys/dev/sound/pci/hda/hdacc.c b/sys/dev/sound/pci/hda/hdacc.c index 47feb1b..3336d4d 100644 --- a/sys/dev/sound/pci/hda/hdacc.c +++ b/sys/dev/sound/pci/hda/hdacc.c @@ -503,13 +503,13 @@ hdacc_codec_command(device_t dev, device_t child, uint32_t verb) static int hdacc_stream_alloc(device_t dev, device_t child, int dir, int format, - uint32_t **dmapos) + int stripe, uint32_t **dmapos) { struct hdacc_softc *codec = device_get_softc(dev); int stream; stream = HDAC_STREAM_ALLOC(device_get_parent(dev), dev, - dir, format, dmapos); + dir, format, stripe, dmapos); if (stream > 0) codec->streams[dir][stream] = child; return (stream); -- cgit v1.1