From: Chris Wulff <crwulff@xxxxxxxxx> Add the ability to create c_alt.x and p_alt.x directories to have different configurations for channels/sample size/sync mode. This patch only implements the user interface and does not yet alter the behavior of the function. Initial values for the alt mode settings are copied from the main settings at the time the alt mode directory is created. Signed-off-by: Chris Wulff <crwulff@xxxxxxxxx> --- Documentation/usb/gadget-testing.rst | 21 +- drivers/usb/gadget/function/f_uac2.c | 313 ++++++++++++++++++++++++--- drivers/usb/gadget/function/u_uac2.h | 91 ++++++-- 3 files changed, 379 insertions(+), 46 deletions(-) diff --git a/Documentation/usb/gadget-testing.rst b/Documentation/usb/gadget-testing.rst index 5aaf03cf8ebf..67cae833f246 100644 --- a/Documentation/usb/gadget-testing.rst +++ b/Documentation/usb/gadget-testing.rst @@ -762,7 +762,7 @@ The uac2 function provides these attributes in its function directory: p_volume_max playback volume control max value (in 1/256 dB) p_volume_res playback volume control resolution (in 1/256 dB) p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto) - req_number the number of pre-allocated request for both capture + req_number the number of pre-allocated requests for both capture and playback function_name name of the interface if_ctrl_name topology control name @@ -778,10 +778,29 @@ The uac2 function provides these attributes in its function directory: c_fu_vol_name capture functional unit name c_terminal_type code of the capture terminal type p_terminal_type code of the playback terminal type + c_alt.x/ alternate capture setting x (0-255) + p_alt.x/ alternate playback setting x (0-255) ================ ==================================================== The attributes have sane default values. +Alternate settings have these attributes settable. Defaults are copied +from the associated function-wide settings. Alternate setting 0 only +has a name and no other settings. If p/c_alt.1 doesn't exist +function-wide settings will be used for alternate setting 1. + + ================ ==================================================== + name alternate setting name + chmask channel mask + ssize sample size (bytes) + sync synchronization type (async/adaptive) *capture only* + hs_bint bInterval for HS/SS (1-4: fixed, 0: auto) + it_name input terminal name + it_ch_name first input channel name + ot_name output terminal name + fu_vol_name mute/volume functional unit name + ================ ==================================================== + Testing the UAC2 function ------------------------- diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 050789d2d3c9..e9f951215c26 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -19,6 +19,9 @@ #include "u_uac2.h" #include "u_uac_utils.h" +#define HOST_TO_DEVICE 0 +#define DEVICE_TO_HOST 1 + /* UAC2 spec: 4.1 Audio Channel Cluster Descriptor */ #define UAC2_CHANNEL_MASK 0x07FFFFFF @@ -646,29 +649,15 @@ struct cntrl_subrange_lay3 { DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES); -static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts, +static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_alt_opts *alt_opts, struct usb_endpoint_descriptor *ep_desc, enum usb_device_speed speed, bool is_playback) { - int chmask, srate, ssize, hs_bint, sync; - - if (is_playback) { - chmask = uac2_opts->p_chmask; - srate = get_max_srate(uac2_opts->p_srates); - ssize = uac2_opts->p_ssize; - hs_bint = uac2_opts->p_hs_bint; - sync = USB_ENDPOINT_SYNC_ASYNC; - } else { - chmask = uac2_opts->c_chmask; - srate = get_max_srate(uac2_opts->c_srates); - ssize = uac2_opts->c_ssize; - hs_bint = uac2_opts->c_hs_bint; - sync = uac2_opts->c_sync; - } - return uac_set_ep_max_packet_size_bint( - dev, ep_desc, speed, is_playback, hs_bint, chmask, - srate, ssize, sync, uac2_opts->fb_max); + dev, ep_desc, speed, is_playback, alt_opts->hs_bint, alt_opts->chmask, + get_max_srate(is_playback ? alt_opts->c.opts->p_srates + : alt_opts->c.opts->c_srates), + alt_opts->ssize, alt_opts->sync, alt_opts->c.opts->fb_max); } static struct uac2_feature_unit_descriptor *build_fu_desc(int chmask) @@ -926,6 +915,57 @@ static int afunc_validate_opts(struct g_audio *agdev, struct device *dev) return 0; } +/*-------------------------------------------------------------------------*/ + +/* + * Configfs alt mode handling + */ + +static void init_alt_0_opts(struct f_uac2_alt_0_opts *alt_0_opts, + struct f_uac2_opts *opts, int playback) +{ + alt_0_opts->c.opts = opts; + alt_0_opts->c.alt_num = 0; + + // Note: Strings are from the host perspective, opt prefixes are from the device perspective + scnprintf(alt_0_opts->name, sizeof(alt_0_opts->name), + (!playback) ? "Playback Inactive" : "Capture Inactive"); +} + +static void init_alt_opts(struct f_uac2_alt_opts *alt_opts, struct f_uac2_opts *opts, + int alt_num, int playback) +{ + alt_opts->c.opts = opts; + alt_opts->c.alt_num = alt_num; + + INIT_LIST_HEAD(&alt_opts->list); + + // Note: Strings are from the host perspective, opt prefixes are from the device perspective + scnprintf(alt_opts->name, sizeof(alt_opts->name), + (!playback) ? "Playback Active" : "Capture Active"); + strscpy(alt_opts->it_name, (playback) ? opts->p_it_name : opts->c_it_name, + sizeof(alt_opts->it_name)); + strscpy(alt_opts->it_ch_name, (playback) ? opts->p_it_ch_name : opts->c_it_ch_name, + sizeof(alt_opts->it_ch_name)); + strscpy(alt_opts->ot_name, (playback) ? opts->p_ot_name : opts->c_ot_name, + sizeof(alt_opts->ot_name)); + strscpy(alt_opts->fu_vol_name, (playback) ? opts->p_fu_vol_name : opts->c_fu_vol_name, + sizeof(alt_opts->fu_vol_name)); + + /* Copy default options from the main opts */ + alt_opts->chmask = (playback) ? opts->p_chmask : opts->c_chmask; + alt_opts->ssize = (playback) ? opts->p_ssize : opts->c_ssize; + alt_opts->sync = (playback) ? USB_ENDPOINT_SYNC_ASYNC : opts->c_sync; /* only for capture */ + alt_opts->hs_bint = (playback) ? opts->p_hs_bint : opts->c_hs_bint; + + /* NOTE: These are backwards with relation to other c_/p_ settings in the existing + * userspace API. Correct terminal type is copied into c/p_alt.x + * (eg p_termial_type == c_alt.x/terminal_type) + */ + alt_opts->terminal_type = (!playback) ? opts->p_terminal_type : opts->c_terminal_type; +} + + static int afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) { @@ -938,6 +978,18 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) struct usb_string *us; int ret; + /* Copy main options to alt modes 0/1 if the groups don't exist + * before validation since they will be checked. + */ + if (!uac2_opts->c_alt_0_opts.c.group.cg_item.ci_name) + init_alt_0_opts(&uac2_opts->c_alt_0_opts, uac2_opts, HOST_TO_DEVICE); + if (!uac2_opts->p_alt_0_opts.c.group.cg_item.ci_name) + init_alt_0_opts(&uac2_opts->p_alt_0_opts, uac2_opts, DEVICE_TO_HOST); + if (!uac2_opts->c_alt_1_opts.c.group.cg_item.ci_name) + init_alt_opts(&uac2_opts->c_alt_1_opts, uac2_opts, 1, HOST_TO_DEVICE); + if (!uac2_opts->p_alt_1_opts.c.group.cg_item.ci_name) + init_alt_opts(&uac2_opts->p_alt_1_opts, uac2_opts, 1, DEVICE_TO_HOST); + ret = afunc_validate_opts(agdev, dev); if (ret) return ret; @@ -1109,42 +1161,42 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) ss_epout_desc.bInterval = uac2_opts->c_hs_bint; /* Calculate wMaxPacketSize according to audio bandwidth */ - ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epin_desc, + ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &fs_epin_desc, USB_SPEED_FULL, true); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epout_desc, + ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &fs_epout_desc, USB_SPEED_FULL, false); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epin_desc, + ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &hs_epin_desc, USB_SPEED_HIGH, true); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epout_desc, + ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &hs_epout_desc, USB_SPEED_HIGH, false); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epin_desc, + ret = set_ep_max_packet_size_bint(dev, &uac2_opts->p_alt_1_opts, &ss_epin_desc, USB_SPEED_SUPER, true); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return ret; } - ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epout_desc, + ret = set_ep_max_packet_size_bint(dev, &uac2_opts->c_alt_1_opts, &ss_epout_desc, USB_SPEED_SUPER, false); if (ret < 0) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); @@ -1773,6 +1825,110 @@ static inline struct f_uac2_opts *to_f_uac2_opts(struct config_item *item) func_inst.group); } +static inline struct f_uac2_alt_opts *to_f_uac2_alt_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uac2_alt_opts, + c.group); +} + +#define UAC2_ALT_ATTR_TO_OPTS struct f_uac2_alt_opts *alt_opts = to_f_uac2_alt_opts(item) +#define UAC2_ALT_ATTRIBUTE(type, name) \ + UAC_ATTRIBUTE(f_uac2_alt_opts, UAC2_ALT_ATTR_TO_OPTS, alt_opts, \ + alt_opts->c.opts->lock, alt_opts->c.opts->refcnt, \ + type, name) + +#define UAC2_ALT_ATTRIBUTE_SYNC(name) \ + UAC_ATTRIBUTE_SYNC(f_uac2_alt_opts, UAC2_ALT_ATTR_TO_OPTS, \ + alt_opts, alt_opts->c.opts->lock, \ + alt_opts->c.opts->refcnt, name) + +#define UAC2_ALT_ATTRIBUTE_STRING(name) \ + UAC_ATTRIBUTE_STRING(f_uac2_alt_opts, UAC2_ALT_ATTR_TO_OPTS, \ + alt_opts, alt_opts->c.opts->lock, \ + alt_opts->c.opts->refcnt, name) + + +UAC2_ALT_ATTRIBUTE_STRING(name); +UAC2_ALT_ATTRIBUTE_STRING(it_name); +UAC2_ALT_ATTRIBUTE_STRING(it_ch_name); +UAC2_ALT_ATTRIBUTE_STRING(ot_name); +UAC2_ALT_ATTRIBUTE_STRING(fu_vol_name); + +UAC2_ALT_ATTRIBUTE(u32, ssize); +UAC2_ALT_ATTRIBUTE(u32, chmask); +UAC2_ALT_ATTRIBUTE_SYNC(sync); +UAC2_ALT_ATTRIBUTE(u8, hs_bint); +UAC2_ALT_ATTRIBUTE(s16, terminal_type); + +static struct configfs_attribute *f_uac2_alt_0_attrs[] = { + &f_uac2_alt_opts_attr_name, + + NULL, +}; + +static const struct config_item_type alt_mode_0_type = { + .ct_attrs = f_uac2_alt_0_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_attribute *f_uac2_alt_attrs_c[] = { + &f_uac2_alt_opts_attr_name, + &f_uac2_alt_opts_attr_it_name, + &f_uac2_alt_opts_attr_it_ch_name, + &f_uac2_alt_opts_attr_ot_name, + &f_uac2_alt_opts_attr_fu_vol_name, + &f_uac2_alt_opts_attr_ssize, + &f_uac2_alt_opts_attr_chmask, + &f_uac2_alt_opts_attr_sync, + &f_uac2_alt_opts_attr_hs_bint, + &f_uac2_alt_opts_attr_terminal_type, + + NULL, +}; + +static struct configfs_attribute *f_uac2_alt_attrs_p[] = { + &f_uac2_alt_opts_attr_name, + &f_uac2_alt_opts_attr_it_name, + &f_uac2_alt_opts_attr_it_ch_name, + &f_uac2_alt_opts_attr_ot_name, + &f_uac2_alt_opts_attr_fu_vol_name, + &f_uac2_alt_opts_attr_ssize, + &f_uac2_alt_opts_attr_chmask, + /* Playback does not have sync */ + &f_uac2_alt_opts_attr_hs_bint, + &f_uac2_alt_opts_attr_terminal_type, + + NULL, +}; + + +static void f_uac2_alt_attr_release(struct config_item *item) +{ + struct f_uac2_alt_opts *alt_opts = to_f_uac2_alt_opts(item); + + /* Opts 0 and 1 are fixed structures, 2+ are kzalloc'd */ + if (alt_opts->c.alt_num > 1) + kfree(alt_opts); +} + +static struct configfs_item_operations f_uac2_alt_item_ops = { + .release = f_uac2_alt_attr_release, +}; + +static const struct config_item_type alt_mode_c_type = { + .ct_item_ops = &f_uac2_alt_item_ops, + .ct_attrs = f_uac2_alt_attrs_c, + .ct_owner = THIS_MODULE, +}; + +static const struct config_item_type alt_mode_p_type = { + .ct_item_ops = &f_uac2_alt_item_ops, + .ct_attrs = f_uac2_alt_attrs_p, + .ct_owner = THIS_MODULE, +}; + +/*-------------------------------------------------------------------------*/ + static void f_uac2_attr_release(struct config_item *item) { struct f_uac2_opts *opts = to_f_uac2_opts(item); @@ -1889,8 +2045,111 @@ static struct configfs_attribute *f_uac2_attrs[] = { NULL, }; +static struct config_group *f_uac2_group_make( + struct config_group *group, + const char *name) +{ + struct f_uac2_opts *opts = to_f_uac2_opts(&group->cg_item); + struct f_uac2_alt_opts *alt_opts; + struct f_uac2_alt_opts *pos; + struct config_group *ret; + unsigned int alt_num; + int playback = 0; + + mutex_lock(&opts->lock); + if (opts->refcnt) { + ret = ERR_PTR(-EBUSY); + goto end; + } + + if (sscanf(name, "c_alt.%u", &alt_num) != 1) { + playback = 1; + if (sscanf(name, "p_alt.%u", &alt_num) != 1) { + ret = ERR_PTR(-EINVAL); + goto end; + } + } + + if (alt_num > 255) { + ret = ERR_PTR(-EINVAL); + goto end; + } + + /* Alt mode 0 has less properties */ + if (alt_num == 0) { + struct f_uac2_alt_0_opts *alt_0_opts = (playback) ? &opts->p_alt_0_opts + : &opts->c_alt_0_opts; + + init_alt_0_opts(alt_0_opts, opts, playback); + config_group_init_type_name(&alt_0_opts->c.group, name, &alt_mode_0_type); + ret = &alt_0_opts->c.group; + goto end; + } + + if (alt_num == 1) { + /* Alt mode 1 always exists */ + alt_opts = (playback) ? &opts->p_alt_1_opts : &opts->c_alt_1_opts; + } else { + /* Allocate a structure for alt mode 2+ */ + alt_opts = kzalloc(sizeof(*alt_opts), GFP_KERNEL); + if (!alt_opts) { + ret = ERR_PTR(-ENOMEM); + goto end; + } + } + + ret = &alt_opts->c.group; + + config_group_init_type_name(&alt_opts->c.group, name, (playback) ? &alt_mode_p_type + : &alt_mode_c_type); + + init_alt_opts(alt_opts, opts, alt_num, playback); + + /* Alt mode 1 doesn't go in the list. It is handled separately to + * also handle the case where the alt.1 group is not created. + */ + if (alt_num == 1) + goto end; + + /* Insert the new alt mode in the list, sorted by alt_num */ + list_for_each_entry(pos, (playback) ? &opts->p_alt_opts : &opts->c_alt_opts, list) { + if (alt_opts->c.alt_num < pos->c.alt_num) { + list_add_tail(&alt_opts->list, &pos->list); + mutex_unlock(&opts->lock); + goto end; + } + } + + list_add_tail(&alt_opts->list, (playback) ? &opts->p_alt_opts : &opts->c_alt_opts); + +end: + mutex_unlock(&opts->lock); + + return ret; +} + +static void f_uac2_group_drop(struct config_group *group, struct config_item *item) +{ + struct f_uac2_alt_opts *alt_opts = to_f_uac2_alt_opts(item); + + /* Alt modes 0 and 1 are preallocated and not included in the list */ + if (alt_opts->c.alt_num > 1) { + mutex_lock(&alt_opts->c.opts->lock); + list_del(&alt_opts->list); + mutex_unlock(&alt_opts->c.opts->lock); + } + + config_item_put(item); +} + +static struct configfs_group_operations f_uac2_group_ops = { + .make_group = &f_uac2_group_make, + .drop_item = &f_uac2_group_drop, +}; + static const struct config_item_type f_uac2_func_type = { .ct_item_ops = &f_uac2_item_ops, + .ct_group_ops = &f_uac2_group_ops, .ct_attrs = f_uac2_attrs, .ct_owner = THIS_MODULE, }; @@ -1914,6 +2173,9 @@ static struct usb_function_instance *afunc_alloc_inst(void) mutex_init(&opts->lock); opts->func_inst.free_func_inst = afunc_free_inst; + INIT_LIST_HEAD(&opts->c_alt_opts); + INIT_LIST_HEAD(&opts->p_alt_opts); + config_group_init_type_name(&opts->func_inst.group, "", &f_uac2_func_type); @@ -1947,6 +2209,7 @@ static struct usb_function_instance *afunc_alloc_inst(void) scnprintf(opts->clksrc_in_name, sizeof(opts->clksrc_in_name), "Input Clock"); scnprintf(opts->clksrc_out_name, sizeof(opts->clksrc_out_name), "Output Clock"); + // Note: Strings are from the host perspective, opt prefixes are from the device perspective scnprintf(opts->p_it_name, sizeof(opts->p_it_name), "USBD Out"); scnprintf(opts->p_it_ch_name, sizeof(opts->p_it_ch_name), "Capture Channels"); scnprintf(opts->p_ot_name, sizeof(opts->p_ot_name), "USBH In"); diff --git a/drivers/usb/gadget/function/u_uac2.h b/drivers/usb/gadget/function/u_uac2.h index 0df808289ded..8c061e588324 100644 --- a/drivers/usb/gadget/function/u_uac2.h +++ b/drivers/usb/gadget/function/u_uac2.h @@ -40,8 +40,59 @@ #define UAC2_DEF_C_TERM_TYPE 0x201 /* UAC_INPUT_TERMINAL_MICROPHONE*/ +struct f_uac2_opts; + +struct f_uac2_alt_opts_common { + struct config_group group; + struct f_uac2_opts *opts; + u8 alt_num; +}; + +/* Alt mode 0 only has a name */ +struct f_uac2_alt_0_opts { + struct f_uac2_alt_opts_common c; + + char name[USB_MAX_STRING_LEN]; +}; + +/* Alt modes 1+ */ +struct f_uac2_alt_opts { + struct f_uac2_alt_opts_common c; + + struct list_head list; + + /* Strings */ + char name[USB_MAX_STRING_LEN]; + char it_name[USB_MAX_STRING_LEN]; + char it_ch_name[USB_MAX_STRING_LEN]; + char ot_name[USB_MAX_STRING_LEN]; + char fu_vol_name[USB_MAX_STRING_LEN]; + + /* Audio options */ + int chmask; + int ssize; + int sync; + u8 hs_bint; + s16 terminal_type; + +}; + struct f_uac2_opts { struct usb_function_instance func_inst; + + /* Alt mode 0 options */ + struct f_uac2_alt_0_opts c_alt_0_opts; + struct f_uac2_alt_0_opts p_alt_0_opts; + + /* Alt mode 1 options */ + struct f_uac2_alt_opts c_alt_1_opts; + struct f_uac2_alt_opts p_alt_1_opts; + + /* Alt mode 2+ options */ + struct list_head c_alt_opts; + struct list_head p_alt_opts; + + /* Default options and Alt mode 1 if no c/p_alt.1 created */ int p_chmask; int p_srates[UAC_MAX_RATES]; int p_ssize; @@ -52,36 +103,36 @@ struct f_uac2_opts { int c_sync; u8 c_hs_bint; - bool p_mute_present; - bool p_volume_present; + bool p_mute_present; + bool p_volume_present; s16 p_volume_min; s16 p_volume_max; s16 p_volume_res; - bool c_mute_present; - bool c_volume_present; + bool c_mute_present; + bool c_volume_present; s16 c_volume_min; s16 c_volume_max; s16 c_volume_res; int req_number; int fb_max; - bool bound; - - char function_name[USB_MAX_STRING_LEN]; - char if_ctrl_name[USB_MAX_STRING_LEN]; - char clksrc_in_name[USB_MAX_STRING_LEN]; - char clksrc_out_name[USB_MAX_STRING_LEN]; - - char p_it_name[USB_MAX_STRING_LEN]; - char p_it_ch_name[USB_MAX_STRING_LEN]; - char p_ot_name[USB_MAX_STRING_LEN]; - char p_fu_vol_name[USB_MAX_STRING_LEN]; - - char c_it_name[USB_MAX_STRING_LEN]; - char c_it_ch_name[USB_MAX_STRING_LEN]; - char c_ot_name[USB_MAX_STRING_LEN]; - char c_fu_vol_name[USB_MAX_STRING_LEN]; + bool bound; + + char function_name[USB_MAX_STRING_LEN]; + char if_ctrl_name[USB_MAX_STRING_LEN]; + char clksrc_in_name[USB_MAX_STRING_LEN]; + char clksrc_out_name[USB_MAX_STRING_LEN]; + + char p_it_name[USB_MAX_STRING_LEN]; + char p_it_ch_name[USB_MAX_STRING_LEN]; + char p_ot_name[USB_MAX_STRING_LEN]; + char p_fu_vol_name[USB_MAX_STRING_LEN]; + + char c_it_name[USB_MAX_STRING_LEN]; + char c_it_ch_name[USB_MAX_STRING_LEN]; + char c_ot_name[USB_MAX_STRING_LEN]; + char c_fu_vol_name[USB_MAX_STRING_LEN]; s16 p_terminal_type; s16 c_terminal_type; -- 2.43.0