diff --git a/Documentation/driver-api/auxiliary_bus.rst b/Documentation/driver-api/auxiliary_bus.rst new file mode 100644 index 00000000000000..6b4ee2a0c8d91d --- /dev/null +++ b/Documentation/driver-api/auxiliary_bus.rst @@ -0,0 +1,230 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +============= +Auxiliary Bus +============= + +In some subsystems, the functionality of the core device (PCI/ACPI/other) is +too complex for a single device to be managed as a monolithic block or a part of +the functionality needs to be exposed to a different subsystem. Splitting the +functionality into smaller orthogonal devices would make it easier to manage +data, power management and domain-specific interaction with the hardware. A key +requirement for such a split is that there is no dependency on a physical bus, +device, register accesses or regmap support. These individual devices split from +the core cannot live on the platform bus as they are not physical devices that +are controlled by DT/ACPI. The same argument applies for not using MFD in this +scenario as MFD relies on individual function devices being physical devices. + +An example for this kind of requirement is the audio subsystem where a single +IP is handling multiple entities such as HDMI, Soundwire, local devices such as +mics/speakers etc. The split for the core's functionality can be arbitrary or +be defined by the DSP firmware topology and include hooks for test/debug. This +allows for the audio core device to be minimal and focused on hardware-specific +control and communication. + +The auxiliary bus is intended to be minimal, generic and avoid domain-specific +assumptions. Each auxiliary_device represents a part of its parent +functionality. The generic behavior can be extended and specialized as needed +by encapsulating an auxiliary_device within other domain-specific structures and +the use of .ops callbacks. Devices on the auxiliary bus do not share any +structures and the use of a communication channel with the parent is +domain-specific. + +When Should the Auxiliary Bus Be Used +===================================== + +The auxiliary bus is to be used when a driver and one or more kernel modules, +who share a common header file with the driver, need a mechanism to connect and +provide access to a shared object allocated by the auxiliary_device's +registering driver. The registering driver for the auxiliary_device(s) and the +kernel module(s) registering auxiliary_drivers can be from the same subsystem, +or from multiple subsystems. + +The emphasis here is on a common generic interface that keeps subsystem +customization out of the bus infrastructure. + +One example could be a multi-port PCI network device that is rdma-capable and +needs to export this functionality and attach to an rdma driver in another +subsystem. The PCI driver will allocate and register an auxiliary_device for +each physical function on the NIC. The rdma driver will register an +auxiliary_driver that will be matched with and probed for each of these +auxiliary_devices. This will give the rdma driver access to the shared data/ops +in the PCI drivers shared object to establish a connection with the PCI driver. + +Another use case is for the PCI device to be split out into multiple sub +functions. For each sub function an auxiliary_device will be created. A PCI +sub function driver will bind to such devices that will create its own one or +more class devices. A PCI sub function auxiliary device will likely be +contained in a struct with additional attributes such as user defined sub +function number and optional attributes such as resources and a link to the +parent device. These attributes could be used by systemd/udev; and hence should +be initialized before a driver binds to an auxiliary_device. + +Auxiliary Device +================ + +An auxiliary_device is created and registered to represent a part of its parent +device's functionality. It is given a name that, combined with the registering +drivers KBUILD_MODNAME, creates a match_name that is used for driver binding, +and an id that combined with the match_name provide a unique name to register +with the bus subsystem. + +Registering an auxiliary_device is a two-step process. First you must call +auxiliary_device_init(), which will check several aspects of the +auxiliary_device struct and perform a device_initialize(). After this step +completes, any error state must have a call to auxiliary_device_unin() in its +resolution path. The second step in registering an auxiliary_device is to +perform a call to auxiliary_device_add(), which will set the name of the device +and add the device to the bus. + +Unregistering an auxiliary_device is also a two-step process to mirror the +register process. First will be a call to auxiliary_device_delete(), then +followed by a call to auxiliary_device_unin(). + +.. code-block:: c + + struct auxiliary_device { + struct device dev; + const char *name; + u32 id; + }; + +If two auxiliary_devices both with a match_name "mod.foo" are registered onto +the bus, they must have unique id values (e.g. "x" and "y") so that the +registered devices names will be "mod.foo.x" and "mod.foo.y". If match_name + +id are not unique, then the device_add will fail and generate an error message. + +The auxiliary_device.dev.type.release or auxiliary_device.dev.release must be +populated with a non-NULL pointer to successfully register the auxiliary_device. + +The auxiliary_device.dev.parent must also be populated. + +Auxiliary Device Memory Model and Lifespan +------------------------------------------ + +When a kernel driver registers an auxiliary_device on the auxiliary bus, we will +use the nomenclature to refer to this kernel driver as a registering driver. It +is the entity that will allocate memory for the auxiliary_device and register it +on the auxiliary bus. It is important to note that, as opposed to the platform +bus, the registering driver is wholly responsible for the management for the +memory used for the driver object. + +A parent object, defined in the shared header file, will contain the +auxiliary_device. It will also contain a pointer to the shared object(s), which +will also be defined in the shared header. Both the parent object and the +shared object(s) will be allocated by the registering driver. This layout +allows the auxiliary_driver's registering module to perform a container_of() +call to go from the pointer to the auxiliary_device, that is passed during the +call to the auxiliary_driver's probe function, up to the parent object, and then +have access to the shared object(s). + +The memory for the auxiliary_device will be freed only in its release() +callback flow as defined by its registering driver. + +The memory for the shared object(s) must have a lifespan equal to, or greater +than, the lifespan of the memory for the auxiliary_device. The auxiliary_driver +should only consider that this shared object is valid as long as the +auxiliary_device is still registered on the auxiliary bus. It is up to the +registering driver to manage (e.g. free or keep available) the memory for the +shared object beyond the life of the auxiliary_device. + +Registering driver must unregister all auxiliary devices before its registering +parent device's remove() is completed. + +Auxiliary Drivers +================= + +Auxiliary drivers follow the standard driver model convention, where +discovery/enumeration is handled by the core, and drivers +provide probe() and remove() methods. They support power management +and shutdown notifications using the standard conventions. + +.. code-block:: c + + struct auxiliary_driver { + int (*probe)(struct auxiliary_device *, + const struct auxiliary_device_id *id); + int (*remove)(struct auxiliary_device *); + void (*shutdown)(struct auxiliary_device *); + int (*suspend)(struct auxiliary_device *, pm_message_t); + int (*resume)(struct auxiliary_device *); + struct device_driver driver; + const struct auxiliary_device_id *id_table; + }; + +Auxiliary drivers register themselves with the bus by calling +auxiliary_driver_register(). The id_table contains the match_names of auxiliary +devices that a driver can bind with. + +Example Usage +============= + +Auxiliary devices are created and registered by a subsystem-level core device +that needs to break up its functionality into smaller fragments. One way to +extend the scope of an auxiliary_device would be to encapsulate it within a +domain-specific structure defined by the parent device. This structure contains +the auxiliary_device and any associated shared data/callbacks needed to +establish the connection with the parent. + +An example would be: + +.. code-block:: c + + struct foo { + struct auxiliary_device auxdev; + void (*connect)(struct auxiliary_device *auxdev); + void (*disconnect)(struct auxiliary_device *auxdev); + void *data; + }; + +The parent device would then register the auxiliary_device by calling +auxiliary_device_init(), and then auxiliary_device_add(), with the pointer to +the auxdev member of the above structure. The parent would provide a name for +the auxiliary_device that, combined with the parent's KBUILD_MODNAME, will +create a match_name that will be used for matching and binding with a driver. + +Whenever an auxiliary_driver is registered, based on the match_name, the +auxiliary_driver's probe() is invoked for the matching devices. The +auxiliary_driver can also be encapsulated inside custom drivers that make the +core device's functionality extensible by adding additional domain-specific ops +as follows: + +.. code-block:: c + + struct my_ops { + void (*send)(struct auxiliary_device *auxdev); + void (*receive)(struct auxiliary_device *auxdev); + }; + + + struct my_driver { + struct auxiliary_driver auxiliary_drv; + const struct my_ops ops; + }; + +An example of this type of usage would be: + +.. code-block:: c + + const struct auxiliary_device_id my_auxiliary_id_table[] = { + { .name = "foo_mod.foo_dev" }, + { }, + }; + + const struct my_ops my_custom_ops = { + .send = my_tx, + .receive = my_rx, + }; + + const struct my_driver my_drv = { + .auxiliary_drv = { + .driver = { + .name = AUX_DRV_NAME("myauxiliarydrv"), + }, + .id_table = my_auxiliary_id_table, + .probe = my_probe, + .remove = my_remove, + .shutdown = my_shutdown, + }, + .ops = my_custom_ops, + }; diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index 5ef2cfe3a16b36..7d65bda6249028 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -74,6 +74,7 @@ available subsections can be seen below. thermal/index fpga/index acpi/index + auxiliary_bus backlight/lp855x-driver.rst connector console diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 8d7001712062eb..9d27fc0d3ee1e6 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -1,6 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 menu "Generic Driver Options" +config AUXILIARY_BUS + tristate "auxiliary bus" + config UEVENT_HELPER bool "Support for uevent helper" help diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 157452080f3d7f..b0d46a4a7f79de 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -7,6 +7,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ attribute_container.o transport_class.o \ topology.o container.o property.o cacheinfo.o \ devcon.o swnode.o +obj-$(CONFIG_AUXILIARY_BUS) += auxiliary.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-y += power/ obj-$(CONFIG_ISA_BUS_API) += isa.o diff --git a/drivers/base/auxiliary.c b/drivers/base/auxiliary.c new file mode 100644 index 00000000000000..c3aa25c0e0a15d --- /dev/null +++ b/drivers/base/auxiliary.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Software based bus for Auxiliary devices + * + * Copyright (c) 2019-2020 Intel Corporation + * + * Please see Documentation/driver-api/auxiliary_bus.rst for more information. + */ + +#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include +#include + +static const struct auxiliary_device_id *auxiliary_match_id(const struct auxiliary_device_id *id, + const struct auxiliary_device *auxdev) +{ + while (id->name[0]) { + const char *p = strrchr(dev_name(&auxdev->dev), '.'); + int match_size; + + if (!p) { + id++; + continue; + } + match_size = p - dev_name(&auxdev->dev); + + /* use dev_name(&auxdev->dev) prefix before last '.' char to match to */ + if (strlen(id->name) == match_size && + !strncmp(dev_name(&auxdev->dev), id->name, match_size)) + return id; + id++; + } + return NULL; +} + +static int auxiliary_match(struct device *dev, struct device_driver *drv) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + struct auxiliary_driver *auxdrv = to_auxiliary_drv(drv); + + return !!auxiliary_match_id(auxdrv->id_table, auxdev); +} + +static int auxiliary_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + const char *name, *p; + + name = dev_name(dev); + p = strrchr(name, '.'); + + return add_uevent_var(env, "MODALIAS=%s%.*s", AUXILIARY_MODULE_PREFIX, (int)(p - name), + name); +} + +static const struct dev_pm_ops auxiliary_dev_pm_ops = { + SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, pm_generic_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_generic_suspend, pm_generic_resume) +}; + +static int auxiliary_bus_probe(struct device *dev) +{ + struct auxiliary_driver *auxdrv = to_auxiliary_drv(dev->driver); + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + int ret; + + ret = dev_pm_domain_attach(dev, true); + if (ret) { + dev_warn(dev, "Failed to attach to PM Domain : %d\n", ret); + return ret; + } + + ret = auxdrv->probe(auxdev, auxiliary_match_id(auxdrv->id_table, auxdev)); + if (ret) + dev_pm_domain_detach(dev, true); + + return ret; +} + +static int auxiliary_bus_remove(struct device *dev) +{ + struct auxiliary_driver *auxdrv = to_auxiliary_drv(dev->driver); + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + int ret = 0; + + if (auxdrv->remove) + ret = auxdrv->remove(auxdev); + dev_pm_domain_detach(dev, true); + + return ret; +} + +static void auxiliary_bus_shutdown(struct device *dev) +{ + struct auxiliary_driver *auxdrv = to_auxiliary_drv(dev->driver); + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + + if (auxdrv->shutdown) + auxdrv->shutdown(auxdev); +} + +struct bus_type auxiliary_bus_type = { + .name = "auxiliary", + .probe = auxiliary_bus_probe, + .remove = auxiliary_bus_remove, + .shutdown = auxiliary_bus_shutdown, + .match = auxiliary_match, + .uevent = auxiliary_uevent, + .pm = &auxiliary_dev_pm_ops, +}; + +/** + * auxiliary_device_init - check auxiliary_device and initialize + * @auxdev: auxiliary device struct + * + * This is the first step in the two-step process to register an auxiliary_device. + * + * When this function returns an error code, then the device_initialize will *not* have + * been performed, and the caller will be responsible to free any memory allocated for the + * auxiliary_device in the error path directly. + * + * It returns 0 on success. On success, the device_initialize has been performed. After this + * point any error unwinding will need to include a call to auxiliary_device_init(). + * In this post-initialize error scenario, a call to the device's .release callback will be + * triggered by auxiliary_device_uninit(), and all memory clean-up is expected to be + * handled there. + */ +int auxiliary_device_init(struct auxiliary_device *auxdev) +{ + struct device *dev = &auxdev->dev; + + dev->bus = &auxiliary_bus_type; + + if (!dev->parent) { + pr_err("auxiliary_device has a NULL dev->parent\n"); + return -EINVAL; + } + + if (!auxdev->name) { + pr_err("acillary_device has a NULL name\n"); + return -EINVAL; + } + + device_initialize(&auxdev->dev); + return 0; +} +EXPORT_SYMBOL_GPL(auxiliary_device_init); + +/** + * __auxiliary_device_add - add an auxiliary bus device + * @auxdev: auxiliary bus device to add to the bus + * @modname: name of the parent device's driver module + * + * This is the second step in the two-step process to register an auxiliary_device. + * + * This function must be called after a successful call to auxiliary_device_init(), which + * will perform the device_initialize. This means that if this returns an error code, then a + * call to auxiliary_device_uninit() must be performed so that the .release callback will + * be triggered to free the memory associated with the auxiliary_device. + */ +int __auxiliary_device_add(struct auxiliary_device *auxdev, const char *modname) +{ + struct device *dev = &auxdev->dev; + int ret; + + if (!modname) { + pr_err("auxiliary device modname is NULL\n"); + return -EINVAL; + } + + ret = dev_set_name(dev, "%s.%s.%d", modname, auxdev->name, auxdev->id); + if (ret) { + pr_err("auxiliary device dev_set_name failed: %d\n", ret); + return ret; + } + + ret = device_add(dev); + if (ret) + dev_err(dev, "adding auxiliary device failed!: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(__auxiliary_device_add); + +/** + * auxiliary_find_device - auxiliary device iterator for locating a particular device. + * @start: Device to begin with + * @data: Data to pass to match function + * @match: Callback function to check device + * + * This function returns a reference to a device that is 'found' + * for later use, as determined by the @match callback. + * + * The callback should return 0 if the device doesn't match and non-zero + * if it does. If the callback returns non-zero, this function will + * return to the caller and not iterate over any more devices. + */ +struct auxiliary_device * +auxiliary_find_device(struct device *start, const void *data, + int (*match)(struct device *dev, const void *data)) +{ + struct device *dev; + + dev = bus_find_device(&auxiliary_bus_type, start, data, match); + if (!dev) + return NULL; + + return to_auxiliary_dev(dev); +} +EXPORT_SYMBOL_GPL(auxiliary_find_device); + +/** + * __auxiliary_driver_register - register a driver for auxiliary bus devices + * @auxdrv: auxiliary_driver structure + * @owner: owning module/driver + * @modname: KBUILD_MODNAME for parent driver + */ +int __auxiliary_driver_register(struct auxiliary_driver *auxdrv, struct module *owner, + const char *modname) +{ + size_t mlen, dlen = 0; + char *name; + + if (WARN_ON(!auxdrv->probe) || WARN_ON(!auxdrv->id_table)) + return -EINVAL; + + mlen = strlen(modname); + if (auxdrv->name) + dlen = strlen(auxdrv->name+1); + /* + * The driver name will be ., so we need + * to add "2" bytes, one for "." and another for the NULL-termination. + */ + name = kmalloc(mlen + dlen + 1, GFP_KERNEL); + if (!name) + return -ENOMEM; + + strcpy(name, modname); + if (dlen) { + strcat(name, "."); + strcat(name, auxdrv->name); + } + + auxdrv->driver.owner = owner; + auxdrv->driver.bus = &auxiliary_bus_type; + auxdrv->driver.mod_name = modname; + auxdrv->driver.name = name; + + return driver_register(&auxdrv->driver); +} +EXPORT_SYMBOL_GPL(__auxiliary_driver_register); + +/** + * auxiliary_driver_unregister - unregister a driver + * @auxdrv: auxiliary_driver structure + */ +void auxiliary_driver_unregister(struct auxiliary_driver *auxdrv) +{ + driver_unregister(&auxdrv->driver); + kfree(auxdrv->driver.name); +} +EXPORT_SYMBOL_GPL(auxiliary_driver_unregister); + +static int __init auxiliary_bus_init(void) +{ + return bus_register(&auxiliary_bus_type); +} + +static void __exit auxiliary_bus_exit(void) +{ + bus_unregister(&auxiliary_bus_type); +} + +module_init(auxiliary_bus_init); +module_exit(auxiliary_bus_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Auxiliary Bus"); +MODULE_AUTHOR("David Ertman "); +MODULE_AUTHOR("Kiran Patil "); diff --git a/include/linux/auxiliary_bus.h b/include/linux/auxiliary_bus.h new file mode 100644 index 00000000000000..282fbf7bf9af63 --- /dev/null +++ b/include/linux/auxiliary_bus.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019-2020 Intel Corporation + * + * Please see Documentation/driver-api/auxiliary_bus.rst for more information. + */ + +#ifndef _AUXILIARY_BUS_H_ +#define _AUXILIARY_BUS_H_ + +#include +#include +#include + +struct auxiliary_device { + struct device dev; + const char *name; + u32 id; +}; + +struct auxiliary_driver { + int (*probe)(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id); + int (*remove)(struct auxiliary_device *auxdev); + void (*shutdown)(struct auxiliary_device *auxdev); + int (*suspend)(struct auxiliary_device *auxdev, pm_message_t state); + int (*resume)(struct auxiliary_device *auxdev); + const char *name; + struct device_driver driver; + const struct auxiliary_device_id *id_table; +}; + +static inline struct auxiliary_device *to_auxiliary_dev(struct device *dev) +{ + return container_of(dev, struct auxiliary_device, dev); +} + +static inline struct auxiliary_driver *to_auxiliary_drv(struct device_driver *drv) +{ + return container_of(drv, struct auxiliary_driver, driver); +} + +int auxiliary_device_init(struct auxiliary_device *auxdev); +int __auxiliary_device_add(struct auxiliary_device *auxdev, const char *modname); +#define auxiliary_device_add(auxdev) __auxiliary_device_add(auxdev, KBUILD_MODNAME) + +static inline void auxiliary_device_uninit(struct auxiliary_device *auxdev) +{ + put_device(&auxdev->dev); +} + +static inline void auxiliary_device_delete(struct auxiliary_device *auxdev) +{ + device_del(&auxdev->dev); +} + +int __auxiliary_driver_register(struct auxiliary_driver *auxdrv, struct module *owner, + const char *modname); +#define auxiliary_driver_register(auxdrv) \ + __auxiliary_driver_register(auxdrv, THIS_MODULE, KBUILD_MODNAME) + +void auxiliary_driver_unregister(struct auxiliary_driver *auxdrv); + +/** + * module_auxiliary_driver() - Helper macro for registering an auxiliary driver + * @__auxiliary_driver: auxiliary driver struct + * + * Helper macro for auxiliary drivers which do not do anything special in + * module init/exit. This eliminates a lot of boilerplate. Each module may only + * use this macro once, and calling it replaces module_init() and module_exit() + */ +#define module_auxiliary_driver(__auxiliary_driver) \ + module_driver(__auxiliary_driver, auxiliary_driver_register, auxiliary_driver_unregister) + +struct auxiliary_device * +auxiliary_find_device(struct device *start, const void *data, + int (*match)(struct device *dev, const void *data)); + +#endif /* _AUXILIARY_BUS_H_ */ diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 5b08a473cdba4d..c425290b21e268 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -838,4 +838,12 @@ struct mhi_device_id { kernel_ulong_t driver_data; }; +#define AUXILIARY_NAME_SIZE 32 +#define AUXILIARY_MODULE_PREFIX "auxiliary:" + +struct auxiliary_device_id { + char name[AUXILIARY_NAME_SIZE]; + kernel_ulong_t driver_data; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index 27007c18e75428..e377f52dbfa32c 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -243,5 +243,8 @@ int main(void) DEVID(mhi_device_id); DEVID_FIELD(mhi_device_id, chan); + DEVID(auxiliary_device_id); + DEVID_FIELD(auxiliary_device_id, name); + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 2417dd1dee33cb..fb482702753620 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1364,6 +1364,13 @@ static int do_mhi_entry(const char *filename, void *symval, char *alias) { DEF_FIELD_ADDR(symval, mhi_device_id, chan); sprintf(alias, MHI_DEVICE_MODALIAS_FMT, *chan); + return 1; +} + +static int do_auxiliary_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD_ADDR(symval, auxiliary_device_id, name); + sprintf(alias, AUXILIARY_MODULE_PREFIX "%s", *name); return 1; } @@ -1442,6 +1449,7 @@ static const struct devtable devtable[] = { {"tee", SIZE_tee_client_device_id, do_tee_entry}, {"wmi", SIZE_wmi_device_id, do_wmi_entry}, {"mhi", SIZE_mhi_device_id, do_mhi_entry}, + {"auxiliary", SIZE_auxiliary_device_id, do_auxiliary_entry}, }; /* Create MODULE_ALIAS() statements. diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 031dad5fc4c701..bec0a5b59c595d 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -42,13 +42,29 @@ config SND_SOC_SOF_OF Say Y if you need this option. If unsure select "N". config SND_SOC_SOF_DEBUG_PROBES - bool "SOF enable data probing" + bool select SND_SOC_COMPRESS help - This option enables the data probing feature that can be used to - gather data directly from specific points of the audio pipeline. - Say Y if you want to enable probes. - If unsure, select "N". + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_CLIENT + tristate + select AUXILIARY_BUS + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_CLIENT_SUPPORT + bool "SOF enable clients" + depends on SND_SOC_SOF + help + This adds support for auxiliary client devices to separate out the debug + functionality for IPC tests, probes etc. into separate devices. This + option would also allow adding client devices based on DSP firmware + capabilities and ACPI/OF device information. + Say Y if you want to enable clients with SOF. + If unsure select "N". config SND_SOC_SOF_DEVELOPER_SUPPORT bool "SOF developer options support" @@ -164,14 +180,25 @@ config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE module parameter (similar to dynamic debug). If unsure, select "N". -config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST - bool "SOF enable IPC flood test" +config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_CLIENT + tristate "SOF enable IPC flood test client" + depends on SND_SOC_SOF_CLIENT help - This option enables the IPC flood test which can be used to flood - the DSP with test IPCs and gather stats about response times. + This option enables a separate client device for IPC flood test + which can be used to flood the DSP with test IPCs and gather stats + about response times. Say Y if you want to enable IPC flood test. If unsure, select "N". +config SND_SOC_SOF_DEBUG_PROBES_CLIENT + tristate "SOF enable data probing" + depends on SND_SOC_SOF_CLIENT + help + This option enables the data probing feature that can be used to + gather data directly from specific points of the audio pipeline. + Say Y if you want to enable probes. + If unsure, select "N". + config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT bool "SOF retain DSP context on any FW exceptions" help @@ -186,6 +213,8 @@ endif ## SND_SOC_SOF_DEVELOPER_SUPPORT config SND_SOC_SOF tristate + select SND_SOC_SOF_CLIENT if SND_SOC_SOF_CLIENT_SUPPORT + select SND_SOC_SOF_DEBUG_PROBES if SND_SOC_SOF_DEBUG_PROBES_CLIENT select SND_SOC_TOPOLOGY select SND_SOC_SOF_NOCODEC if SND_SOC_SOF_NOCODEC_SUPPORT help diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 05718dfe6cd2f3..cf49466f791071 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -2,12 +2,15 @@ snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ control.o trace.o utils.o sof-audio.o -snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += probe.o compress.o +snd-sof-client-objs := sof-client.o +snd-sof-probes-objs := probe.o compress.o sof-probes-client.o snd-sof-pci-objs := sof-pci-dev.o snd-sof-acpi-objs := sof-acpi-dev.o snd-sof-of-objs := sof-of-dev.o +snd-sof-ipc-test-objs := sof-ipc-test-client.o + snd-sof-nocodec-objs := nocodec.o obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o @@ -18,6 +21,11 @@ obj-$(CONFIG_SND_SOC_SOF_ACPI) += snd-sof-acpi.o obj-$(CONFIG_SND_SOC_SOF_OF) += snd-sof-of.o obj-$(CONFIG_SND_SOC_SOF_PCI) += snd-sof-pci.o +obj-$(CONFIG_SND_SOC_SOF_CLIENT) += snd-sof-client.o + +obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_CLIENT) += snd-sof-ipc-test.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES_CLIENT) += snd-sof-probes.o + obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/ diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 2d4969c705a4e5..bbb77f028e7445 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -10,8 +10,17 @@ #include #include "compress.h" -#include "ops.h" #include "probe.h" +#include "sof-client.h" + +struct snd_soc_cdai_ops sof_probe_compr_ops = { + .startup = sof_probe_compr_open, + .shutdown = sof_probe_compr_free, + .set_params = sof_probe_compr_set_params, + .trigger = sof_probe_compr_trigger, + .pointer = sof_probe_compr_pointer, +}; +EXPORT_SYMBOL(sof_probe_compr_ops); struct snd_compress_ops sof_probe_compressed_ops = { .copy = sof_probe_compr_copy, @@ -21,17 +30,18 @@ EXPORT_SYMBOL(sof_probe_compressed_ops); int sof_probe_compr_open(struct snd_compr_stream *cstream, struct snd_soc_dai *dai) { - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_data *probes_data = cdev->data; int ret; - ret = snd_sof_probe_compr_assign(sdev, cstream, dai); + ret = sof_client_probe_compr_assign(cdev, cstream, dai); if (ret < 0) { dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret); return ret; } - sdev->extractor_stream_tag = ret; + probes_data->extractor_stream_tag = ret; return 0; } EXPORT_SYMBOL(sof_probe_compr_open); @@ -39,55 +49,57 @@ EXPORT_SYMBOL(sof_probe_compr_open); int sof_probe_compr_free(struct snd_compr_stream *cstream, struct snd_soc_dai *dai) { - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_data *probes_data = cdev->data; struct sof_probe_point_desc *desc; size_t num_desc; int i, ret; /* disconnect all probe points */ - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); + ret = sof_probe_points_info(cdev, &desc, &num_desc); if (ret < 0) { dev_err(dai->dev, "Failed to get probe points: %d\n", ret); goto exit; } for (i = 0; i < num_desc; i++) - sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1); + sof_probe_points_remove(cdev, &desc[i].buffer_id, 1); kfree(desc); exit: - ret = sof_ipc_probe_deinit(sdev); + ret = sof_probe_deinit(cdev); if (ret < 0) dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); - sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; + probes_data->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; snd_compr_free_pages(cstream); - return snd_sof_probe_compr_free(sdev, cstream, dai); + return sof_client_probe_compr_free(cdev, cstream, dai); } EXPORT_SYMBOL(sof_probe_compr_free); int sof_probe_compr_set_params(struct snd_compr_stream *cstream, struct snd_compr_params *params, struct snd_soc_dai *dai) { + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_data *probes_data = cdev->data; struct snd_compr_runtime *rtd = cstream->runtime; - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); int ret; cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; - cstream->dma_buffer.dev.dev = sdev->dev; + cstream->dma_buffer.dev.dev = sof_client_get_dma_dev(cdev); ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); if (ret < 0) return ret; - ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai); + ret = sof_client_probe_compr_set_params(cdev, cstream, params, dai); if (ret < 0) return ret; - ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag, - rtd->dma_bytes); + ret = sof_probe_init(cdev, probes_data->extractor_stream_tag, + rtd->dma_bytes); if (ret < 0) { dev_err(dai->dev, "Failed to init probe: %d\n", ret); return ret; @@ -100,20 +112,20 @@ EXPORT_SYMBOL(sof_probe_compr_set_params); int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd, struct snd_soc_dai *dai) { - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); - return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai); + return sof_client_probe_compr_trigger(cdev, cstream, cmd, dai); } EXPORT_SYMBOL(sof_probe_compr_trigger); int sof_probe_compr_pointer(struct snd_compr_stream *cstream, struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai) { - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); - return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai); + return sof_client_probe_compr_pointer(cdev, cstream, tstamp, dai); } EXPORT_SYMBOL(sof_probe_compr_pointer); diff --git a/sound/soc/sof/compress.h b/sound/soc/sof/compress.h index ca8790bd4b1339..689c83ac8ffc96 100644 --- a/sound/soc/sof/compress.h +++ b/sound/soc/sof/compress.h @@ -13,6 +13,7 @@ #include +extern struct snd_soc_cdai_ops sof_probe_compr_ops; extern struct snd_compress_ops sof_probe_compressed_ops; int sof_probe_compr_open(struct snd_compr_stream *cstream, diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index adc7c37145d640..9b72317d652502 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -14,9 +14,6 @@ #include #include "sof-priv.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "probe.h" -#endif /* see SOF_DBG_ flags */ int sof_core_debug; @@ -246,8 +243,17 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) if (plat_data->sof_probe_complete) plat_data->sof_probe_complete(sdev->dev); + /* If registering certain clients fails, unregister the previously registered clients. */ + ret = snd_sof_register_clients(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to register clients %d\n", ret); + goto client_reg_err; + } + return 0; +client_reg_err: + snd_sof_unregister_clients(sdev); fw_trace_err: snd_sof_free_trace(sdev); fw_run_err: @@ -296,9 +302,6 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) sdev->pdata = plat_data; sdev->first_boot = true; sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; -#endif dev_set_drvdata(dev, sdev); /* check all mandatory ops */ @@ -314,8 +317,10 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->widget_list); INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->route_list); + INIT_LIST_HEAD(&sdev->client_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock); + mutex_init(&sdev->client_mutex); if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) INIT_WORK(&sdev->probe_work, sof_probe_work); @@ -354,6 +359,7 @@ int snd_sof_device_remove(struct device *dev) dev_warn(dev, "error: %d failed to prepare DSP for device removal", ret); + snd_sof_unregister_clients(sdev); snd_sof_fw_unload(sdev); snd_sof_ipc_free(sdev); snd_sof_free_debug(sdev); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 9419a99bab5367..234ae704c00125 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -17,335 +17,9 @@ #include "sof-priv.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "probe.h" - -/** - * strsplit_u32 - Split string into sequence of u32 tokens - * @buf: String to split into tokens. - * @delim: String containing delimiter characters. - * @tkns: Returned u32 sequence pointer. - * @num_tkns: Returned number of tokens obtained. - */ -static int -strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns) -{ - char *s; - u32 *data, *tmp; - size_t count = 0; - size_t cap = 32; - int ret = 0; - - *tkns = NULL; - *num_tkns = 0; - data = kcalloc(cap, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - while ((s = strsep(buf, delim)) != NULL) { - ret = kstrtouint(s, 0, data + count); - if (ret) - goto exit; - if (++count >= cap) { - cap *= 2; - tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); - if (!tmp) { - ret = -ENOMEM; - goto exit; - } - data = tmp; - } - } - - if (!count) - goto exit; - *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); - if (*tkns == NULL) { - ret = -ENOMEM; - goto exit; - } - *num_tkns = count; - -exit: - kfree(data); - return ret; -} - -static int tokenize_input(const char __user *from, size_t count, - loff_t *ppos, u32 **tkns, size_t *num_tkns) -{ - char *buf; - int ret; - - buf = kmalloc(count + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = simple_write_to_buffer(buf, count, ppos, from, count); - if (ret != count) { - ret = ret >= 0 ? -EIO : ret; - goto exit; - } - - buf[count] = '\0'; - ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_read(struct file *file, - char __user *to, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_desc, len = 0; - char *buf; - int i, ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - buf = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); - if (ret < 0) - goto exit; - - for (i = 0; i < num_desc; i++) { - ret = snprintf(buf + len, PAGE_SIZE - len, - "Id: %#010x Purpose: %d Node id: %#x\n", - desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); - if (ret < 0) - goto free_desc; - len += ret; - } - - ret = simple_read_from_buffer(to, count, ppos, buf, len); -free_desc: - kfree(desc); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_tkns, bytes; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - bytes = sizeof(*tkns) * num_tkns; - if (!num_tkns || (bytes % sizeof(*desc))) { - ret = -EINVAL; - goto exit; - } - - desc = (struct sof_probe_point_desc *)tkns; - ret = sof_ipc_probe_points_add(sdev, - desc, bytes / sizeof(*desc)); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_fops = { - .open = simple_open, - .read = probe_points_read, - .write = probe_points_write, - .llseek = default_llseek, -}; - -static ssize_t probe_points_remove_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - size_t num_tkns; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - if (!num_tkns) { - ret = -EINVAL; - goto exit; - } - - ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_remove_fops = { - .open = simple_open, - .write = probe_points_remove_write, - .llseek = default_llseek, -}; - -static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, - const char *name, mode_t mode, - const struct file_operations *fops) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->sdev = sdev; - - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) -#define MAX_IPC_FLOOD_DURATION_MS 1000 -#define MAX_IPC_FLOOD_COUNT 10000 -#define IPC_FLOOD_TEST_RESULT_LEN 512 - -static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev, - struct snd_sof_dfsentry *dfse, - bool flood_duration_test, - unsigned long ipc_duration_ms, - unsigned long ipc_count) -{ - struct sof_ipc_cmd_hdr hdr; - struct sof_ipc_reply reply; - u64 min_response_time = U64_MAX; - ktime_t start, end, test_end; - u64 avg_response_time = 0; - u64 max_response_time = 0; - u64 ipc_response_time; - int i = 0; - int ret; - - /* configure test IPC */ - hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; - hdr.size = sizeof(hdr); - - /* set test end time for duration flood test */ - if (flood_duration_test) - test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; - - /* send test IPC's */ - while (1) { - start = ktime_get(); - ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size, - &reply, sizeof(reply)); - end = ktime_get(); - - if (ret < 0) - break; - - /* compute min and max response times */ - ipc_response_time = ktime_to_ns(ktime_sub(end, start)); - min_response_time = min(min_response_time, ipc_response_time); - max_response_time = max(max_response_time, ipc_response_time); - - /* sum up response times */ - avg_response_time += ipc_response_time; - i++; - - /* test complete? */ - if (flood_duration_test) { - if (ktime_to_ns(end) >= test_end) - break; - } else { - if (i == ipc_count) - break; - } - } - - if (ret < 0) - dev_err(sdev->dev, - "error: ipc flood test failed at %d iterations\n", i); - - /* return if the first IPC fails */ - if (!i) - return ret; - - /* compute average response time */ - do_div(avg_response_time, i); - - /* clear previous test output */ - memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN); - - if (flood_duration_test) { - dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n", - ipc_duration_ms); - snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN, - "IPC Flood test duration: %lums\n", ipc_duration_ms); - } - - dev_dbg(sdev->dev, - "IPC Flood count: %d, Avg response time: %lluns\n", - i, avg_response_time); - dev_dbg(sdev->dev, "Max response time: %lluns\n", - max_response_time); - dev_dbg(sdev->dev, "Min response time: %lluns\n", - min_response_time); - - /* format output string */ - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "IPC Flood count: %d\nAvg response time: %lluns\n", - i, avg_response_time); - - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "Max response time: %lluns\nMin response time: %lluns\n", - max_response_time, min_response_time); - - return ret; -} -#endif - static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - unsigned long ipc_duration_ms = 0; - bool flood_duration_test = false; - unsigned long ipc_count = 0; - struct dentry *dentry; - int err; -#endif size_t size; char *string; int ret; @@ -357,78 +31,6 @@ static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size = simple_write_to_buffer(string, count, ppos, buffer, count); ret = size; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* - * write op is only supported for ipc_flood_count or - * ipc_flood_duration_ms debugfs entries atm. - * ipc_flood_count floods the DSP with the number of IPC's specified. - * ipc_duration_ms test floods the DSP for the time specified - * in the debugfs entry. - */ - dentry = file->f_path.dentry; - if (strcmp(dentry->d_name.name, "ipc_flood_count") && - strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) { - ret = -EINVAL; - goto out; - } - - if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) - flood_duration_test = true; - - /* test completion criterion */ - if (flood_duration_test) - ret = kstrtoul(string, 0, &ipc_duration_ms); - else - ret = kstrtoul(string, 0, &ipc_count); - if (ret < 0) - goto out; - - /* limit max duration/ipc count for flood test */ - if (flood_duration_test) { - if (!ipc_duration_ms) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) - ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; - } else { - if (!ipc_count) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_count > MAX_IPC_FLOOD_COUNT) - ipc_count = MAX_IPC_FLOOD_COUNT; - } - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to resume %d\n", - ret); - pm_runtime_put_noidle(sdev->dev); - goto out; - } - - /* flood test */ - ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test, - ipc_duration_ms, ipc_count); - - pm_runtime_mark_last_busy(sdev->dev); - err = pm_runtime_put_autosuspend(sdev->dev); - if (err < 0) - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to idle %d\n", - err); - - /* return size if test is successful */ - if (ret >= 0) - ret = size; -out: -#endif kfree(string); return ret; } @@ -444,25 +46,6 @@ static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, int size; u8 *buf; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct dentry *dentry; - - dentry = file->f_path.dentry; - if ((!strcmp(dentry->d_name.name, "ipc_flood_count") || - !strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) && - dfse->cache_buf) { - if (*ppos) - return 0; - - count = strlen(dfse->cache_buf); - size_ret = copy_to_user(buffer, dfse->cache_buf, count); - if (size_ret) - return -EFAULT; - - *ppos += count; - return count; - } -#endif size = dfse->size; /* validate position & count */ @@ -606,17 +189,6 @@ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, dfse->size = size; dfse->sdev = sdev; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* - * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries. - * So, use it to save the results of the last IPC flood test. - */ - dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN, - GFP_KERNEL); - if (!dfse->cache_buf) - return -ENOMEM; -#endif - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, &sof_dfs_fops); /* add to dfsentry list */ @@ -651,35 +223,6 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) return err; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - err = snd_sof_debugfs_probe_item(sdev, "probe_points", - 0644, &probe_points_fops); - if (err < 0) - return err; - err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove", - 0200, &probe_points_remove_fops); - if (err < 0) - return err; -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* create read-write ipc_flood_count debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_count", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; - - /* create read-write ipc_flood_duration_ms debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_duration_ms", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; -#endif - return 0; } EXPORT_SYMBOL_GPL(snd_sof_dbg_init); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 6836a8b6cec4d4..d863b368d430ac 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -13,6 +13,8 @@ config SND_SOC_SOF_INTEL_ACPI def_tristate SND_SOC_SOF_ACPI select SND_SOC_SOF_BAYTRAIL if SND_SOC_SOF_BAYTRAIL_SUPPORT select SND_SOC_SOF_BROADWELL if SND_SOC_SOF_BROADWELL_SUPPORT + select SND_SOC_SOF_PROBE_WORK_QUEUE if SND_SOC_SOF_CLIENT + select SND_SOC_SOF_INTEL_CLIENT if SND_SOC_SOF_CLIENT help This option is not user-selectable but automagically handled by 'select' statements at a higher level. @@ -29,6 +31,7 @@ config SND_SOC_SOF_INTEL_PCI select SND_SOC_SOF_TIGERLAKE if SND_SOC_SOF_TIGERLAKE_SUPPORT select SND_SOC_SOF_ELKHARTLAKE if SND_SOC_SOF_ELKHARTLAKE_SUPPORT select SND_SOC_SOF_JASPERLAKE if SND_SOC_SOF_JASPERLAKE_SUPPORT + select SND_SOC_SOF_INTEL_CLIENT if SND_SOC_SOF_CLIENT help This option is not user-selectable but automagically handled by 'select' statements at a higher level. @@ -57,6 +60,12 @@ config SND_SOC_SOF_INTEL_COMMON This option is not user-selectable but automagically handled by 'select' statements at a higher level. +config SND_SOC_SOF_INTEL_CLIENT + tristate + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + if SND_SOC_SOF_INTEL_ACPI config SND_SOC_SOF_BAYTRAIL_SUPPORT diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile index 72d85b25df7d4a..683e64c627c193 100644 --- a/sound/soc/sof/intel/Makefile +++ b/sound/soc/sof/intel/Makefile @@ -5,6 +5,8 @@ snd-sof-intel-bdw-objs := bdw.o snd-sof-intel-ipc-objs := intel-ipc.o +snd-sof-intel-client-objs := intel-client.o + snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \ hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \ hda-dai.o hda-bus.o \ @@ -18,3 +20,4 @@ obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-intel-bdw.o obj-$(CONFIG_SND_SOC_SOF_INTEL_HIFI_EP_IPC) += snd-sof-intel-ipc.o obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o +obj-$(CONFIG_SND_SOC_SOF_INTEL_CLIENT) += snd-sof-intel-client.o diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 4eeade2e77f7f7..3489678e711ea4 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -15,9 +15,12 @@ * Hardware interface for audio DSP on Apollolake and GeminiLake */ +#include #include "../sof-priv.h" #include "hda.h" #include "../sof-audio.h" +#include "../sof-client.h" +#include "intel-client.h" static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = { {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, @@ -25,6 +28,16 @@ static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = { {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, }; +static int apl_register_clients(struct snd_sof_dev *sdev) +{ + return intel_register_ipc_test_clients(sdev); +} + +static void apl_unregister_clients(struct snd_sof_dev *sdev) +{ + intel_unregister_ipc_test_clients(sdev); +} + /* apollolake ops */ const struct snd_sof_dsp_ops sof_apl_ops = { /* probe and remove */ @@ -101,6 +114,10 @@ const struct snd_sof_dsp_ops sof_apl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger, + /* client ops */ + .register_clients = apl_register_clients, + .unregister_clients = apl_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, @@ -140,3 +157,4 @@ const struct sof_intel_dsp_desc apl_chip_info = { .ssp_base_offset = APL_SSP_BASE_OFFSET, }; EXPORT_SYMBOL_NS(apl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_CLIENT); diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c index 50a4a73e6b9f68..b5e2969e67e815 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -12,12 +12,15 @@ * Hardware interface for audio DSP on Broadwell */ +#include #include #include #include #include "../ops.h" #include "shim.h" #include "../sof-audio.h" +#include "../sof-client.h" +#include "intel-client.h" /* BARs */ #define BDW_DSP_BAR 0 @@ -563,6 +566,16 @@ static void bdw_set_mach_params(const struct snd_soc_acpi_mach *mach, mach_params->platform = dev_name(dev); } +static int bdw_register_clients(struct snd_sof_dev *sdev) +{ + return intel_register_ipc_test_clients(sdev); +} + +static void bdw_unregister_clients(struct snd_sof_dev *sdev) +{ + intel_unregister_ipc_test_clients(sdev); +} + /* Broadwell DAIs */ static struct snd_soc_dai_driver bdw_dai[] = { { @@ -638,6 +651,10 @@ const struct snd_sof_dsp_ops sof_bdw_ops = { /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, + /* client ops */ + .register_clients = bdw_register_clients, + .unregister_clients = bdw_unregister_clients, + /* DAI drivers */ .drv = bdw_dai, .num_drv = ARRAY_SIZE(bdw_dai), @@ -662,3 +679,4 @@ EXPORT_SYMBOL_NS(bdw_chip_info, SND_SOC_SOF_BROADWELL); MODULE_LICENSE("Dual BSD/GPL"); MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC); MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_CLIENT); diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index 186736ee5fc2e8..e1438e403bbcbd 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -12,13 +12,16 @@ * Hardware interface for audio DSP on Baytrail, Braswell and Cherrytrail. */ +#include #include #include #include #include "../ops.h" #include "shim.h" #include "../sof-audio.h" +#include "../sof-client.h" #include "../../intel/common/soc-intel-quirks.h" +#include "intel-client.h" /* DSP memories */ #define IRAM_OFFSET 0x0C0000 @@ -821,6 +824,16 @@ static int byt_acpi_probe(struct snd_sof_dev *sdev) return ret; } +static int byt_register_clients(struct snd_sof_dev *sdev) +{ + return intel_register_ipc_test_clients(sdev); +} + +static void byt_unregister_clients(struct snd_sof_dev *sdev) +{ + intel_unregister_ipc_test_clients(sdev); +} + /* baytrail ops */ const struct snd_sof_dsp_ops sof_byt_ops = { /* device init */ @@ -879,6 +892,10 @@ const struct snd_sof_dsp_ops sof_byt_ops = { .suspend = byt_suspend, .resume = byt_resume, + /* client ops */ + .register_clients = byt_register_clients, + .unregister_clients = byt_unregister_clients, + /* DAI drivers */ .drv = byt_dai, .num_drv = 3, /* we have only 3 SSPs on byt*/ @@ -958,6 +975,10 @@ const struct snd_sof_dsp_ops sof_cht_ops = { .suspend = byt_suspend, .resume = byt_resume, + /* client ops */ + .register_clients = byt_register_clients, + .unregister_clients = byt_unregister_clients, + /* DAI drivers */ .drv = byt_dai, /* all 6 SSPs may be available for cherrytrail */ @@ -985,3 +1006,4 @@ EXPORT_SYMBOL_NS(cht_chip_info, SND_SOC_SOF_BAYTRAIL); MODULE_LICENSE("Dual BSD/GPL"); MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC); MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_CLIENT); diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index a5d3258104c08d..b281538e3b2925 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -15,10 +15,13 @@ * Hardware interface for audio DSP on Cannonlake. */ +#include #include "../ops.h" #include "hda.h" #include "hda-ipc.h" #include "../sof-audio.h" +#include "../sof-client.h" +#include "intel-client.h" static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = { {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, @@ -230,6 +233,30 @@ void cnl_ipc_dump(struct snd_sof_dev *sdev) hipcida, hipctdr, hipcctl); } +static int cnl_register_clients(struct snd_sof_dev *sdev) +{ + int ret; + + ret = intel_register_ipc_test_clients(sdev); + if (ret < 0) + return ret; + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) + return sof_client_dev_register(sdev, "probes", 0); +#endif + + return 0; +} + +static void cnl_unregister_clients(struct snd_sof_dev *sdev) +{ + intel_unregister_ipc_test_clients(sdev); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) + sof_client_dev_unregister(sdev, "probes", 0); +#endif +} + /* cannonlake ops */ const struct snd_sof_dsp_ops sof_cnl_ops = { /* probe and remove */ @@ -306,6 +333,10 @@ const struct snd_sof_dsp_ops sof_cnl_ops = { .trace_release = hda_dsp_trace_release, .trace_trigger = hda_dsp_trace_trigger, + /* client ops */ + .register_clients = cnl_register_clients, + .unregister_clients = cnl_unregister_clients, + /* DAI drivers */ .drv = skl_dai, .num_drv = SOF_SKL_NUM_DAIS, @@ -393,3 +424,5 @@ const struct sof_intel_dsp_desc jsl_chip_info = { .ssp_base_offset = CNL_SSP_BASE_OFFSET, }; EXPORT_SYMBOL_NS(jsl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_CLIENT); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index c6cb8c212eca59..35965e2e72de2a 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -400,18 +400,6 @@ static const struct snd_soc_dai_ops hda_link_dai_ops = { .prepare = hda_link_pcm_prepare, }; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -#include "../compress.h" - -static struct snd_soc_cdai_ops sof_probe_compr_ops = { - .startup = sof_probe_compr_open, - .shutdown = sof_probe_compr_free, - .set_params = sof_probe_compr_set_params, - .trigger = sof_probe_compr_trigger, - .pointer = sof_probe_compr_pointer, -}; - -#endif #endif /* @@ -569,20 +557,5 @@ struct snd_soc_dai_driver skl_dai[] = { .channels_max = 16, }, }, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -{ - .name = "Probe Extraction CPU DAI", - .compress_new = snd_soc_new_compress, - .cops = &sof_probe_compr_ops, - .capture = { - .stream_name = "Probe Extraction", - .channels_min = 1, - .channels_max = 8, - .rates = SNDRV_PCM_RATE_48000, - .rate_min = 48000, - .rate_max = 48000, - }, -}, -#endif #endif }; diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 1bc4dabdd39450..f0ac57e8b24372 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -352,13 +352,7 @@ /* Number of DAIs */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -#define SOF_SKL_NUM_DAIS 16 -#else #define SOF_SKL_NUM_DAIS 15 -#endif - #else #define SOF_SKL_NUM_DAIS 8 #endif diff --git a/sound/soc/sof/intel/intel-client.c b/sound/soc/sof/intel/intel-client.c new file mode 100644 index 00000000000000..a612de365fc50a --- /dev/null +++ b/sound/soc/sof/intel/intel-client.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan +// + +#include +#include "../sof-priv.h" +#include "../sof-client.h" +#include "intel-client.h" + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_CLIENT) +int intel_register_ipc_test_clients(struct snd_sof_dev *sdev) +{ + int ret; + + /* + * Register 2 IPC clients to facilitate tandem flood test. The device name below is + * appended with the device ID assigned automatically when the auxiliary device is + * registered making them unique. + */ + ret = sof_client_dev_register(sdev, "ipc_test", 0); + if (ret < 0) + return ret; + + return sof_client_dev_register(sdev, "ipc_test", 1); +} +EXPORT_SYMBOL_NS_GPL(intel_register_ipc_test_clients, SND_SOC_SOF_INTEL_CLIENT); + +void intel_unregister_ipc_test_clients(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "ipc_test", 0); + sof_client_dev_unregister(sdev, "ipc_test", 1); +} +EXPORT_SYMBOL_NS_GPL(intel_unregister_ipc_test_clients, SND_SOC_SOF_INTEL_CLIENT); +#endif + +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/intel/intel-client.h b/sound/soc/sof/intel/intel-client.h new file mode 100644 index 00000000000000..49b2c6c0dcc4d5 --- /dev/null +++ b/sound/soc/sof/intel/intel-client.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2020 Intel Corporation. All rights reserved. + * + * Author: Ranjani Sridharan + */ + +#ifndef __INTEL_CLIENT_H +#define __INTEL_CLIENT_H + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_CLIENT) +int intel_register_ipc_test_clients(struct snd_sof_dev *sdev); +void intel_unregister_ipc_test_clients(struct snd_sof_dev *sdev); +#else +static inline int intel_register_ipc_test_clients(struct snd_sof_dev *sdev) +{ + return 0; +} + +static void intel_unregister_ipc_test_clients(struct snd_sof_dev *sdev) {} +#endif + +#endif diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index b21632f5511a10..00370f8bcd75aa 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -470,6 +470,20 @@ snd_sof_set_mach_params(const struct snd_soc_acpi_mach *mach, sof_ops(sdev)->set_mach_params(mach, dev); } +static inline int snd_sof_register_clients(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->register_clients) + return sof_ops(sdev)->register_clients(sdev); + + return 0; +} + +static inline void snd_sof_unregister_clients(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->unregister_clients) + sof_ops(sdev)->unregister_clients(sdev); +} + static inline const struct snd_sof_dsp_ops *sof_get_ops(const struct sof_dev_desc *d, const struct sof_ops_table mach_ops[], int asize) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 967c29f303648b..dc17d266fbaad5 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -16,9 +16,6 @@ #include "sof-priv.h" #include "sof-audio.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "compress.h" -#endif /* Create DMA buffer page table for DSP */ static int create_page_table(struct snd_soc_component *component, @@ -794,14 +791,6 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->hw_free = sof_pcm_hw_free; pd->trigger = sof_pcm_trigger; pd->pointer = sof_pcm_pointer; - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) - pd->compress_ops = &sof_compressed_ops; -#endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - /* override cops when probe support is enabled */ - pd->compress_ops = &sof_probe_compressed_ops; -#endif pd->pcm_construct = sof_pcm_new; pd->ignore_machine = drv_name; pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; diff --git a/sound/soc/sof/probe.c b/sound/soc/sof/probe.c index 14509f4d3f8644..a926619b484acd 100644 --- a/sound/soc/sof/probe.c +++ b/sound/soc/sof/probe.c @@ -8,12 +8,15 @@ // Author: Cezary Rojewski // -#include "sof-priv.h" +#include +#include #include "probe.h" +#include "sof-client.h" + /** - * sof_ipc_probe_init - initialize data probing - * @sdev: SOF sound device + * sof_probe_init - initialize data probing + * @cdev: SOF client device * @stream_tag: Extractor stream tag * @buffer_size: DMA buffer size to set for extractor * @@ -25,8 +28,8 @@ * Probing is initialized only once and each INIT request must be * matched by DEINIT call. */ -int sof_ipc_probe_init(struct snd_sof_dev *sdev, - u32 stream_tag, size_t buffer_size) +int sof_probe_init(struct sof_client_dev *cdev, u32 stream_tag, + size_t buffer_size) { struct sof_ipc_probe_dma_add_params *msg; struct sof_ipc_reply reply; @@ -42,22 +45,22 @@ int sof_ipc_probe_init(struct snd_sof_dev *sdev, msg->dma[0].stream_tag = stream_tag; msg->dma[0].dma_buffer_size = buffer_size; - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message(cdev, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); kfree(msg); return ret; } -EXPORT_SYMBOL(sof_ipc_probe_init); +EXPORT_SYMBOL(sof_probe_init); /** - * sof_ipc_probe_deinit - cleanup after data probing - * @sdev: SOF sound device + * sof_probe_deinit - cleanup after data probing + * @cdev: SOF client device * * Host sends DEINIT request to free previously initialized probe * on DSP side once it is no longer needed. DEINIT only when there * are no probes connected and with all injectors detached. */ -int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) +int sof_probe_deinit(struct sof_client_dev *cdev) { struct sof_ipc_cmd_hdr msg; struct sof_ipc_reply reply; @@ -65,13 +68,13 @@ int sof_ipc_probe_deinit(struct snd_sof_dev *sdev) msg.size = sizeof(msg); msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; - return sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, - &reply, sizeof(reply)); + return sof_client_ipc_tx_message(cdev, msg.cmd, &msg, msg.size, + &reply, sizeof(reply)); } -EXPORT_SYMBOL(sof_ipc_probe_deinit); +EXPORT_SYMBOL(sof_probe_deinit); -static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, - void **params, size_t *num_params) +static int sof_probe_info(struct sof_client_dev *cdev, unsigned int cmd, + void **params, size_t *num_params) { struct sof_ipc_probe_info_params msg = {{{0}}}; struct sof_ipc_probe_info_params *reply; @@ -87,8 +90,9 @@ static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, msg.rhdr.hdr.size = sizeof(msg); msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; - ret = sof_ipc_tx_message(sdev->ipc, msg.rhdr.hdr.cmd, &msg, - msg.rhdr.hdr.size, reply, SOF_IPC_MSG_MAX_SIZE); + ret = sof_client_ipc_tx_message(cdev, msg.rhdr.hdr.cmd, &msg, + msg.rhdr.hdr.size, reply, + SOF_IPC_MSG_MAX_SIZE); if (ret < 0 || reply->rhdr.error < 0) goto exit; @@ -113,8 +117,8 @@ static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, } /** - * sof_ipc_probe_dma_info - retrieve list of active injection dmas - * @sdev: SOF sound device + * sof_probe_dma_info - retrieve list of active injection dmas + * @cdev: SOF client device * @dma: Returned list of active dmas * @num_dma: Returned count of active dmas * @@ -127,17 +131,17 @@ static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd, * which is not the case for injection where multiple streams * could be engaged. */ -int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev, - struct sof_probe_dma **dma, size_t *num_dma) +int sof_probe_dma_info(struct sof_client_dev *cdev, + struct sof_probe_dma **dma, size_t *num_dma) { - return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_DMA_INFO, - (void **)dma, num_dma); + return sof_probe_info(cdev, SOF_IPC_PROBE_DMA_INFO, + (void **)dma, num_dma); } -EXPORT_SYMBOL(sof_ipc_probe_dma_info); +EXPORT_SYMBOL(sof_probe_dma_info); /** - * sof_ipc_probe_dma_add - attach to specified dmas - * @sdev: SOF sound device + * sof_probe_dma_add - attach to specified dmas + * @cdev: SOF client device * @dma: List of streams (dmas) to attach to * @num_dma: Number of elements in @dma * @@ -146,8 +150,8 @@ EXPORT_SYMBOL(sof_ipc_probe_dma_info); * for specifying streams which will be later used to transfer data * to connected probe points. */ -int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev, - struct sof_probe_dma *dma, size_t num_dma) +int sof_probe_dma_add(struct sof_client_dev *cdev, + struct sof_probe_dma *dma, size_t num_dma) { struct sof_ipc_probe_dma_add_params *msg; struct sof_ipc_reply reply; @@ -162,16 +166,16 @@ int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev, msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_ADD; memcpy(&msg->dma[0], dma, size - sizeof(*msg)); - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message(cdev, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); kfree(msg); return ret; } -EXPORT_SYMBOL(sof_ipc_probe_dma_add); +EXPORT_SYMBOL(sof_probe_dma_add); /** - * sof_ipc_probe_dma_remove - detach from specified dmas - * @sdev: SOF sound device + * sof_probe_dma_remove - detach from specified dmas + * @cdev: SOF client device * @stream_tag: List of stream tags to detach from * @num_stream_tag: Number of elements in @stream_tag * @@ -180,8 +184,8 @@ EXPORT_SYMBOL(sof_ipc_probe_dma_add); * match equivalent DMA_ADD. Detach only when all probes tied to * given stream have been disconnected. */ -int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev, - unsigned int *stream_tag, size_t num_stream_tag) +int sof_probe_dma_remove(struct sof_client_dev *cdev, + unsigned int *stream_tag, size_t num_stream_tag) { struct sof_ipc_probe_dma_remove_params *msg; struct sof_ipc_reply reply; @@ -196,16 +200,16 @@ int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev, msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_REMOVE; memcpy(&msg->stream_tag[0], stream_tag, size - sizeof(*msg)); - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message(cdev, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); kfree(msg); return ret; } -EXPORT_SYMBOL(sof_ipc_probe_dma_remove); +EXPORT_SYMBOL(sof_probe_dma_remove); /** - * sof_ipc_probe_points_info - retrieve list of active probe points - * @sdev: SOF sound device + * sof_probe_points_info - retrieve list of active probe points + * @cdev: SOF client device * @desc: Returned list of active probes * @num_desc: Returned count of active probes * @@ -213,17 +217,18 @@ EXPORT_SYMBOL(sof_ipc_probe_dma_remove); * points, valid for disconnection when given probe is no longer * required. */ -int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, size_t *num_desc) +int sof_probe_points_info(struct sof_client_dev *cdev, + struct sof_probe_point_desc **desc, + size_t *num_desc) { - return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_POINT_INFO, + return sof_probe_info(cdev, SOF_IPC_PROBE_POINT_INFO, (void **)desc, num_desc); } -EXPORT_SYMBOL(sof_ipc_probe_points_info); +EXPORT_SYMBOL(sof_probe_points_info); /** - * sof_ipc_probe_points_add - connect specified probes - * @sdev: SOF sound device + * sof_probe_points_add - connect specified probes + * @cdev: SOF client device * @desc: List of probe points to connect * @num_desc: Number of elements in @desc * @@ -234,8 +239,9 @@ EXPORT_SYMBOL(sof_ipc_probe_points_info); * Each probe point should be removed using PROBE_POINT_REMOVE * request when no longer needed. */ -int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, size_t num_desc) +int sof_probe_points_add(struct sof_client_dev *cdev, + struct sof_probe_point_desc *desc, + size_t num_desc) { struct sof_ipc_probe_point_add_params *msg; struct sof_ipc_reply reply; @@ -250,24 +256,24 @@ int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; memcpy(&msg->desc[0], desc, size - sizeof(*msg)); - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message(cdev, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); kfree(msg); return ret; } -EXPORT_SYMBOL(sof_ipc_probe_points_add); +EXPORT_SYMBOL(sof_probe_points_add); /** - * sof_ipc_probe_points_remove - disconnect specified probes - * @sdev: SOF sound device + * sof_probe_points_remove - disconnect specified probes + * @cdev: SOF client device * @buffer_id: List of probe points to disconnect * @num_buffer_id: Number of elements in @desc * * Removes previously connected probes from list of active probe * points and frees all resources on DSP side. */ -int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id) +int sof_probe_points_remove(struct sof_client_dev *cdev, + unsigned int *buffer_id, size_t num_buffer_id) { struct sof_ipc_probe_point_remove_params *msg; struct sof_ipc_reply reply; @@ -282,9 +288,9 @@ int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message(cdev, msg->hdr.cmd, msg, msg->hdr.size, + &reply, sizeof(reply)); kfree(msg); return ret; } -EXPORT_SYMBOL(sof_ipc_probe_points_remove); +EXPORT_SYMBOL(sof_probe_points_remove); diff --git a/sound/soc/sof/probe.h b/sound/soc/sof/probe.h index 5e159ab239fa97..42e802b7e7fc1d 100644 --- a/sound/soc/sof/probe.h +++ b/sound/soc/sof/probe.h @@ -12,8 +12,8 @@ #define __SOF_PROBE_H #include - -struct snd_sof_dev; +#include +#include "sof-client.h" #define SOF_PROBE_INVALID_NODE_ID UINT_MAX @@ -66,20 +66,27 @@ struct sof_ipc_probe_point_remove_params { unsigned int buffer_id[]; } __packed; -int sof_ipc_probe_init(struct snd_sof_dev *sdev, - u32 stream_tag, size_t buffer_size); -int sof_ipc_probe_deinit(struct snd_sof_dev *sdev); -int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev, - struct sof_probe_dma **dma, size_t *num_dma); -int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev, - struct sof_probe_dma *dma, size_t num_dma); -int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev, - unsigned int *stream_tag, size_t num_stream_tag); -int sof_ipc_probe_points_info(struct snd_sof_dev *sdev, - struct sof_probe_point_desc **desc, size_t *num_desc); -int sof_ipc_probe_points_add(struct snd_sof_dev *sdev, - struct sof_probe_point_desc *desc, size_t num_desc); -int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev, - unsigned int *buffer_id, size_t num_buffer_id); +int sof_probe_init(struct sof_client_dev *cdev, u32 stream_tag, + size_t buffer_size); +int sof_probe_deinit(struct sof_client_dev *cdev); +int sof_probe_dma_info(struct sof_client_dev *cdev, + struct sof_probe_dma **dma, size_t *num_dma); +int sof_probe_dma_add(struct sof_client_dev *cdev, + struct sof_probe_dma *dma, size_t num_dma); +int sof_probe_dma_remove(struct sof_client_dev *cdev, + unsigned int *stream_tag, size_t num_stream_tag); +int sof_probe_points_info(struct sof_client_dev *cdev, + struct sof_probe_point_desc **desc, + size_t *num_desc); +int sof_probe_points_add(struct sof_client_dev *cdev, + struct sof_probe_point_desc *desc, + size_t num_desc); +int sof_probe_points_remove(struct sof_client_dev *cdev, + unsigned int *buffer_id, size_t num_buffer_id); + +struct sof_probes_data { + struct dentry *dfs_root; + unsigned int extractor_stream_tag; +}; #endif diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c new file mode 100644 index 00000000000000..838aaa5ea17999 --- /dev/null +++ b/sound/soc/sof/sof-client.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan +// + +#include +#include +#include +#include +#include +#include +#include "ops.h" +#include "sof-client.h" +#include "sof-priv.h" + +static void sof_client_auxdev_release(struct device *dev) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + + kfree(cdev); +} + +static struct sof_client_dev *sof_client_dev_alloc(struct snd_sof_dev *sdev, const char *name, + u32 id) +{ + struct sof_client_dev *cdev; + struct auxiliary_device *auxdev; + int ret; + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return ERR_PTR(-ENOMEM); + + cdev->sdev = sdev; + auxdev = &cdev->auxdev; + auxdev->name = name; + auxdev->dev.parent = sdev->dev; + auxdev->dev.release = sof_client_auxdev_release; + auxdev->id = id; + + ret = auxiliary_device_init(auxdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to initialize client dev %s\n", name); + goto err_free; + } + + return cdev; + +err_free: + kfree(cdev); + return NULL; +} + +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id) +{ + struct sof_client_dev *cdev; + int ret; + + cdev = sof_client_dev_alloc(sdev, name, id); + if (IS_ERR_OR_NULL(cdev)) + return PTR_ERR(cdev); + + ret = auxiliary_device_add(&cdev->auxdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to add client dev %s\n", name); + /* cdev will be freed when the release callback is invoked through put_device() */ + auxiliary_device_uninit(&cdev->auxdev); + return ret; + } + + /* add to list of SOF client devices */ + mutex_lock(&sdev->client_mutex); + list_add(&cdev->list, &sdev->client_list); + mutex_unlock(&sdev->client_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_register, SND_SOC_SOF_CLIENT); + +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id) +{ + struct sof_client_dev *cdev, *_cdev; + + mutex_lock(&sdev->client_mutex); + + /* cdev will be freed when the release callback for the auxiliary device is invoked */ + list_for_each_entry_safe(cdev, _cdev, &sdev->client_list, list) { + if (!strcmp(cdev->auxdev.name, name) && cdev->auxdev.id == id) { + auxiliary_device_delete(&cdev->auxdev); + auxiliary_device_uninit(&cdev->auxdev); + break; + } + } + + mutex_unlock(&sdev->client_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_unregister, SND_SOC_SOF_CLIENT); + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, u32 header, void *msg_data, + size_t msg_bytes, void *reply_data, size_t reply_bytes) +{ + return sof_ipc_tx_message(cdev->sdev->ipc, header, msg_data, msg_bytes, + reply_data, reply_bytes); +} +EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev) +{ + return cdev->sdev->debugfs_root; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_debugfs_root, SND_SOC_SOF_CLIENT); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES_CLIENT) +int sof_client_probe_compr_assign(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + return snd_sof_probe_compr_assign(cdev->sdev, cstream, dai); +} +EXPORT_SYMBOL_NS_GPL(sof_client_probe_compr_assign, SND_SOC_SOF_CLIENT); + +int sof_client_probe_compr_free(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + return snd_sof_probe_compr_free(cdev->sdev, cstream, dai); +} +EXPORT_SYMBOL_NS_GPL(sof_client_probe_compr_free, SND_SOC_SOF_CLIENT); + +int sof_client_probe_compr_set_params(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) +{ + return snd_sof_probe_compr_set_params(cdev->sdev, cstream, params, dai); +} +EXPORT_SYMBOL_NS_GPL(sof_client_probe_compr_set_params, SND_SOC_SOF_CLIENT); + +int sof_client_probe_compr_trigger(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + return snd_sof_probe_compr_trigger(cdev->sdev, cstream, cmd, dai); +} +EXPORT_SYMBOL_NS_GPL(sof_client_probe_compr_trigger, SND_SOC_SOF_CLIENT); + +int sof_client_probe_compr_pointer(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) +{ + return snd_sof_probe_compr_pointer(cdev->sdev, cstream, tstamp, dai); +} +EXPORT_SYMBOL_NS_GPL(sof_client_probe_compr_pointer, SND_SOC_SOF_CLIENT); +#endif + +/* + * DMA buffer alloc fails when using the client device. Use the SOF core device instead. + * This will be needed for clients other than the probes client device as well. + */ +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev) +{ + return cdev->sdev->dev; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_dma_dev, SND_SOC_SOF_CLIENT); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h new file mode 100644 index 00000000000000..be80053068c9ef --- /dev/null +++ b/sound/soc/sof/sof-client.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOUND_SOC_SOF_CLIENT_H +#define __SOUND_SOC_SOF_CLIENT_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SOF_CLIENT_PROBE_TIMEOUT_MS 2000 + +struct snd_sof_dev; + +/* SOF client device */ +struct sof_client_dev { + struct auxiliary_device auxdev; + struct snd_sof_dev *sdev; + struct list_head list; /* item in SOF core client dev list */ + void *data; +}; + +/* client-specific ops, all optional */ +struct sof_client_ops { + int (*client_ipc_rx)(struct sof_client_dev *cdev, u32 msg_cmd); +}; + +struct sof_client_drv { + const struct sof_client_ops ops; + struct auxiliary_driver auxiliary_drv; +}; + +#define auxiliary_dev_to_sof_client_dev(auxiliary_dev) \ + container_of(auxiliary_dev, struct sof_client_dev, auxdev) + +static inline int sof_client_drv_register(struct sof_client_drv *drv) +{ + return auxiliary_driver_register(&drv->auxiliary_drv); +} + +static inline void sof_client_drv_unregister(struct sof_client_drv *drv) +{ + auxiliary_driver_unregister(&drv->auxiliary_drv); +} + +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id); +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id); + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, u32 header, void *msg_data, + size_t msg_bytes, void *reply_data, size_t reply_bytes); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev); +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES_CLIENT) +int sof_client_probe_compr_assign(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); +int sof_client_probe_compr_free(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); +int sof_client_probe_compr_set_params(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai); +int sof_client_probe_compr_trigger(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai); +int sof_client_probe_compr_pointer(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai); +#endif + +/** + * module_sof_client_driver() - Helper macro for registering an SOF Client + * driver + * @__sof_client_driver: SOF client driver struct + * + * Helper macro for SOF client drivers which do not do anything special in + * module init/exit. This eliminates a lot of boilerplate. Each module may only + * use this macro once, and calling it replaces module_init() and module_exit() + */ +#define module_sof_client_driver(__sof_client_driver) \ + module_driver(__sof_client_driver, sof_client_drv_register, sof_client_drv_unregister) + +#endif diff --git a/sound/soc/sof/sof-ipc-test-client.c b/sound/soc/sof/sof-ipc-test-client.c new file mode 100644 index 00000000000000..b4d803b9139b2a --- /dev/null +++ b/sound/soc/sof/sof-ipc-test-client.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sof-client.h" + +#define MAX_IPC_FLOOD_DURATION_MS 1000 +#define MAX_IPC_FLOOD_COUNT 10000 +#define IPC_FLOOD_TEST_RESULT_LEN 512 +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +struct sof_ipc_client_data { + struct dentry *dfs_root; + char *buf; +}; + +/* + * helper function to perform the flood test. Only one of the two params, ipc_duration_ms + * or ipc_count, will be non-zero and will determine the type of test + */ +static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev, unsigned long ipc_duration_ms, + unsigned long ipc_count) +{ + struct sof_ipc_client_data *ipc_client_data = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply reply; + u64 min_response_time = U64_MAX; + u64 avg_response_time = 0; + u64 max_response_time = 0; + ktime_t cur; + ktime_t test_end; + int i = 0; + int ret = 0; + bool end_test = false; + + /* configure test IPC */ + hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; + hdr.size = sizeof(hdr); + + /* set test end time for duration flood test */ + test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; + + /* send test IPC's */ + do { + ktime_t start; + u64 ipc_response_time; + + start = ktime_get(); + ret = sof_client_ipc_tx_message(cdev, hdr.cmd, &hdr, hdr.size, &reply, + sizeof(reply)); + if (ret < 0) + break; + cur = ktime_get(); + + i++; + + /* compute min and max response times */ + ipc_response_time = ktime_to_ns(ktime_sub(cur, start)); + min_response_time = min(min_response_time, ipc_response_time); + max_response_time = max(max_response_time, ipc_response_time); + + /* sum up response times */ + avg_response_time += ipc_response_time; + + /* end test? */ + if (ipc_count && i == ipc_count) + end_test = true; + else if (ipc_duration_ms && (ktime_to_ns(cur) >= test_end)) + end_test = true; + + } while (!end_test); + + if (ret < 0) + return ret; + + /* return if the first IPC fails */ + if (!i) + return ret; + + /* compute average response time */ + DIV_ROUND_CLOSEST(avg_response_time, i); + + /* clear previous test output */ + memset(ipc_client_data->buf, 0, IPC_FLOOD_TEST_RESULT_LEN); + + if (!ipc_count) { + dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms); + snprintf(ipc_client_data->buf, IPC_FLOOD_TEST_RESULT_LEN, + "IPC Flood test duration: %lums\n", ipc_duration_ms); + } + + dev_dbg(dev, + "IPC Flood count: %d, Avg response time: %lluns\n", i, avg_response_time); + dev_dbg(dev, "Max response time: %lluns\n", max_response_time); + dev_dbg(dev, "Min response time: %lluns\n", min_response_time); + + /* format output string and save test results */ + snprintf(ipc_client_data->buf + strlen(ipc_client_data->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(ipc_client_data->buf), + "IPC Flood count: %d\nAvg response time: %lluns\n", i, avg_response_time); + + snprintf(ipc_client_data->buf + strlen(ipc_client_data->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(ipc_client_data->buf), + "Max response time: %lluns\nMin response time: %lluns\n", + max_response_time, min_response_time); + + return ret; +} + +/* + * Writing to the debugfs entry initiates the IPC flood test based on + * the IPC count or the duration specified by the user. + */ +static ssize_t sof_ipc_dfsentry_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct dentry *dentry = file->f_path.dentry; + struct sof_client_dev *cdev = file->private_data; + struct device *dev = &cdev->auxdev.dev; + unsigned long ipc_duration_ms = 0; + bool flood_duration_test; + unsigned long ipc_count = 0; + char *string; + size_t size; + int err; + int ret; + + string = kzalloc(count, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + + flood_duration_test = !strcmp(dentry->d_name.name, "ipc_flood_duration_ms"); + + /* limit max duration/ipc count for flood test */ + if (flood_duration_test) { + ret = kstrtoul(string, 0, &ipc_duration_ms); + if (ret < 0) + goto out; + + if (!ipc_duration_ms) { + ret = size; + goto out; + } + + ipc_duration_ms = min_t(unsigned long, ipc_duration_ms, MAX_IPC_FLOOD_DURATION_MS); + } else { + ret = kstrtoul(string, 0, &ipc_count); + if (ret < 0) + goto out; + + if (!ipc_count) { + ret = size; + goto out; + } + + ipc_count = min_t(unsigned long, ipc_count, MAX_IPC_FLOOD_COUNT); + } + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "error: debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto out; + } + + ret = sof_debug_ipc_flood_test(cdev, ipc_duration_ms, ipc_count); + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) { + ret = err; + goto out; + } + + /* return size if test is successful */ + if (ret >= 0) + ret = size; +out: + kfree(string); + return ret; +} + +/* return the result of the last IPC flood test */ +static ssize_t sof_ipc_dfsentry_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_ipc_client_data *ipc_client_data = cdev->data; + size_t size_ret; + + if (*ppos) + return 0; + + /* return results of the last IPC test */ + count = min_t(size_t, count, strlen(ipc_client_data->buf)); + size_ret = copy_to_user(buffer, ipc_client_data->buf, count); + if (size_ret) + return -EFAULT; + + *ppos += count; + return count; +} + +static const struct file_operations sof_ipc_dfs_fops = { + .open = simple_open, + .read = sof_ipc_dfsentry_read, + .llseek = default_llseek, + .write = sof_ipc_dfsentry_write, +}; + +/* + * The IPC test client creates a couple of debugfs entries that will be used + * flood tests. Users can write to these entries to execute the IPC flood test + * by specifying either the number of IPCs to flood the DSP with or the duration + * (in ms) for which the DSP should be flooded with test IPCs. At the + * end of each test, the average, min and max response times are reported back. + * The results of the last flood test can be accessed by reading the debugfs + * entries. + */ +static int sof_ipc_test_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_ipc_client_data *ipc_client_data; + + /* allocate memory for client data */ + ipc_client_data = devm_kzalloc(&auxdev->dev, sizeof(*ipc_client_data), GFP_KERNEL); + if (!ipc_client_data) + return -ENOMEM; + + ipc_client_data->buf = devm_kzalloc(&auxdev->dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL); + if (!ipc_client_data->buf) + return -ENOMEM; + + cdev->data = ipc_client_data; + + /* create debugfs root folder with device name under parent SOF dir */ + ipc_client_data->dfs_root = debugfs_create_dir(dev_name(&auxdev->dev), + sof_client_get_debugfs_root(cdev)); + + /* create read-write ipc_flood_count debugfs entry */ + debugfs_create_file("ipc_flood_count", 0644, ipc_client_data->dfs_root, + cdev, &sof_ipc_dfs_fops); + + /* create read-write ipc_flood_duration_ms debugfs entry */ + debugfs_create_file("ipc_flood_duration_ms", 0644, ipc_client_data->dfs_root, + cdev, &sof_ipc_dfs_fops); + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(&auxdev->dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&auxdev->dev); + pm_runtime_enable(&auxdev->dev); + pm_runtime_mark_last_busy(&auxdev->dev); + pm_runtime_idle(&auxdev->dev); + + return 0; +} + +static int sof_ipc_test_cleanup(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_ipc_client_data *ipc_client_data = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + debugfs_remove_recursive(ipc_client_data->dfs_root); + + return 0; +} + +static int sof_ipc_test_remove(struct auxiliary_device *auxdev) +{ + return sof_ipc_test_cleanup(auxdev); +} + +static void sof_ipc_test_shutdown(struct auxiliary_device *auxdev) +{ + sof_ipc_test_cleanup(auxdev); +} + +static const struct auxiliary_device_id sof_ipc_auxbus_id_table[] = { + { .name = "snd_sof_client.ipc_test" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_ipc_auxbus_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus type are enough to + * ensure that the parent SOF device resumes to bring the DSP back to D0. + * driver name will be set based on KBUILD_MODNAME. + */ +static struct sof_client_drv sof_ipc_test_client_drv = { + .auxiliary_drv = { + .id_table = sof_ipc_auxbus_id_table, + .probe = sof_ipc_test_probe, + .remove = sof_ipc_test_remove, + .shutdown = sof_ipc_test_shutdown, + }, +}; + +module_sof_client_driver(sof_ipc_test_client_drv); + +MODULE_DESCRIPTION("SOF IPC Test Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 0aed2a7ab85809..121c374297fce5 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -50,10 +50,6 @@ extern int sof_core_debug; #define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT) -#define ENABLE_DEBUGFS_CACHEBUF \ - (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ - IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)) - /* So far the primary core on all DSPs has ID 0 */ #define SOF_DSP_PRIMARY_CORE 0 @@ -252,6 +248,10 @@ struct snd_sof_dsp_ops { void (*set_mach_params)(const struct snd_soc_acpi_mach *mach, struct device *dev); /* optional */ + /* client ops */ + int (*register_clients)(struct snd_sof_dev *sdev); /* optional */ + void (*unregister_clients)(struct snd_sof_dev *sdev); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; @@ -297,7 +297,7 @@ struct snd_sof_dfsentry { * or if it is accessible only when the DSP is in D0. */ enum sof_debugfs_access_type access_type; -#if ENABLE_DEBUGFS_CACHEBUF +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) char *cache_buf; /* buffer to cache the contents of debugfs memory */ #endif struct snd_sof_dev *sdev; @@ -425,10 +425,6 @@ struct snd_sof_dev { int ipc_timeout; int boot_timeout; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - unsigned int extractor_stream_tag; -#endif - /* DMA for Trace */ struct snd_dma_buffer dmatb; struct snd_dma_buffer dmatp; @@ -442,6 +438,15 @@ struct snd_sof_dev { bool msi_enabled; + /* + * Used to keep track of registered client devices so that they can be removed when the + * parent SOF module is removed. + */ + struct list_head client_list; + + /* mutex to protect client list */ + struct mutex client_mutex; + void *private; /* core does not touch this */ }; diff --git a/sound/soc/sof/sof-probes-client.c b/sound/soc/sof/sof-probes-client.c new file mode 100644 index 00000000000000..73227af3d3393f --- /dev/null +++ b/sound/soc/sof/sof-probes-client.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: (GPL-2.0-only) +// +// Copyright(c) 2020 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan +// + +#include +#include +#include +#include +#include +#include +#include +#include "compress.h" +#include "probe.h" +#include "sof-client.h" + +#define SOF_PROBES_SUSPEND_DELAY_MS 3000 +/* only extraction supported for now */ +#define SOF_PROBES_NUM_DAI_LINKS 1 + +/** + * strsplit_u32 - Split string into sequence of u32 tokens + * @buf: String to split into tokens. + * @delim: String containing delimiter characters. + * @tkns: Returned u32 sequence pointer. + * @num_tkns: Returned number of tokens obtained. + */ +static int +strsplit_u32(char *buf, const char *delim, u32 **tkns, size_t *num_tkns) +{ + char *s; + u32 *data, *tmp; + size_t count = 0; + size_t cap = 32; + int ret = 0; + + *tkns = NULL; + *num_tkns = 0; + data = kcalloc(cap, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + while ((s = strsep(&buf, delim)) != NULL) { + ret = kstrtouint(s, 0, data + count); + if (ret) + goto exit; + if (++count >= cap) { + cap *= 2; + tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); + if (!tmp) { + ret = -ENOMEM; + goto exit; + } + data = tmp; + } + } + + if (!count) + goto exit; + *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); + if (!(*tkns)) { + ret = -ENOMEM; + goto exit; + } + *num_tkns = count; + +exit: + kfree(data); + return ret; +} + +static int tokenize_input(const char __user *from, size_t count, + loff_t *ppos, u32 **tkns, size_t *num_tkns) +{ + char *buf; + int ret; + + buf = kmalloc(count + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = simple_write_to_buffer(buf, count, ppos, from, count); + if (ret != count) { + ret = ret >= 0 ? -EIO : ret; + goto exit; + } + + buf[count] = '\0'; + ret = strsplit_u32(buf, ",", tkns, num_tkns); +exit: + kfree(buf); + return ret; +} + +static ssize_t probe_points_read(struct file *file, char __user *to, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_data *probes_data = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + size_t num_desc; + int remaining; + char *buf; + int i, ret, err; + + if (probes_data->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "error: debugfs read failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probe_points_info(cdev, &desc, &num_desc); + if (ret < 0) + goto exit; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "error: debugfs read failed to idle %d\n", err); + + for (i = 0; i < num_desc; i++) { + remaining = PAGE_SIZE - strlen(buf); + if (remaining > 0) { + ret = snprintf(buf + strlen(buf), remaining, + "Id: %#010x Purpose: %u Node id: %#x\n", + desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); + if (ret < 0) + goto free_desc; + } else { + break; + } + } + + ret = simple_read_from_buffer(to, count, ppos, buf, strlen(buf)); +free_desc: + kfree(desc); +exit: + kfree(buf); + return ret; +} + +static ssize_t probe_points_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_data *probes_data = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + size_t num_tkns, bytes; + u32 *tkns; + int ret, err; + + if (probes_data->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + bytes = sizeof(*tkns) * num_tkns; + if (!num_tkns || (bytes % sizeof(*desc))) { + ret = -EINVAL; + goto exit; + } + + desc = (struct sof_probe_point_desc *)tkns; + + ret = pm_runtime_get_sync(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "error: debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probe_points_add(cdev, desc, bytes / sizeof(*desc)); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "error: debugfs write failed to idle %d\n", err); +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations probe_points_fops = { + .open = simple_open, + .read = probe_points_read, + .write = probe_points_write, + .llseek = default_llseek, +}; + +static ssize_t +probe_points_remove_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_data *probes_data = cdev->data; + struct device *dev = &cdev->auxdev.dev; + size_t num_tkns; + u32 *tkns; + int ret, err; + + if (probes_data->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); + if (ret < 0) + return ret; + if (!num_tkns) { + ret = -EINVAL; + goto exit; + } + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err_ratelimited(dev, "error: debugfs write failed to resume %d\n", ret); + pm_runtime_put_noidle(dev); + goto exit; + } + + ret = sof_probe_points_remove(cdev, tkns, num_tkns); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "error: debugfs write failed to idle %d\n", err); +exit: + kfree(tkns); + return ret; +} + +static const struct file_operations probe_points_remove_fops = { + .open = simple_open, + .write = probe_points_remove_write, + .llseek = default_llseek, +}; + +struct snd_soc_dai_driver sof_probes_dai_drv[] = { +{ + .name = "Probe Extraction CPU DAI", + .compress_new = snd_soc_new_compress, + .cops = &sof_probe_compr_ops, + .capture = { + .stream_name = "Probe Extraction", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + }, +}, +}; + +static const struct snd_soc_component_driver sof_probes_component = { + .name = "sof-probes-component", + .compress_ops = &sof_probe_compressed_ops, + .module_get_upon_open = 1, +}; + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_card sof_probes_card = { + .name = "sof-probes", + .owner = THIS_MODULE +}; + +static int sof_probes_client_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct snd_soc_dai_link_component platform_component[] = { + { + .name = dev_name(&auxdev->dev), + } + }; + struct snd_soc_card *card = &sof_probes_card; + struct sof_probes_data *probes_client_data; + struct snd_soc_dai_link_component *cpus; + struct snd_soc_dai_link *links; + int ret; + + /* register probes component driver and dai */ + ret = devm_snd_soc_register_component(&auxdev->dev, &sof_probes_component, + sof_probes_dai_drv, ARRAY_SIZE(sof_probes_dai_drv)); + if (ret < 0) { + dev_err(&auxdev->dev, "error: failed to register SOF probes DAI driver %d\n", ret); + return ret; + } + + /* set client data */ + probes_client_data = devm_kzalloc(&auxdev->dev, sizeof(*probes_client_data), GFP_KERNEL); + if (!probes_client_data) + return -ENOMEM; + + probes_client_data->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; + cdev->data = probes_client_data; + + /* create probes debugfs dir under SOF debugfs root dir */ + probes_client_data->dfs_root = debugfs_create_dir("probes", + sof_client_get_debugfs_root(cdev)); + + /* create read-write probes_points debugfs entry */ + debugfs_create_file("probe_points", 0644, probes_client_data->dfs_root, + cdev, &probe_points_fops); + + /* create read-write probe_points_remove debugfs entry */ + debugfs_create_file("probe_points_remove", 0644, probes_client_data->dfs_root, + cdev, &probe_points_remove_fops); + + links = devm_kzalloc(&auxdev->dev, sizeof(*links) * SOF_PROBES_NUM_DAI_LINKS, GFP_KERNEL); + cpus = devm_kzalloc(&auxdev->dev, sizeof(*cpus) * SOF_PROBES_NUM_DAI_LINKS, GFP_KERNEL); + if (!links || !cpus) + return -ENOMEM; + + /* extraction DAI link */ + links[0].name = "Compress Probe Capture"; + links[0].id = 0; + links[0].cpus = &cpus[0]; + links[0].num_cpus = 1; + links[0].cpus->dai_name = "Probe Extraction CPU DAI"; + links[0].codecs = dummy; + links[0].num_codecs = 1; + links[0].platforms = platform_component; + links[0].num_platforms = ARRAY_SIZE(platform_component); + links[0].nonatomic = 1; + + card->num_links = SOF_PROBES_NUM_DAI_LINKS; + card->dai_link = links; + card->dev = &auxdev->dev; + + /* set idle_bias_off to prevent the core from resuming the card->dev */ + card->dapm.idle_bias_off = true; + + snd_soc_card_set_drvdata(&sof_probes_card, cdev); + + ret = devm_snd_soc_register_card(&auxdev->dev, card); + if (ret < 0) { + dev_err(&auxdev->dev, "error: Probes card register failed %d\n", ret); + return ret; + } + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(&auxdev->dev, SOF_PROBES_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&auxdev->dev); + pm_runtime_enable(&auxdev->dev); + pm_runtime_mark_last_busy(&auxdev->dev); + pm_runtime_idle(&auxdev->dev); + + return 0; +} + +static int sof_probes_client_cleanup(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_probes_data *probes_client_data = cdev->data; + + pm_runtime_disable(&auxdev->dev); + debugfs_remove_recursive(probes_client_data->dfs_root); + + return 0; +} + +static int sof_probes_client_remove(struct auxiliary_device *auxdev) +{ + return sof_probes_client_cleanup(auxdev); +} + +static void sof_probes_client_shutdown(struct auxiliary_device *auxdev) +{ + sof_probes_client_cleanup(auxdev); +} + +static const struct auxiliary_device_id sof_probes_auxbus_id_table[] = { + { .name = "snd_sof_client.probes" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_probes_auxbus_id_table); + +/* driver name will be set based on KBUILD_MODNAME */ +static struct sof_client_drv sof_probes_test_client_drv = { + .auxiliary_drv = { + .id_table = sof_probes_auxbus_id_table, + .probe = sof_probes_client_probe, + .remove = sof_probes_client_remove, + .shutdown = sof_probes_client_shutdown, + }, +}; + +module_sof_client_driver(sof_probes_test_client_drv); + +MODULE_DESCRIPTION("SOF Probes Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);