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, i;
	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];
			i = pb_repeated_next(&info->fgrps.repeated);
			info->fgrps.item[i].nid = fg->fg_nid;
			info->fgrps.item[i].codecid = codecid;
			info->fgrps.item[i].vendor_id = fg->fg_vendor;
			info->fgrps.item[i].product_id = fg->fg_product;
			info->fgrps.item[i].subsystem_id = fg->fg_subsystem;
			if (fg->fg_device != NULL) {
				struct pb_string *device =
				    &info->fgrps.item[i].device;
				error = pb_string_set_copy_asciz(device,
				    device_xname(fg->fg_device));
				if (error)
					return error;
			}
		}
	}

	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, j, 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;
	for (i = 0; i < w->w_conns; i++) {
		if (w->w_conns[i] == 0)
			continue;
		j = pb_repeated_next(&response->conns.repeated);
		response->conns.item[j] = w->w_conns[i];
	}

	return 0;
}