From 79a21979a0de3f14ca784bae20cbd21e0f52fdec Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 27 May 2025 17:24:12 +0200 Subject: [PATCH 1/4] ASoC: SOF: ipc4: add a command to start GDB Add an IPC4 message type to start a firmware GDB debugging session. Signed-off-by: Guennadi Liakhovetski --- include/sound/sof/ipc4/header.h | 3 ++- sound/soc/sof/ipc4.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index 15fac532688e28..e5c9fe761bda7e 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -123,8 +123,9 @@ enum sof_ipc4_global_msg { /* Notification (FW to SW driver) */ SOF_IPC4_GLB_NOTIFICATION, - /* 28 .. 31: RESERVED - do not use */ + /* 28 .. 30: RESERVED - do not use */ + SOF_IPC4_GLB_ENTER_GDB = 31, SOF_IPC4_GLB_TYPE_LAST, }; diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index a4a090e6724a63..055df99b43335c 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -164,6 +164,7 @@ static const char * const ipc4_dbg_glb_msg_type[] = { DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_LIBRARY_PREPARE), DBG_IPC4_MSG_TYPE_ENTRY(GLB_INTERNAL_MESSAGE), DBG_IPC4_MSG_TYPE_ENTRY(GLB_NOTIFICATION), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_ENTER_GDB), }; #define DBG_IPC4_NOTIFICATION_TYPE_ENTRY(type) [SOF_IPC4_NOTIFY_##type] = #type From ed85edbeab2aa611bba25ce914d1c6dd160a4c70 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 11 Jun 2025 12:18:24 +0200 Subject: [PATCH 2/4] ASoC: SOF: special-case a part of the debug memory window The debug memory window is split in 4KiB large pages. Page zero is used for slot descriptors, which leaves one or two more free slots for actual useful work. One of them is usually used for mtrace logging, which leaves no free slots on some systems and only one such slot on others. This commit repurposes the free space in page zero after slot descriptors to be used as slot number 16, one higher than the currently highest possible slot number. To improve documentation we move the tabular representation of the window layout from ipc4-mtrace.c to header.h. Signed-off-by: Guennadi Liakhovetski --- include/sound/sof/ipc4/header.h | 41 ++++++++++++++++++++++++++++----- sound/soc/sof/ipc4-mtrace.c | 26 +-------------------- sound/soc/sof/ipc4.c | 9 ++++++-- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/include/sound/sof/ipc4/header.h b/include/sound/sof/ipc4/header.h index e5c9fe761bda7e..4106d3b4cfe890 100644 --- a/include/sound/sof/ipc4/header.h +++ b/include/sound/sof/ipc4/header.h @@ -541,16 +541,45 @@ struct sof_ipc4_notify_resource_data { uint32_t data[6]; } __packed __aligned(4); -#define SOF_IPC4_DEBUG_DESCRIPTOR_SIZE 12 /* 3 x u32 */ - /* - * The debug memory window is divided into 16 slots, and the - * first slot is used as a recorder for the other 15 slots. + * The debug memory window contains up to 16 4kB large pages, where pages one to + * fifteen are used for user slots and page zero contains slot descriptors: + * + * ------------------------ + * | Page0 - descriptors | + * ------------------------ + * | Page1 - slot0 | + * ------------------------ + * | Page2 - slot1 | + * ------------------------ + * | ... | + * ------------------------ + * | Page14 - slot13 | + * ------------------------ + * | Page15 - slot14 | + * ------------------------ + * + * The slot descriptor is: + * u32 res_id; + * u32 type; + * u32 vma; */ + +#define SOF_IPC4_DEBUG_PAGE_SIZE 0x1000 +#define SOF_IPC4_DEBUG_SLOT_SIZE SOF_IPC4_DEBUG_PAGE_SIZE +#define SOF_IPC4_DEBUG_PAGE0_SLOT_OFFSET 1024 + +/* Usable slots 0..14 (page 1..15) */ #define SOF_IPC4_MAX_DEBUG_SLOTS 15 -#define SOF_IPC4_DEBUG_SLOT_SIZE 0x1000 -/* debug log slot types */ +/* + * Descriptor 0-14: slot 0-14 (page 1-15) + * Descriptor 15: partial slot at page 0 + 1024 + */ +#define SOF_IPC4_MAX_DEBUG_DESCS (SOF_IPC4_MAX_DEBUG_SLOTS + 1) +#define SOF_IPC4_DEBUG_DESCRIPTOR_SIZE 12 /* 3 x u32 */ + +/* Debug log slot types */ #define SOF_IPC4_DEBUG_SLOT_UNUSED 0x00000000 #define SOF_IPC4_DEBUG_SLOT_CRITICAL_LOG 0x54524300 /* byte 0: core ID */ #define SOF_IPC4_DEBUG_SLOT_DEBUG_LOG 0x474f4c00 /* byte 0: core ID */ diff --git a/sound/soc/sof/ipc4-mtrace.c b/sound/soc/sof/ipc4-mtrace.c index aa5b78604db69b..8268e4ad793585 100644 --- a/sound/soc/sof/ipc4-mtrace.c +++ b/sound/soc/sof/ipc4-mtrace.c @@ -10,31 +10,7 @@ #include "ipc4-priv.h" /* - * debug info window is organized in 16 (equal sized) pages: - * - * ------------------------ - * | Page0 - descriptors | - * ------------------------ - * | Page1 - slot0 | - * ------------------------ - * | Page2 - slot1 | - * ------------------------ - * | ... | - * ------------------------ - * | Page14 - slot13 | - * ------------------------ - * | Page15 - slot14 | - * ------------------------ - * - * The slot size == page size - * - * The first page contains descriptors for the remaining 15 cores - * The slot descriptor is: - * u32 res_id; - * u32 type; - * u32 vma; - * - * Log buffer slots have the following layout: + * Log buffer debug window slots have the following layout: * u32 host_read_ptr; * u32 dsp_write_ptr; * u8 buffer[]; diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index 055df99b43335c..fed74fa2dd807f 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -586,11 +586,16 @@ size_t sof_ipc4_find_debug_slot_offset_by_type(struct snd_sof_dev *sdev, /* The type is the second u32 in the slot descriptor */ slot_desc_type_offset = sdev->debug_box.offset + sizeof(u32); - for (i = 0; i < SOF_IPC4_MAX_DEBUG_SLOTS; i++) { + for (i = 0; i < SOF_IPC4_MAX_DEBUG_DESCS; i++) { sof_mailbox_read(sdev, slot_desc_type_offset, &type, sizeof(type)); - if (type == slot_type) + if (type == slot_type) { + if (i == SOF_IPC4_MAX_DEBUG_DESCS - 1) + /* Desc 15: see comment on SOF_IPC4_MAX_DEBUG_DESCS in header.h */ + return sdev->debug_box.offset + SOF_IPC4_DEBUG_PAGE0_SLOT_OFFSET; + return sdev->debug_box.offset + (i + 1) * SOF_IPC4_DEBUG_SLOT_SIZE; + } slot_desc_type_offset += SOF_IPC4_DEBUG_DESCRIPTOR_SIZE; } From ec811d45ccb41c08505298fdc26478da37de3e31 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 13 Jun 2025 14:33:50 +0200 Subject: [PATCH 3/4] ASoC: SOF: client: add wrappers for memory window access Add wrappers for mailbox reading and writing and for debug slot offset extraction. Signed-off-by: Guennadi Liakhovetski --- sound/soc/sof/sof-client.c | 21 +++++++++++++++++++++ sound/soc/sof/sof-client.h | 8 ++++++++ 2 files changed, 29 insertions(+) diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index b0802484a2d339..51f8e27fd6e79c 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -393,6 +393,13 @@ struct snd_sof_widget *sof_client_ipc4_find_swidget_by_id(struct sof_client_dev return NULL; } EXPORT_SYMBOL_NS_GPL(sof_client_ipc4_find_swidget_by_id, "SND_SOC_SOF_CLIENT"); + +ssize_t sof_client_ipc4_find_debug_slot_offset_by_type(struct sof_client_dev *cdev, + u32 type) +{ + return sof_ipc4_find_debug_slot_offset_by_type(sof_client_dev_to_sof_dev(cdev), type); +} +EXPORT_SYMBOL_NS_GPL(sof_client_ipc4_find_debug_slot_offset_by_type, "SND_SOC_SOF_CLIENT"); #endif int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) @@ -673,3 +680,17 @@ struct snd_sof_dev *sof_client_dev_to_sof_dev(struct sof_client_dev *cdev) return centry->sdev; } EXPORT_SYMBOL_NS_GPL(sof_client_dev_to_sof_dev, "SND_SOC_SOF_CLIENT"); + +void sof_client_mailbox_read(struct sof_client_dev *cdev, u32 offset, + void *message, size_t bytes) +{ + sof_mailbox_read(sof_client_dev_to_sof_dev(cdev), offset, message, bytes); +} +EXPORT_SYMBOL_NS_GPL(sof_client_mailbox_read, "SND_SOC_SOF_CLIENT"); + +void sof_client_mailbox_write(struct sof_client_dev *cdev, u32 offset, + void *message, size_t bytes) +{ + sof_mailbox_write(sof_client_dev_to_sof_dev(cdev), offset, message, bytes); +} +EXPORT_SYMBOL_NS_GPL(sof_client_mailbox_write, "SND_SOC_SOF_CLIENT"); diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h index 3b02506c03f1ca..e796801570c823 100644 --- a/sound/soc/sof/sof-client.h +++ b/sound/soc/sof/sof-client.h @@ -76,4 +76,12 @@ void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev); enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev); int sof_client_ipc_rx_message(struct sof_client_dev *cdev, void *ipc_msg, void *msg_buf); +void sof_client_mailbox_read(struct sof_client_dev *cdev, u32 offset, + void *message, size_t bytes); +void sof_client_mailbox_write(struct sof_client_dev *cdev, u32 offset, + void *message, size_t bytes); + +ssize_t sof_client_ipc4_find_debug_slot_offset_by_type(struct sof_client_dev *cdev, + u32 type); + #endif /* __SOC_SOF_CLIENT_H */ From f4ba24f6d442b6557ebda76334ec69394cb7f653 Mon Sep 17 00:00:00 2001 From: Noah Klayman Date: Wed, 22 Jun 2022 00:36:40 +0000 Subject: [PATCH 4/4] ASoC: SOF: sof-client: expose Zephyr GDB stub Adds a DebugFS file to communicate with Zephyr's GDB stub. Communication is via ringbuffers in shared SRAM. Both IPC3 and IPC4 are supported. Signed-off-by: Noah Klayman Co-developed-by: Guennadi Liakhovetski Signed-off-by: Guennadi Liakhovetski --- sound/soc/sof/Kconfig | 12 + sound/soc/sof/Makefile | 2 + sound/soc/sof/sof-client-fw-gdb.c | 483 ++++++++++++++++++++++++++++++ sound/soc/sof/sof-client.c | 29 ++ 4 files changed, 526 insertions(+) create mode 100644 sound/soc/sof/sof-client-fw-gdb.c diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 32ffd345e07fc4..893df198543d5a 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -258,6 +258,18 @@ config SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR Say Y if you want to enable the IPC kernel injector. If unsure, select "N". +config SND_SOC_SOF_DEBUG_FW_GDB + tristate "SOF enable Firmware GDB debugging" + select SND_SOC_SOF_CLIENT + help + This enables GDB debugging of the SOF firmware. If selected, this + will make the kernel create a debugfs file, that can be used to + communicate with the firmware GDB stub. When the file is opened, the + kernel sends a command to the firmware to switch to the GDB mode and + to wait for GDB commands. socat can be used to connect that file to a + socket, to which GDB can then connect. + If unsure, select "N". + config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT bool "SOF retain DSP context on any FW exceptions" help diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index fc29fff7b568be..18dea3b4ade4fc 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -35,6 +35,7 @@ endif ifneq ($(CONFIG_SND_SOC_SOF_IPC4),) snd-sof-probes-y += sof-client-probes-ipc4.o endif +snd-sof-fw-gdb-objs := sof-client-fw-gdb.o snd-sof-nocodec-y := nocodec.o @@ -53,6 +54,7 @@ obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-flood-test.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR) += snd-sof-ipc-kernel-injector.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_FW_GDB) += snd-sof-fw-gdb.o obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ diff --git a/sound/soc/sof/sof-client-fw-gdb.c b/sound/soc/sof/sof-client-fw-gdb.c new file mode 100644 index 00000000000000..f30e47e87f68cb --- /dev/null +++ b/sound/soc/sof/sof-client-fw-gdb.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2022 Intel Corporation. +// +// Authors: Noah Klayman +// Guennadi Liakhovetski +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sof-client.h" +#include "sof-priv.h" +#include "ipc4-priv.h" +#include "ops.h" + +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 +#define SOF_FS_GDB_POLL_DELAY_MS 5 + +/* + * We should use a free slot in the debug window, but on Tiger Lake all slots + * are occupied. So we use the reserved space in the first page of the window. + * That page only contains an array of descriptors in the beginning, which + * currently occupies 180 bytes. We leave the first kilobyte reserved for future + * compatibility but we should use a free slot on other platforms with larger + * debug windows. + */ + +union sof_ipc_gdb_message { + struct { + struct sof_ipc_cmd_hdr hdr; + char cmd[]; + } ipc3; + struct { + struct sof_ipc4_msg msg; + char cmd[]; + } ipc4; +} __packed; + +struct sof_fw_gdb_priv { + struct dentry *dfs_file; + enum sof_ipc_type ipc_type; + union sof_ipc_gdb_message *tx_buffer; + struct delayed_work poll_work; + wait_queue_head_t rxq; + wait_queue_head_t txq; + struct mutex mutex; /* Protect the ring buffer */ + struct sof_client_dev *cdev; +}; + +#define DSP_CACHE_LINE_SIZE 64 +/* + * Must match the respective structure in the firmware. Ring buffer "head" and + * "tail" pointers are placed in separate DSP cache lines to guarantee coherency. + */ +#define RING_SIZE (8 * DSP_CACHE_LINE_SIZE) +struct ring { + u32 head; + u8 fill1[DSP_CACHE_LINE_SIZE - sizeof(u32)]; + u32 tail; + u8 fill2[DSP_CACHE_LINE_SIZE - sizeof(u32)]; + u8 data[RING_SIZE]; +} __packed; + +#define RING_TX_OFFSET 0 +#define RING_RX_OFFSET sizeof(struct ring) + +static int sof_fw_gdb_send_message(struct sof_client_dev *cdev) +{ + struct sof_fw_gdb_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + int ret; + + switch (priv->ipc_type) { + case SOF_IPC_TYPE_3: + { + struct sof_ipc_cmd_hdr *hdr = &priv->tx_buffer->ipc3.hdr; + + hdr->cmd = SOF_IPC_GLB_GDB_DEBUG; + hdr->size = sizeof(*hdr); + break; + } + case SOF_IPC_TYPE_4: + { + struct sof_ipc4_msg *msg = &priv->tx_buffer->ipc4.msg; + + msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_ENTER_GDB) | + SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST) | + SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); + break; + } + default: + return -EINVAL; + } + + /* send the message */ + ret = sof_client_ipc_tx_message_no_reply(cdev, priv->tx_buffer); + if (ret < 0) + dev_err(dev, "IPC message send failed: %d\n", ret); + + return ret; +} + +static bool gdb_is_open; + +static int sof_fw_gdb_dfs_open(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + struct device *dev = &cdev->auxdev.dev; + struct sof_fw_gdb_priv *priv = cdev->data; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + if (gdb_is_open) + return -EBUSY; + + ret = debugfs_file_get(file->f_path.dentry); + if (ret < 0) + return ret; + + ret = simple_open(inode, file); + if (ret < 0) { + dev_err(dev, "failed to open %d\n", ret); + goto e_dbgfs; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "debugfs open failed to resume %d\n", ret); + goto e_dbgfs; + } + + ret = sof_client_boot_dsp(cdev); + if (ret < 0) { + dev_err(dev, "debugfs open failed to power on DSP %d\n", ret); + goto e_pm; + } + + /* When file is opened, send GDB init command to FW */ + if (!gdb_is_open) { + ret = sof_fw_gdb_send_message(cdev); + if (ret < 0) { + dev_err(dev, "failed to send IPC %d\n", ret); + goto e_pm; + } + + gdb_is_open = true; + } + + priv->cdev = cdev; + + schedule_delayed_work(&priv->poll_work, msecs_to_jiffies(SOF_FS_GDB_POLL_DELAY_MS)); + + return ret; + +e_pm: + pm_runtime_put_autosuspend(dev); +e_dbgfs: + debugfs_file_put(file->f_path.dentry); + return ret; +} + +static ssize_t sof_fw_gdb_get_ptrs(struct sof_client_dev *cdev, + u32 *head, u32 *tail, size_t offset) +{ + ssize_t slot_offset = sof_client_ipc4_find_debug_slot_offset_by_type(cdev, + SOF_IPC4_DEBUG_SLOT_GDB_STUB); + + if (!slot_offset) + return -ENODEV; + + slot_offset += offset; + + sof_client_mailbox_read(cdev, slot_offset, head, sizeof(*head)); + sof_client_mailbox_read(cdev, slot_offset + offsetof(struct ring, tail), + tail, sizeof(*tail)); + + return slot_offset; +} + +static ssize_t sof_fw_gdb_get_rx_ptrs(struct sof_client_dev *cdev, + u32 *head, u32 *tail) +{ + return sof_fw_gdb_get_ptrs(cdev, head, tail, RING_RX_OFFSET); +} + +static ssize_t sof_debug_read(struct sof_client_dev *cdev, char *buff, size_t bytes) +{ + size_t count = 0; + u32 head, tail; + ssize_t rx_ring_offset = sof_fw_gdb_get_rx_ptrs(cdev, &head, &tail); + unsigned int i; + + if (rx_ring_offset < 0) + return rx_ring_offset; + + if (tail >= RING_SIZE) { + dev_err(&cdev->auxdev.dev, "%s: %#zx head %x beyond buffer limit!\n", + __func__, rx_ring_offset, head); + return -EIO; + } + + for (i = 0; i < bytes; i++) { + if (tail == head) { + /* No Data */ + break; + } + sof_client_mailbox_read(cdev, rx_ring_offset + offsetof(struct ring, data) + tail, + buff + i, sizeof(buff[i])); + tail = (tail + 1) % RING_SIZE; + count++; + } + + /* Read out up to "bytes" bytes from "tail." Update "tail" to the new location */ + sof_client_mailbox_write(cdev, rx_ring_offset + offsetof(struct ring, tail), &tail, + sizeof(tail)); + + return count; +} + +static ssize_t sof_fw_gdb_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_fw_gdb_priv *priv = cdev->data; + char *kbuff = kzalloc(count, GFP_KERNEL); + + if (!kbuff) + return -ENOMEM; + + mutex_lock(&priv->mutex); + + ssize_t count_read = sof_debug_read(cdev, kbuff, count); + + mutex_unlock(&priv->mutex); + + unsigned long remain = copy_to_user(buffer, kbuff, count_read); + + kfree(kbuff); + + if (remain) + return -EFAULT; + + /* + * Since the DSP runs asynchronously to the kernel, we have to wait a + * while for a response. If we return no data, gdb will exit. This keeps + * it listening for future data. + */ + if (count_read == 0) { + if (copy_to_user(buffer, "\0", 1)) + return -EFAULT; + + return 1; + } + + return count_read; +} + +static ssize_t sof_fw_gdb_get_tx_ptrs(struct sof_client_dev *cdev, + u32 *head, u32 *tail) +{ + return sof_fw_gdb_get_ptrs(cdev, head, tail, RING_TX_OFFSET); +} + +static ssize_t sof_debug_write(struct sof_client_dev *cdev, char *message, size_t bytes) +{ + size_t count = 0; + u32 head, tail; + ssize_t tx_ring_offset = sof_fw_gdb_get_tx_ptrs(cdev, &head, &tail); + unsigned int i; + + if (tx_ring_offset < 0) + return tx_ring_offset; + + if (head >= RING_SIZE) { + dev_err(&cdev->auxdev.dev, "%s: %#zx head %x beyond buffer limit!\n", + __func__, tx_ring_offset, head); + return -EIO; + } + + for (i = 0; i < bytes; i++) { + if ((head + 1) % RING_SIZE == tail) + break; + + sof_client_mailbox_write(cdev, tx_ring_offset + offsetof(struct ring, data) + head, + message + i, sizeof(message[i])); + head = (head + 1) % RING_SIZE; + count++; + } + + sof_client_mailbox_write(cdev, tx_ring_offset, &head, sizeof(head)); + + return count; +} + +static ssize_t sof_fw_gdb_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_fw_gdb_priv *priv = cdev->data; + char *kbuff = kzalloc(count + 1, GFP_KERNEL); + + if (!kbuff) + return -ENOMEM; + + unsigned long num_failed = copy_from_user(kbuff, buffer, count); + + if (num_failed == count) { + kfree(kbuff); + return -EFAULT; + } + + mutex_lock(&priv->mutex); + + ssize_t num_written = sof_debug_write(cdev, kbuff, count - num_failed); + + mutex_unlock(&priv->mutex); + + kfree(kbuff); + + return num_written; +}; + +static int sof_fw_gdb_dfs_release(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + struct device *dev = &cdev->auxdev.dev; + struct sof_fw_gdb_priv *priv = cdev->data; + + cancel_delayed_work_sync(&priv->poll_work); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + debugfs_file_put(file->f_path.dentry); + gdb_is_open = false; + + return 0; +} + +static __poll_t sof_fw_gdb_dfs_poll(struct file *file, struct poll_table_struct *wait) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_fw_gdb_priv *priv = cdev->data; + __poll_t mask = 0; + + mutex_lock(&priv->mutex); + u32 head, tail; + + poll_wait(file, &priv->rxq, wait); + poll_wait(file, &priv->txq, wait); + + if (sof_fw_gdb_get_rx_ptrs(cdev, &head, &tail) >= 0 && tail != head) + mask |= POLLIN | POLLRDNORM; + /* readable */ + + if (sof_fw_gdb_get_tx_ptrs(cdev, &head, &tail) >= 0 && (head + 1) % RING_SIZE != tail) + mask |= POLLOUT | POLLWRNORM; + /* writable */ + + mutex_unlock(&priv->mutex); + + return mask; +} + +static const struct file_operations sof_fw_gdb_fops = { + .open = sof_fw_gdb_dfs_open, + .read = sof_fw_gdb_dfs_read, + .write = sof_fw_gdb_dfs_write, + .poll = sof_fw_gdb_dfs_poll, + .llseek = default_llseek, + .release = sof_fw_gdb_dfs_release, + + .owner = THIS_MODULE, +}; + +static void sof_fw_gdb_poll_work(struct work_struct *work) +{ + struct sof_fw_gdb_priv *priv = container_of(work, struct sof_fw_gdb_priv, + poll_work.work); + u32 head, tail; + + mutex_lock(&priv->mutex); + + if (sof_fw_gdb_get_rx_ptrs(priv->cdev, &head, &tail) >= 0 && head != tail) + wake_up_interruptible(&priv->rxq); + + if (sof_fw_gdb_get_tx_ptrs(priv->cdev, &head, &tail) >= 0 && (head + 1) % RING_SIZE != tail) + wake_up_interruptible(&priv->txq); + + mutex_unlock(&priv->mutex); + + schedule_delayed_work(&priv->poll_work, msecs_to_jiffies(SOF_FS_GDB_POLL_DELAY_MS)); +} + +static int sof_fw_gdb_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); + static const struct file_operations *fops; + struct device *dev = &auxdev->dev; + struct sof_fw_gdb_priv *priv; + size_t alloc_size; + + /* allocate memory for client data */ + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ipc_type = sof_client_get_ipc_type(cdev); + alloc_size = sizeof(*priv->tx_buffer); + INIT_DELAYED_WORK(&priv->poll_work, sof_fw_gdb_poll_work); + + priv->tx_buffer = devm_kmalloc(dev, alloc_size, GFP_KERNEL); + if (!priv->tx_buffer) + return -ENOMEM; + + fops = &sof_fw_gdb_fops; + + cdev->data = priv; + mutex_init(&priv->mutex); + init_waitqueue_head(&priv->rxq); + init_waitqueue_head(&priv->txq); + + priv->dfs_file = debugfs_create_file("fw_gdb", 0644, debugfs_root, + cdev, fops); + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_fw_gdb_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_fw_gdb_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + debugfs_remove(priv->dfs_file); +} + +static const struct auxiliary_device_id sof_fw_gdb_client_id_table[] = { + { .name = "snd_sof.fw_gdb" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_fw_gdb_client_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 auxiliary_driver sof_gdb_fw_client_drv = { + .probe = sof_fw_gdb_probe, + .remove = sof_fw_gdb_remove, + + .id_table = sof_fw_gdb_client_id_table, +}; + +module_auxiliary_driver(sof_gdb_fw_client_drv); + +MODULE_DESCRIPTION("SOF IPC FW GDB Client Driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("SND_SOC_SOF_CLIENT"); diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index 51f8e27fd6e79c..03af9efa6eb3bb 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -167,6 +167,25 @@ static inline int sof_register_ipc_kernel_injector(struct snd_sof_dev *sdev) static inline void sof_unregister_ipc_kernel_injector(struct snd_sof_dev *sdev) {} #endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_FW_GDB) +static int sof_register_fw_gdb(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "fw_gdb", 0, NULL, 0); +} + +static void sof_unregister_fw_gdb(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "fw_gdb", 0); +} +#else +static inline int sof_register_fw_gdb(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_fw_gdb(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_FW_GDB */ + int sof_register_clients(struct snd_sof_dev *sdev) { int ret; @@ -193,6 +212,12 @@ int sof_register_clients(struct snd_sof_dev *sdev) goto err_kernel_injector; } + ret = sof_register_fw_gdb(sdev); + if (ret) { + dev_err(sdev->dev, "Firmware GDB client registration failed\n"); + goto err_fw_gdb; + } + /* Platform dependent client device registration */ if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) @@ -203,6 +228,9 @@ int sof_register_clients(struct snd_sof_dev *sdev) sof_unregister_ipc_kernel_injector(sdev); +err_fw_gdb: + sof_unregister_fw_gdb(sdev); + err_kernel_injector: sof_unregister_ipc_msg_injector(sdev); @@ -220,6 +248,7 @@ void sof_unregister_clients(struct snd_sof_dev *sdev) sof_unregister_ipc_kernel_injector(sdev); sof_unregister_ipc_msg_injector(sdev); sof_unregister_ipc_flood_test(sdev); + sof_unregister_fw_gdb(sdev); } int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id,