/*- * Copyright (c) 2015 Taylor R. Campbell * 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. */ int hdaudioioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l) { struct hdaudio_softc *sc; struct plistref *pref = addr; int err; sc = device_lookup_private(&hdaudio_cd, HDAUDIOUNIT(dev)); if (sc == NULL) return ENXIO; /* * XXX This should be auto-generated, perhaps from a protobuf * service description. */ switch (cmd) { case HDAUDIO_FGRP_INFO: { struct hdaudio_fgrp_info_request request; struct hdaudio_fgrp_info_request response; err = pb_copyin_init(pref, hdaudio_fgrp_info_request(&request)); if (err) break; pb_init(hdaudio_fgrp_info_response(&response)); err = hdaudioioctl_fgrp_info(sc, &request, &response); pb_destroy(hdaudio_fgrp_info_request(&request)); if (err) { pb_destroy(hdaudio_fgrp_info_response(&response)); break; } err = pb_copyout_destroy(pref, hdaudio_fgrp_info_response(&response)); if (err) break; break; } case HDAUDIO_FGRP_GETCONFIG: { struct hdaudio_fgrp_getconfig_request request; struct hdaudio_fgrp_getconfig_request response; err = pb_copyin_init(pref, hdaudio_fgrp_getconfig_request(&request)); if (err) break; pb_init(hdaudio_fgrp_getconfig_response(&response)); err = hdaudioioctl_fgrp_getconfig(sc, &request, &response); pb_destroy(hdaudio_fgrp_getconfig_request(&request)); if (err) { pb_destroy(hdaudio_fgrp_getconfig_response(&response)); break; } err = pb_copyout_destroy(pref, hdaudio_fgrp_getconfig_response(&response)); if (err) break; break; } ... default: err = EINVAL; break; } return err; } static int hdaudioioctl_fgrp_info(struct hdaudio_softc *sc, const struct hdaudio_fgrp_info_request *request __unused, struct hdaudio_fgrp_info_response *response) { struct hdaudio_codec *co; struct hdaudio_function_group *fg; struct hdaudio_fgrp_info *info = &response->fgrp_info; int codecid, fgid; int nfgrps = 0; int error; for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++) { co = &sc->sc_codec[codecid]; if (!co->co_valid) continue; /* XXX Integer overflow? */ nfgrps += co->co_nfg; } error = pb_repeated_alloc(&info->fgrps.repeated, nfgrps); if (error) return error; for (codecid = 0; codecid < HDAUDIO_MAX_CODECS; codecid++) { co = &sc->sc_codec[codecid]; if (!co->co_valid) continue; for (fgid = 0; fgid < co->co_nfg; fgid++) { fg = &co->co_fg[fgid]; info->fgrps.item[fgid].nid = fg->fg_nid; info->fgrps.item[fgid].codecid = codecid; info->fgrps.item[fgid].vendor_id = fg->fg_vendor; info->fgrps.item[fgid].product_id = fg->fg_product; info->fgrps.item[fgid].subsystem_id = fg->fg_subsystem; if (fg->fg_device != NULL) { struct pb_string *device = &info->fgrps.item[fgid].device.value; error = pb_string_set_copy_asciz(device, device_xname(fg->fg_device)); if (error) return error; info->fgrps.item[fgid].device.present = true; } } } return 0; } static int hdaudioioctl_fgrp_getconfig(struct hdaudio_softc *sc, const struct hdaudio_fgrp_getconfig_request *request, struct hdaudio_fgrp_getconfig_response *response) { struct hdaudio_function_group *fg; uint32_t nodecnt, wcap, config; int16_t i, j; unsigned int startnode, endnode; int error; fg = hdaudioctl_fgrp_lookup(sc, request->codecid, request->nid); if (fg == NULL) return ENODEV; nodecnt = hdaudio_command(fg->fg_codec fg->fg_nid, CORB_GET_PARAMETER, COP_SUBORDINATE_NODE_COUNT); startnode = COP_NODECNT_STARTNODE(nodecnt); endnode = startnode + COP_NODECNT_NUMNODES(nodecnt); /* Example of optional fields: start, end. */ if (request->end.present) { if ((endnode - startnode) < request->end.value) return EINVAL; endnode = (startnode + request->end.value); } if (request->start.present) { if ((endnode - startnode) < request->start.value) return EINVAL; startnode = (startnode + request->start.value); } error = pb_repeated_alloc(&response->pin_config.pins.repeated, (endnode - startnode)); if (error) return error; for (i = startnode; i < endnode; i++) { wcap = hdaudio_command(fg->fg_codec, i, CORB_GET_PARAMETER, COP_AUDIO_WIDGET_CAPABILITIES); if (COP_AWCAP_TYPE(wcap) != COP_AWCAP_TYPE_PIN_COMPLEX) continue; config = hdaudio_command(fg->fg_codec, i, CORB_GET_CONFIGURATION_DEFAULT, 0); j = pb_repeated_next(&response->pin_config.pins.repeated); response.pin_config.pins.item[j].nid = i; response.pin_config.pins.item[j].config = config; } return 0; } static int hdaudioioctl_fgrp_setconfig(struct hdaudio_softc *sc, const struct hdaudio_fgrp_setconfig_request *request, struct hdaudio_fgrp_setconfig_response *response) { struct hdaudio_function_group *fg; int error; fg = hdaudioioctl_fgrp_lookup(sc, request->codecid, request->nid); if (fg == NULL) return ENODEV; if (fg->fg_device) { error = config_detach(fg->fg_device, 0); if (error) return error; fg->fg_device = NULL; } hdaudio_attach_fg(fg, (request->pin_config.present? request->pin_config.value : NULL)); } static int hdafg_widget_info(void *opaque, const struct hdaudio_fgrp_widget_info_request *request, struct hdaudio_fgrp_widget_info_response *response) { struct hdafg_softc *sc = opaque; struct hdaudio_widget *w; uint32_t config, wcap; int nid; int i, ci, n = 0; int error; #if 0 KASSERT(sc->sc_startnode <= sc->sc_endnode); if (request->index > (sc->sc_endnode - sc->sc_startnode)) return EINVAL; #endif nid = sc->sc_startnode + request->index; if (nid >= sc->sc_endnode) return EINVAL; w = hdafg_widget_lookup(sc, nid); if (w == NULL) return ENXIO; wcap = hda_get_wparam(w, PIN_CAPABILITIES); config = hdaudio_command(sc->sc_codec, w->w_nid, CORB_GET_CONFIGURATION_DEFAULT, 0); pb_string_set_ptr_asciz(&response->name, w->w_name); response->enable = w->w_enable; response->nid = w->w_nid; response->type = w->w_type; response->config = config; response->cap = wcap; if (w->w_nconns == 0) return 0; for (i = 0; i < w->w_conns; i++) { if (w->w_conns[i] == 0) continue; n++; } error = pb_repeated_alloc(&response->conns.repeated, n); if (error) return error; ci = 0; for (i = 0; i < w->w_conns; i++) { if (w->w_conns[i] == 0) continue; repeated->conns.item[ci++] = w->w_conns[i]; } return 0; }