blob: 457d28f31c06e21838c425e36426ce528178d2f2 [file] [edit]
// 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;
}