summaryrefslogtreecommitdiffstats
path: root/drivers/peci/request.c
blob: a49eb351cda31001cf2f4737f48fac32dcfc2d9b (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2021 Intel Corporation

#include <linux/bug.h>
#include <linux/export.h>
#include <linux/peci.h>
#include <linux/slab.h>
#include <linux/types.h>

#include <asm/unaligned.h>

#include "internal.h"

#define PECI_GET_DIB_CMD		0xf7
#define  PECI_GET_DIB_WR_LEN		1
#define  PECI_GET_DIB_RD_LEN		8

#define PECI_RDPKGCFG_CMD		0xa1
#define  PECI_RDPKGCFG_WR_LEN		5
#define  PECI_RDPKGCFG_RD_LEN_BASE	1
#define PECI_WRPKGCFG_CMD		0xa5
#define  PECI_WRPKGCFG_WR_LEN_BASE	6
#define  PECI_WRPKGCFG_RD_LEN		1

/* Device Specific Completion Code (CC) Definition */
#define PECI_CC_SUCCESS				0x40
#define PECI_CC_NEED_RETRY			0x80
#define PECI_CC_OUT_OF_RESOURCE			0x81
#define PECI_CC_UNAVAIL_RESOURCE		0x82
#define PECI_CC_INVALID_REQ			0x90
#define PECI_CC_MCA_ERROR			0x91
#define PECI_CC_CATASTROPHIC_MCA_ERROR		0x93
#define PECI_CC_FATAL_MCA_ERROR			0x94
#define PECI_CC_PARITY_ERR_GPSB_OR_PMSB		0x98
#define PECI_CC_PARITY_ERR_GPSB_OR_PMSB_IERR	0x9B
#define PECI_CC_PARITY_ERR_GPSB_OR_PMSB_MCA	0x9C

#define PECI_RETRY_BIT			BIT(0)

#define PECI_RETRY_TIMEOUT		msecs_to_jiffies(700)
#define PECI_RETRY_INTERVAL_MIN		msecs_to_jiffies(1)
#define PECI_RETRY_INTERVAL_MAX		msecs_to_jiffies(128)

static u8 peci_request_data_cc(struct peci_request *req)
{
	return req->rx.buf[0];
}

/**
 * peci_request_status() - return -errno based on PECI completion code
 * @req: the PECI request that contains response data with completion code
 *
 * It can't be used for Ping(), GetDIB() and GetTemp() - for those commands we
 * don't expect completion code in the response.
 *
 * Return: -errno
 */
int peci_request_status(struct peci_request *req)
{
	u8 cc = peci_request_data_cc(req);

	if (cc != PECI_CC_SUCCESS)
		dev_dbg(&req->device->dev, "ret: %#02x\n", cc);

	switch (cc) {
	case PECI_CC_SUCCESS:
		return 0;
	case PECI_CC_NEED_RETRY:
	case PECI_CC_OUT_OF_RESOURCE:
	case PECI_CC_UNAVAIL_RESOURCE:
		return -EAGAIN;
	case PECI_CC_INVALID_REQ:
		return -EINVAL;
	case PECI_CC_MCA_ERROR:
	case PECI_CC_CATASTROPHIC_MCA_ERROR:
	case PECI_CC_FATAL_MCA_ERROR:
	case PECI_CC_PARITY_ERR_GPSB_OR_PMSB:
	case PECI_CC_PARITY_ERR_GPSB_OR_PMSB_IERR:
	case PECI_CC_PARITY_ERR_GPSB_OR_PMSB_MCA:
		return -EIO;
	}

	WARN_ONCE(1, "Unknown PECI completion code: %#02x\n", cc);

	return -EIO;
}
EXPORT_SYMBOL_NS_GPL(peci_request_status, PECI);

static int peci_request_xfer(struct peci_request *req)
{
	struct peci_device *device = req->device;
	struct peci_controller *controller = to_peci_controller(device->dev.parent);
	int ret;

	mutex_lock(&controller->bus_lock);
	ret = controller->ops->xfer(controller, device->addr, req);
	mutex_unlock(&controller->bus_lock);

	return ret;
}

static int peci_request_xfer_retry(struct peci_request *req)
{
	long wait_interval = PECI_RETRY_INTERVAL_MIN;
	struct peci_device *device = req->device;
	struct peci_controller *controller = to_peci_controller(device->dev.parent);
	unsigned long start = jiffies;
	int ret;

	/* Don't try to use it for ping */
	if (WARN_ON(req->tx.len == 0))
		return 0;

	do {
		ret = peci_request_xfer(req);
		if (ret) {
			dev_dbg(&controller->dev, "xfer error: %d\n", ret);
			return ret;
		}

		if (peci_request_status(req) != -EAGAIN)
			return 0;

		/* Set the retry bit to indicate a retry attempt */
		req->tx.buf[1] |= PECI_RETRY_BIT;

		if (schedule_timeout_interruptible(wait_interval))
			return -ERESTARTSYS;

		wait_interval = min_t(long, wait_interval * 2, PECI_RETRY_INTERVAL_MAX);
	} while (time_before(jiffies, start + PECI_RETRY_TIMEOUT));

	dev_dbg(&controller->dev, "request timed out\n");

	return -ETIMEDOUT;
}

/**
 * peci_request_alloc() - allocate &struct peci_requests
 * @device: PECI device to which request is going to be sent
 * @tx_len: TX length
 * @rx_len: RX length
 *
 * Return: A pointer to a newly allocated &struct peci_request on success or NULL otherwise.
 */
struct peci_request *peci_request_alloc(struct peci_device *device, u8 tx_len, u8 rx_len)
{
	struct peci_request *req;

	/*
	 * TX and RX buffers are fixed length members of peci_request, this is
	 * just a warn for developers to make sure to expand the buffers (or
	 * change the allocation method) if we go over the current limit.
	 */
	if (WARN_ON_ONCE(tx_len > PECI_REQUEST_MAX_BUF_SIZE || rx_len > PECI_REQUEST_MAX_BUF_SIZE))
		return NULL;
	/*
	 * PECI controllers that we are using now don't support DMA, this
	 * should be converted to DMA API once support for controllers that do
	 * allow it is added to avoid an extra copy.
	 */
	req = kzalloc(sizeof(*req), GFP_KERNEL);
	if (!req)
		return NULL;

	req->device = device;
	req->tx.len = tx_len;
	req->rx.len = rx_len;

	return req;
}
EXPORT_SYMBOL_NS_GPL(peci_request_alloc, PECI);

/**
 * peci_request_free() - free peci_request
 * @req: the PECI request to be freed
 */
void peci_request_free(struct peci_request *req)
{
	kfree(req);
}
EXPORT_SYMBOL_NS_GPL(peci_request_free, PECI);

struct peci_request *peci_xfer_get_dib(struct peci_device *device)
{
	struct peci_request *req;
	int ret;

	req = peci_request_alloc(device, PECI_GET_DIB_WR_LEN, PECI_GET_DIB_RD_LEN);
	if (!req)
		return ERR_PTR(-ENOMEM);

	req->tx.buf[0] = PECI_GET_DIB_CMD;

	ret = peci_request_xfer(req);
	if (ret) {
		peci_request_free(req);
		return ERR_PTR(ret);
	}

	return req;
}
EXPORT_SYMBOL_NS_GPL(peci_xfer_get_dib, PECI);

static struct peci_request *
__pkg_cfg_read(struct peci_device *device, u8 index, u16 param, u8 len)
{
	struct peci_request *req;
	int ret;

	req = peci_request_alloc(device, PECI_RDPKGCFG_WR_LEN, PECI_RDPKGCFG_RD_LEN_BASE + len);
	if (!req)
		return ERR_PTR(-ENOMEM);

	req->tx.buf[0] = PECI_RDPKGCFG_CMD;
	req->tx.buf[1] = 0;
	req->tx.buf[2] = index;
	put_unaligned_le16(param, &req->tx.buf[3]);

	ret = peci_request_xfer_retry(req);
	if (ret) {
		peci_request_free(req);
		return ERR_PTR(ret);
	}

	return req;
}

u8 peci_request_data_readb(struct peci_request *req)
{
	return req->rx.buf[1];
}
EXPORT_SYMBOL_NS_GPL(peci_request_data_readb, PECI);

u16 peci_request_data_readw(struct peci_request *req)
{
	return get_unaligned_le16(&req->rx.buf[1]);
}
EXPORT_SYMBOL_NS_GPL(peci_request_data_readw, PECI);

u32 peci_request_data_readl(struct peci_request *req)
{
	return get_unaligned_le32(&req->rx.buf[1]);
}
EXPORT_SYMBOL_NS_GPL(peci_request_data_readl, PECI);

u64 peci_request_data_readq(struct peci_request *req)
{
	return get_unaligned_le64(&req->rx.buf[1]);
}
EXPORT_SYMBOL_NS_GPL(peci_request_data_readq, PECI);

u64 peci_request_dib_read(struct peci_request *req)
{
	return get_unaligned_le64(&req->rx.buf[0]);
}
EXPORT_SYMBOL_NS_GPL(peci_request_dib_read, PECI);

#define __read_pkg_config(x, type) \
struct peci_request *peci_xfer_pkg_cfg_##x(struct peci_device *device, u8 index, u16 param) \
{ \
	return __pkg_cfg_read(device, index, param, sizeof(type)); \
} \
EXPORT_SYMBOL_NS_GPL(peci_xfer_pkg_cfg_##x, PECI)

__read_pkg_config(readb, u8);
__read_pkg_config(readw, u16);
__read_pkg_config(readl, u32);
__read_pkg_config(readq, u64);