widget connection lists are described as a series of nids.  the widget
gives the number of connection list entries.  if the most significant
bit of a connection list entry is set, all nids between the last nid
in the connection list and the nid in the entry with the most significant
bit set are connected.

this means the number of connected nids isn't necessarily the same as
the number of connection list entries.  azalia currently assumes they
are the same, and ends up ignoring some of the connections, which leads
to widgets being considered unusable.

the following runs through the connection list entries twice.  the
first is to get the number of connections.  then once the number of
connections is known, an array to hold the nids of the connected
widgets is allocated.  then loop over the connection list entries
again and insert the nids into the array.

this affects almost all Analog Devices (AD198x) and Sigmatel/IDT
(STAC*/92HD*) codecs.

please test and report to me.  please include a dmesg and a diff of
'mixerctl -v' output from before and after applying the diff.  thanks.

-- 
jake...@sdf.lonestar.org
SDF Public Access UNIX System - http://sdf.lonestar.org

Index: azalia.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/azalia.c,v
retrieving revision 1.188
diff -u -p azalia.c
--- azalia.c    12 Sep 2010 03:17:34 -0000      1.188
+++ azalia.c    8 Feb 2011 03:52:17 -0000
@@ -3330,7 +3330,7 @@ azalia_widget_init_connection(widget_t *this, const co
        uint32_t result;
        int err;
        int i, j, k;
-       int length, bits, conn, last;
+       int length, nconn, bits, conn, last;
 
        this->selected = -1;
        if ((this->widgetcap & COP_AWCAP_CONNLIST) == 0)
@@ -3349,32 +3349,59 @@ azalia_widget_init_connection(widget_t *this, const co
        if (length == 0)
                return 0;
 
-       this->nconnections = length;
-       this->connections = malloc(sizeof(nid_t) * length, M_DEVBUF, M_NOWAIT);
+       /*
+        * 'length' is the number of entries, not the number of
+        * connections.  Find the number of connections, 'nconn', so
+        * enough space can be allocated for the list of connected
+        * nids.
+        */
+       nconn = last = 0;
+       for (i = 0; i < length;) {
+               err = azalia_comresp(codec, this->nid,
+                   CORB_GET_CONNECTION_LIST_ENTRY, i, &result);
+               if (err)
+                       return err;
+               for (k = 0; i < length && (k < 32 / bits); k++) {
+                       conn = (result >> (k * bits)) & ((1 << bits) - 1);
+                       /* If high bit is set, this is the end of a continuous
+                        * list that started with the last connection.
+                        */
+                       if ((nconn > 0) && (conn & (1 << (bits - 1))))
+                               nconn += (conn & ~(1 << (bits - 1))) - last;
+                       else
+                               nconn++;
+                       last = conn;
+                       i++;
+               }
+       }
+
+       this->connections = malloc(sizeof(nid_t) * nconn, M_DEVBUF, M_NOWAIT);
        if (this->connections == NULL) {
                printf("%s: out of memory\n", XNAME(codec->az));
                return ENOMEM;
        }
-       for (i = 0; i < length;) {
+       for (i = 0; i < nconn;) {
                err = azalia_comresp(codec, this->nid,
                    CORB_GET_CONNECTION_LIST_ENTRY, i, &result);
                if (err)
                        return err;
-               for (k = 0; i < length && (k < 32 / bits); k++) {
+               for (k = 0; i < nconn && (k < 32 / bits); k++) {
                        conn = (result >> (k * bits)) & ((1 << bits) - 1);
                        /* If high bit is set, this is the end of a continuous
                         * list that started with the last connection.
                         */
                        if ((i > 0) && (conn & (1 << (bits - 1)))) {
-                               last = this->connections[i - 1];
-                               for (j = 1; i < length && j <= conn - last; j++)
+                               for (j = 1; i < nconn && j <= conn - last; j++)
                                        this->connections[i++] = last + j;
                        } else {
                                this->connections[i++] = conn;
                        }
+                       last = conn;
                }
        }
-       if (length > 0) {
+       this->nconnections = nconn;
+
+       if (nconn > 0) {
                err = azalia_comresp(codec, this->nid,
                    CORB_GET_CONNECTION_SELECT_CONTROL, 0, &result);
                if (err)

Reply via email to