summaryrefslogtreecommitdiff
path: root/drivers/misc/issei/dma.c
blob: 457d28f31c06e21838c425e36426ce528178d2f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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;
}