| // 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; | |
| } |