summaryrefslogtreecommitdiff
path: root/drivers/misc/issei/dma.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/issei/dma.c')
-rw-r--r--drivers/misc/issei/dma.c154
1 files changed, 154 insertions, 0 deletions
diff --git a/drivers/misc/issei/dma.c b/drivers/misc/issei/dma.c
new file mode 100644
index 000000000000..457d28f31c06
--- /dev/null
+++ b/drivers/misc/issei/dma.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/dev_printk.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timekeeping.h>
+
+#include "issei_dev.h"
+#include "hw_msg.h"
+
+static inline size_t __issei_dma_size(const struct issei_dma *dma)
+{
+ return dma->length.h2f + dma->length.f2h + dma->length.ctl;
+}
+
+/**
+ * issei_dmam_setup - setup DMA buffer and clean it
+ * @idev: issei device object
+ *
+ * Return: 0 on success, <0 on failures
+ */
+int issei_dmam_setup(struct issei_device *idev)
+{
+ struct issei_dma *dma = &idev->dma;
+ size_t size;
+
+ size = __issei_dma_size(dma);
+ if (!size)
+ return -EINVAL;
+
+ if (!dma->vaddr)
+ dma->vaddr = dmam_alloc_coherent(idev->parent, size, &dma->daddr,
+ GFP_KERNEL | __GFP_ZERO);
+ if (dma->vaddr)
+ memset(dma->vaddr, 0, size);
+ return dma->vaddr ? 0 : -ENOMEM;
+}
+
+static inline struct control_buffer *__dma_get_ctl_buf(struct issei_dma *dma)
+{
+ return dma->vaddr + dma->length.h2f + dma->length.f2h;
+}
+
+static bool __issei_dma_is_read_busy(struct issei_dma *dma)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(dma);
+
+ return ctl->f2h_counter_wr != ctl->f2h_counter_rd;
+}
+
+static bool __issei_dma_is_write_busy(struct issei_dma *dma)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(dma);
+
+ return ctl->h2f_counter_wr != ctl->h2f_counter_rd;
+}
+
+static void __issei_dma_read_finalize(struct issei_device *idev)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma);
+
+ dev_dbg(&idev->dev, "ctl->f2h_counter_rd %u\n", ctl->f2h_counter_rd);
+ /* No need to check overflow - the firmware counters overflow the same way */
+ ctl->f2h_counter_rd++;
+}
+
+static void __issei_dma_write_finalize(struct issei_device *idev)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma);
+
+ dev_dbg(&idev->dev, "ctl->h2f_counter_wr %u\n", ctl->h2f_counter_wr);
+ /* No need to check overflow - the firmware counters overflow the same way */
+ ctl->h2f_counter_wr++;
+}
+
+/**
+ * issei_dma_write - write data package to DMA
+ * @idev: issei device object
+ * @data: data atructure
+ *
+ * Return: 0 on success, <0 on failures
+ */
+int issei_dma_write(struct issei_device *idev, const struct issei_dma_data *data)
+{
+ u8 *write_buf = idev->dma.vaddr;
+ struct ham_message_header *hdr = (struct ham_message_header *)write_buf;
+
+ if (data->length > idev->dma.length.h2f - sizeof(*hdr)) {
+ dev_err(&idev->dev, "Message is too big\n");
+ return -EMSGSIZE;
+ }
+
+ if (__issei_dma_is_write_busy(&idev->dma)) {
+ if (ktime_ms_delta(ktime_get(), idev->last_write_ts) > ISSEI_WRITE_TIMEOUT_MSEC) {
+ dev_err(&idev->dev, "Write stuck in queue\n");
+ return -EIO;
+ }
+ dev_info(&idev->dev, "Write is busy\n");
+ return -EBUSY;
+ }
+
+ hdr->length = data->length;
+ hdr->fw_id = data->fw_id;
+ hdr->host_id = data->host_id;
+ hdr->flags = data->flags;
+ hdr->status = data->status;
+ hdr->reserved = 0;
+
+ memcpy(write_buf + sizeof(*hdr), data->buf, data->length);
+
+ __issei_dma_write_finalize(idev);
+ idev->last_write_ts = ktime_get();
+ return 0;
+}
+
+/**
+ * issei_dma_read - read data package from DMA
+ * @idev: issei device object
+ * @data: data atructure
+ *
+ * Return: %0 on success, <0 on failures
+ */
+int issei_dma_read(struct issei_device *idev, struct issei_dma_data *data)
+{
+ u8 *read_buf = idev->dma.vaddr + idev->dma.length.h2f;
+ struct ham_message_header *hdr = (struct ham_message_header *)read_buf;
+
+ if (!__issei_dma_is_read_busy(&idev->dma)) {
+ dev_dbg(&idev->dev, "Nothing to read\n");
+ return -ENODATA;
+ }
+
+ dev_dbg(&idev->dev, "Reading header\n");
+ data->length = hdr->length;
+ data->fw_id = hdr->fw_id;
+ data->host_id = hdr->host_id;
+ data->flags = hdr->flags;
+ data->status = hdr->status;
+
+ if (data->length > idev->dma.length.f2h - sizeof(*hdr)) {
+ dev_err(&idev->dev, "Message length %u is bigger than buffer %zu\n",
+ data->length, idev->dma.length.f2h - sizeof(*hdr));
+ return -EIO;
+ }
+
+ dev_dbg(&idev->dev, "Reading data (size %u)\n", data->length);
+ data->buf = kmemdup(read_buf + sizeof(*hdr), data->length, GFP_KERNEL);
+ if (!data->buf)
+ return -ENOMEM;
+ __issei_dma_read_finalize(idev);
+ return 0;
+}