xref: /linux/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c (revision ec7174f637d75abe5ada8482b9947898db231cd2)
155ab48b4SAndrew Jeffery // SPDX-License-Identifier: GPL-2.0
255ab48b4SAndrew Jeffery /*
355ab48b4SAndrew Jeffery  * Copyright (c) 2015-2018, Intel Corporation.
455ab48b4SAndrew Jeffery  */
555ab48b4SAndrew Jeffery 
655ab48b4SAndrew Jeffery #define pr_fmt(fmt) "kcs-bmc: " fmt
755ab48b4SAndrew Jeffery 
855ab48b4SAndrew Jeffery #include <linux/errno.h>
955ab48b4SAndrew Jeffery #include <linux/io.h>
1055ab48b4SAndrew Jeffery #include <linux/ipmi_bmc.h>
11d4e7ac68SAndrew Jeffery #include <linux/list.h>
12d4e7ac68SAndrew Jeffery #include <linux/miscdevice.h>
1355ab48b4SAndrew Jeffery #include <linux/module.h>
14d4e7ac68SAndrew Jeffery #include <linux/mutex.h>
1555ab48b4SAndrew Jeffery #include <linux/platform_device.h>
1655ab48b4SAndrew Jeffery #include <linux/poll.h>
1755ab48b4SAndrew Jeffery #include <linux/sched.h>
1855ab48b4SAndrew Jeffery #include <linux/slab.h>
1955ab48b4SAndrew Jeffery 
20d4e7ac68SAndrew Jeffery #include "kcs_bmc_client.h"
21d4e7ac68SAndrew Jeffery 
22d4e7ac68SAndrew Jeffery /* Different phases of the KCS BMC module.
23d4e7ac68SAndrew Jeffery  *  KCS_PHASE_IDLE:
24d4e7ac68SAndrew Jeffery  *            BMC should not be expecting nor sending any data.
25d4e7ac68SAndrew Jeffery  *  KCS_PHASE_WRITE_START:
26d4e7ac68SAndrew Jeffery  *            BMC is receiving a WRITE_START command from system software.
27d4e7ac68SAndrew Jeffery  *  KCS_PHASE_WRITE_DATA:
28d4e7ac68SAndrew Jeffery  *            BMC is receiving a data byte from system software.
29d4e7ac68SAndrew Jeffery  *  KCS_PHASE_WRITE_END_CMD:
30d4e7ac68SAndrew Jeffery  *            BMC is waiting a last data byte from system software.
31d4e7ac68SAndrew Jeffery  *  KCS_PHASE_WRITE_DONE:
32d4e7ac68SAndrew Jeffery  *            BMC has received the whole request from system software.
33d4e7ac68SAndrew Jeffery  *  KCS_PHASE_WAIT_READ:
34d4e7ac68SAndrew Jeffery  *            BMC is waiting the response from the upper IPMI service.
35d4e7ac68SAndrew Jeffery  *  KCS_PHASE_READ:
36d4e7ac68SAndrew Jeffery  *            BMC is transferring the response to system software.
37d4e7ac68SAndrew Jeffery  *  KCS_PHASE_ABORT_ERROR1:
38d4e7ac68SAndrew Jeffery  *            BMC is waiting error status request from system software.
39d4e7ac68SAndrew Jeffery  *  KCS_PHASE_ABORT_ERROR2:
40d4e7ac68SAndrew Jeffery  *            BMC is waiting for idle status afer error from system software.
41d4e7ac68SAndrew Jeffery  *  KCS_PHASE_ERROR:
42d4e7ac68SAndrew Jeffery  *            BMC has detected a protocol violation at the interface level.
43d4e7ac68SAndrew Jeffery  */
44d4e7ac68SAndrew Jeffery enum kcs_ipmi_phases {
45d4e7ac68SAndrew Jeffery 	KCS_PHASE_IDLE,
46d4e7ac68SAndrew Jeffery 
47d4e7ac68SAndrew Jeffery 	KCS_PHASE_WRITE_START,
48d4e7ac68SAndrew Jeffery 	KCS_PHASE_WRITE_DATA,
49d4e7ac68SAndrew Jeffery 	KCS_PHASE_WRITE_END_CMD,
50d4e7ac68SAndrew Jeffery 	KCS_PHASE_WRITE_DONE,
51d4e7ac68SAndrew Jeffery 
52d4e7ac68SAndrew Jeffery 	KCS_PHASE_WAIT_READ,
53d4e7ac68SAndrew Jeffery 	KCS_PHASE_READ,
54d4e7ac68SAndrew Jeffery 
55d4e7ac68SAndrew Jeffery 	KCS_PHASE_ABORT_ERROR1,
56d4e7ac68SAndrew Jeffery 	KCS_PHASE_ABORT_ERROR2,
57d4e7ac68SAndrew Jeffery 	KCS_PHASE_ERROR
58d4e7ac68SAndrew Jeffery };
59d4e7ac68SAndrew Jeffery 
60d4e7ac68SAndrew Jeffery /* IPMI 2.0 - Table 9-4, KCS Interface Status Codes */
61d4e7ac68SAndrew Jeffery enum kcs_ipmi_errors {
62d4e7ac68SAndrew Jeffery 	KCS_NO_ERROR                = 0x00,
63d4e7ac68SAndrew Jeffery 	KCS_ABORTED_BY_COMMAND      = 0x01,
64d4e7ac68SAndrew Jeffery 	KCS_ILLEGAL_CONTROL_CODE    = 0x02,
65d4e7ac68SAndrew Jeffery 	KCS_LENGTH_ERROR            = 0x06,
66d4e7ac68SAndrew Jeffery 	KCS_UNSPECIFIED_ERROR       = 0xFF
67d4e7ac68SAndrew Jeffery };
68d4e7ac68SAndrew Jeffery 
69d4e7ac68SAndrew Jeffery struct kcs_bmc_ipmi {
70d4e7ac68SAndrew Jeffery 	struct list_head entry;
71d4e7ac68SAndrew Jeffery 
72d4e7ac68SAndrew Jeffery 	struct kcs_bmc_client client;
73d4e7ac68SAndrew Jeffery 
74d4e7ac68SAndrew Jeffery 	spinlock_t lock;
75d4e7ac68SAndrew Jeffery 
76d4e7ac68SAndrew Jeffery 	enum kcs_ipmi_phases phase;
77d4e7ac68SAndrew Jeffery 	enum kcs_ipmi_errors error;
78d4e7ac68SAndrew Jeffery 
79d4e7ac68SAndrew Jeffery 	wait_queue_head_t queue;
80d4e7ac68SAndrew Jeffery 	bool data_in_avail;
81d4e7ac68SAndrew Jeffery 	int  data_in_idx;
82d4e7ac68SAndrew Jeffery 	u8  *data_in;
83d4e7ac68SAndrew Jeffery 
84d4e7ac68SAndrew Jeffery 	int  data_out_idx;
85d4e7ac68SAndrew Jeffery 	int  data_out_len;
86d4e7ac68SAndrew Jeffery 	u8  *data_out;
87d4e7ac68SAndrew Jeffery 
88d4e7ac68SAndrew Jeffery 	struct mutex mutex;
89d4e7ac68SAndrew Jeffery 	u8 *kbuffer;
90d4e7ac68SAndrew Jeffery 
91d4e7ac68SAndrew Jeffery 	struct miscdevice miscdev;
92d4e7ac68SAndrew Jeffery };
9355ab48b4SAndrew Jeffery 
9455ab48b4SAndrew Jeffery #define DEVICE_NAME "ipmi-kcs"
9555ab48b4SAndrew Jeffery 
9655ab48b4SAndrew Jeffery #define KCS_MSG_BUFSIZ    1000
9755ab48b4SAndrew Jeffery 
9855ab48b4SAndrew Jeffery #define KCS_ZERO_DATA     0
9955ab48b4SAndrew Jeffery 
10055ab48b4SAndrew Jeffery /* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
10155ab48b4SAndrew Jeffery #define KCS_STATUS_STATE(state) (state << 6)
10255ab48b4SAndrew Jeffery #define KCS_STATUS_STATE_MASK   GENMASK(7, 6)
10355ab48b4SAndrew Jeffery #define KCS_STATUS_CMD_DAT      BIT(3)
10455ab48b4SAndrew Jeffery #define KCS_STATUS_SMS_ATN      BIT(2)
10555ab48b4SAndrew Jeffery #define KCS_STATUS_IBF          BIT(1)
10655ab48b4SAndrew Jeffery #define KCS_STATUS_OBF          BIT(0)
10755ab48b4SAndrew Jeffery 
10855ab48b4SAndrew Jeffery /* IPMI 2.0 - Table 9-2, KCS Interface State Bits */
10955ab48b4SAndrew Jeffery enum kcs_states {
11055ab48b4SAndrew Jeffery 	IDLE_STATE  = 0,
11155ab48b4SAndrew Jeffery 	READ_STATE  = 1,
11255ab48b4SAndrew Jeffery 	WRITE_STATE = 2,
11355ab48b4SAndrew Jeffery 	ERROR_STATE = 3,
11455ab48b4SAndrew Jeffery };
11555ab48b4SAndrew Jeffery 
11655ab48b4SAndrew Jeffery /* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */
11755ab48b4SAndrew Jeffery #define KCS_CMD_GET_STATUS_ABORT  0x60
11855ab48b4SAndrew Jeffery #define KCS_CMD_WRITE_START       0x61
11955ab48b4SAndrew Jeffery #define KCS_CMD_WRITE_END         0x62
12055ab48b4SAndrew Jeffery #define KCS_CMD_READ_BYTE         0x68
12155ab48b4SAndrew Jeffery 
122d4e7ac68SAndrew Jeffery static inline void set_state(struct kcs_bmc_ipmi *priv, u8 state)
12355ab48b4SAndrew Jeffery {
124d4e7ac68SAndrew Jeffery 	kcs_bmc_update_status(priv->client.dev, KCS_STATUS_STATE_MASK, KCS_STATUS_STATE(state));
12555ab48b4SAndrew Jeffery }
12655ab48b4SAndrew Jeffery 
127d4e7ac68SAndrew Jeffery static void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv)
12855ab48b4SAndrew Jeffery {
129d4e7ac68SAndrew Jeffery 	set_state(priv, ERROR_STATE);
130d4e7ac68SAndrew Jeffery 	kcs_bmc_read_data(priv->client.dev);
131d4e7ac68SAndrew Jeffery 	kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
13255ab48b4SAndrew Jeffery 
133d4e7ac68SAndrew Jeffery 	priv->phase = KCS_PHASE_ERROR;
134d4e7ac68SAndrew Jeffery 	priv->data_in_avail = false;
135d4e7ac68SAndrew Jeffery 	priv->data_in_idx = 0;
13655ab48b4SAndrew Jeffery }
13755ab48b4SAndrew Jeffery 
138d4e7ac68SAndrew Jeffery static void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv)
13955ab48b4SAndrew Jeffery {
140d4e7ac68SAndrew Jeffery 	struct kcs_bmc_device *dev;
14155ab48b4SAndrew Jeffery 	u8 data;
14255ab48b4SAndrew Jeffery 
143d4e7ac68SAndrew Jeffery 	dev = priv->client.dev;
144d4e7ac68SAndrew Jeffery 
145d4e7ac68SAndrew Jeffery 	switch (priv->phase) {
14655ab48b4SAndrew Jeffery 	case KCS_PHASE_WRITE_START:
147d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_WRITE_DATA;
14855ab48b4SAndrew Jeffery 		fallthrough;
14955ab48b4SAndrew Jeffery 
15055ab48b4SAndrew Jeffery 	case KCS_PHASE_WRITE_DATA:
151d4e7ac68SAndrew Jeffery 		if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
152d4e7ac68SAndrew Jeffery 			set_state(priv, WRITE_STATE);
153d4e7ac68SAndrew Jeffery 			kcs_bmc_write_data(dev, KCS_ZERO_DATA);
154d4e7ac68SAndrew Jeffery 			priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
15555ab48b4SAndrew Jeffery 		} else {
156d4e7ac68SAndrew Jeffery 			kcs_bmc_ipmi_force_abort(priv);
157d4e7ac68SAndrew Jeffery 			priv->error = KCS_LENGTH_ERROR;
15855ab48b4SAndrew Jeffery 		}
15955ab48b4SAndrew Jeffery 		break;
16055ab48b4SAndrew Jeffery 
16155ab48b4SAndrew Jeffery 	case KCS_PHASE_WRITE_END_CMD:
162d4e7ac68SAndrew Jeffery 		if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
163d4e7ac68SAndrew Jeffery 			set_state(priv, READ_STATE);
164d4e7ac68SAndrew Jeffery 			priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
165d4e7ac68SAndrew Jeffery 			priv->phase = KCS_PHASE_WRITE_DONE;
166d4e7ac68SAndrew Jeffery 			priv->data_in_avail = true;
167d4e7ac68SAndrew Jeffery 			wake_up_interruptible(&priv->queue);
16855ab48b4SAndrew Jeffery 		} else {
169d4e7ac68SAndrew Jeffery 			kcs_bmc_ipmi_force_abort(priv);
170d4e7ac68SAndrew Jeffery 			priv->error = KCS_LENGTH_ERROR;
17155ab48b4SAndrew Jeffery 		}
17255ab48b4SAndrew Jeffery 		break;
17355ab48b4SAndrew Jeffery 
17455ab48b4SAndrew Jeffery 	case KCS_PHASE_READ:
175d4e7ac68SAndrew Jeffery 		if (priv->data_out_idx == priv->data_out_len)
176d4e7ac68SAndrew Jeffery 			set_state(priv, IDLE_STATE);
17755ab48b4SAndrew Jeffery 
178d4e7ac68SAndrew Jeffery 		data = kcs_bmc_read_data(dev);
17955ab48b4SAndrew Jeffery 		if (data != KCS_CMD_READ_BYTE) {
180d4e7ac68SAndrew Jeffery 			set_state(priv, ERROR_STATE);
181d4e7ac68SAndrew Jeffery 			kcs_bmc_write_data(dev, KCS_ZERO_DATA);
18255ab48b4SAndrew Jeffery 			break;
18355ab48b4SAndrew Jeffery 		}
18455ab48b4SAndrew Jeffery 
185d4e7ac68SAndrew Jeffery 		if (priv->data_out_idx == priv->data_out_len) {
186d4e7ac68SAndrew Jeffery 			kcs_bmc_write_data(dev, KCS_ZERO_DATA);
187d4e7ac68SAndrew Jeffery 			priv->phase = KCS_PHASE_IDLE;
18855ab48b4SAndrew Jeffery 			break;
18955ab48b4SAndrew Jeffery 		}
19055ab48b4SAndrew Jeffery 
191d4e7ac68SAndrew Jeffery 		kcs_bmc_write_data(dev, priv->data_out[priv->data_out_idx++]);
19255ab48b4SAndrew Jeffery 		break;
19355ab48b4SAndrew Jeffery 
19455ab48b4SAndrew Jeffery 	case KCS_PHASE_ABORT_ERROR1:
195d4e7ac68SAndrew Jeffery 		set_state(priv, READ_STATE);
196d4e7ac68SAndrew Jeffery 		kcs_bmc_read_data(dev);
197d4e7ac68SAndrew Jeffery 		kcs_bmc_write_data(dev, priv->error);
198d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_ABORT_ERROR2;
19955ab48b4SAndrew Jeffery 		break;
20055ab48b4SAndrew Jeffery 
20155ab48b4SAndrew Jeffery 	case KCS_PHASE_ABORT_ERROR2:
202d4e7ac68SAndrew Jeffery 		set_state(priv, IDLE_STATE);
203d4e7ac68SAndrew Jeffery 		kcs_bmc_read_data(dev);
204d4e7ac68SAndrew Jeffery 		kcs_bmc_write_data(dev, KCS_ZERO_DATA);
205d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_IDLE;
20655ab48b4SAndrew Jeffery 		break;
20755ab48b4SAndrew Jeffery 
20855ab48b4SAndrew Jeffery 	default:
209d4e7ac68SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(priv);
21055ab48b4SAndrew Jeffery 		break;
21155ab48b4SAndrew Jeffery 	}
21255ab48b4SAndrew Jeffery }
21355ab48b4SAndrew Jeffery 
214d4e7ac68SAndrew Jeffery static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv)
21555ab48b4SAndrew Jeffery {
21655ab48b4SAndrew Jeffery 	u8 cmd;
21755ab48b4SAndrew Jeffery 
218d4e7ac68SAndrew Jeffery 	set_state(priv, WRITE_STATE);
219d4e7ac68SAndrew Jeffery 	kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
22055ab48b4SAndrew Jeffery 
221d4e7ac68SAndrew Jeffery 	cmd = kcs_bmc_read_data(priv->client.dev);
22255ab48b4SAndrew Jeffery 	switch (cmd) {
22355ab48b4SAndrew Jeffery 	case KCS_CMD_WRITE_START:
224d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_WRITE_START;
225d4e7ac68SAndrew Jeffery 		priv->error = KCS_NO_ERROR;
226d4e7ac68SAndrew Jeffery 		priv->data_in_avail = false;
227d4e7ac68SAndrew Jeffery 		priv->data_in_idx = 0;
22855ab48b4SAndrew Jeffery 		break;
22955ab48b4SAndrew Jeffery 
23055ab48b4SAndrew Jeffery 	case KCS_CMD_WRITE_END:
231d4e7ac68SAndrew Jeffery 		if (priv->phase != KCS_PHASE_WRITE_DATA) {
232d4e7ac68SAndrew Jeffery 			kcs_bmc_ipmi_force_abort(priv);
23355ab48b4SAndrew Jeffery 			break;
23455ab48b4SAndrew Jeffery 		}
23555ab48b4SAndrew Jeffery 
236d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_WRITE_END_CMD;
23755ab48b4SAndrew Jeffery 		break;
23855ab48b4SAndrew Jeffery 
23955ab48b4SAndrew Jeffery 	case KCS_CMD_GET_STATUS_ABORT:
240d4e7ac68SAndrew Jeffery 		if (priv->error == KCS_NO_ERROR)
241d4e7ac68SAndrew Jeffery 			priv->error = KCS_ABORTED_BY_COMMAND;
24255ab48b4SAndrew Jeffery 
243d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_ABORT_ERROR1;
244d4e7ac68SAndrew Jeffery 		priv->data_in_avail = false;
245d4e7ac68SAndrew Jeffery 		priv->data_in_idx = 0;
24655ab48b4SAndrew Jeffery 		break;
24755ab48b4SAndrew Jeffery 
24855ab48b4SAndrew Jeffery 	default:
249d4e7ac68SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(priv);
250d4e7ac68SAndrew Jeffery 		priv->error = KCS_ILLEGAL_CONTROL_CODE;
25155ab48b4SAndrew Jeffery 		break;
25255ab48b4SAndrew Jeffery 	}
25355ab48b4SAndrew Jeffery }
25455ab48b4SAndrew Jeffery 
255d4e7ac68SAndrew Jeffery static inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client)
25655ab48b4SAndrew Jeffery {
257d4e7ac68SAndrew Jeffery 	return container_of(client, struct kcs_bmc_ipmi, client);
258faae6e39SAndrew Jeffery }
259faae6e39SAndrew Jeffery 
260faae6e39SAndrew Jeffery static irqreturn_t kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
261faae6e39SAndrew Jeffery {
262d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv;
26355ab48b4SAndrew Jeffery 	u8 status;
264faae6e39SAndrew Jeffery 	int ret;
265faae6e39SAndrew Jeffery 
266d4e7ac68SAndrew Jeffery 	priv = client_to_kcs_bmc_ipmi(client);
267d4e7ac68SAndrew Jeffery 	if (!priv)
268d4e7ac68SAndrew Jeffery 		return IRQ_NONE;
26955ab48b4SAndrew Jeffery 
270d4e7ac68SAndrew Jeffery 	spin_lock(&priv->lock);
27155ab48b4SAndrew Jeffery 
272d4e7ac68SAndrew Jeffery 	status = kcs_bmc_read_status(client->dev);
27355ab48b4SAndrew Jeffery 	if (status & KCS_STATUS_IBF) {
274d4e7ac68SAndrew Jeffery 		if (status & KCS_STATUS_CMD_DAT)
275d4e7ac68SAndrew Jeffery 			kcs_bmc_ipmi_handle_cmd(priv);
27655ab48b4SAndrew Jeffery 		else
277d4e7ac68SAndrew Jeffery 			kcs_bmc_ipmi_handle_data(priv);
27855ab48b4SAndrew Jeffery 
279faae6e39SAndrew Jeffery 		ret = IRQ_HANDLED;
280faae6e39SAndrew Jeffery 	} else {
281faae6e39SAndrew Jeffery 		ret = IRQ_NONE;
28255ab48b4SAndrew Jeffery 	}
28355ab48b4SAndrew Jeffery 
284d4e7ac68SAndrew Jeffery 	spin_unlock(&priv->lock);
28555ab48b4SAndrew Jeffery 
28655ab48b4SAndrew Jeffery 	return ret;
28755ab48b4SAndrew Jeffery }
28855ab48b4SAndrew Jeffery 
289faae6e39SAndrew Jeffery static const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
290faae6e39SAndrew Jeffery 	.event = kcs_bmc_ipmi_event,
291faae6e39SAndrew Jeffery };
292faae6e39SAndrew Jeffery 
293d4e7ac68SAndrew Jeffery static inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp)
29455ab48b4SAndrew Jeffery {
295d4e7ac68SAndrew Jeffery 	return container_of(filp->private_data, struct kcs_bmc_ipmi, miscdev);
29655ab48b4SAndrew Jeffery }
29755ab48b4SAndrew Jeffery 
29855ab48b4SAndrew Jeffery static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
29955ab48b4SAndrew Jeffery {
300d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
30155ab48b4SAndrew Jeffery 
302d4e7ac68SAndrew Jeffery 	return kcs_bmc_enable_device(priv->client.dev, &priv->client);
30355ab48b4SAndrew Jeffery }
30455ab48b4SAndrew Jeffery 
30555ab48b4SAndrew Jeffery static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
30655ab48b4SAndrew Jeffery {
307d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
30855ab48b4SAndrew Jeffery 	__poll_t mask = 0;
30955ab48b4SAndrew Jeffery 
310d4e7ac68SAndrew Jeffery 	poll_wait(filp, &priv->queue, wait);
31155ab48b4SAndrew Jeffery 
312d4e7ac68SAndrew Jeffery 	spin_lock_irq(&priv->lock);
313d4e7ac68SAndrew Jeffery 	if (priv->data_in_avail)
31455ab48b4SAndrew Jeffery 		mask |= EPOLLIN;
315d4e7ac68SAndrew Jeffery 	spin_unlock_irq(&priv->lock);
31655ab48b4SAndrew Jeffery 
31755ab48b4SAndrew Jeffery 	return mask;
31855ab48b4SAndrew Jeffery }
31955ab48b4SAndrew Jeffery 
32055ab48b4SAndrew Jeffery static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
32155ab48b4SAndrew Jeffery 			    size_t count, loff_t *ppos)
32255ab48b4SAndrew Jeffery {
323d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
32455ab48b4SAndrew Jeffery 	bool data_avail;
32555ab48b4SAndrew Jeffery 	size_t data_len;
32655ab48b4SAndrew Jeffery 	ssize_t ret;
32755ab48b4SAndrew Jeffery 
32855ab48b4SAndrew Jeffery 	if (!(filp->f_flags & O_NONBLOCK))
329d4e7ac68SAndrew Jeffery 		wait_event_interruptible(priv->queue,
330d4e7ac68SAndrew Jeffery 					 priv->data_in_avail);
33155ab48b4SAndrew Jeffery 
332d4e7ac68SAndrew Jeffery 	mutex_lock(&priv->mutex);
33355ab48b4SAndrew Jeffery 
334d4e7ac68SAndrew Jeffery 	spin_lock_irq(&priv->lock);
335d4e7ac68SAndrew Jeffery 	data_avail = priv->data_in_avail;
33655ab48b4SAndrew Jeffery 	if (data_avail) {
337d4e7ac68SAndrew Jeffery 		data_len = priv->data_in_idx;
338d4e7ac68SAndrew Jeffery 		memcpy(priv->kbuffer, priv->data_in, data_len);
33955ab48b4SAndrew Jeffery 	}
340d4e7ac68SAndrew Jeffery 	spin_unlock_irq(&priv->lock);
34155ab48b4SAndrew Jeffery 
34255ab48b4SAndrew Jeffery 	if (!data_avail) {
34355ab48b4SAndrew Jeffery 		ret = -EAGAIN;
34455ab48b4SAndrew Jeffery 		goto out_unlock;
34555ab48b4SAndrew Jeffery 	}
34655ab48b4SAndrew Jeffery 
34755ab48b4SAndrew Jeffery 	if (count < data_len) {
34855ab48b4SAndrew Jeffery 		pr_err("channel=%u with too large data : %zu\n",
349d4e7ac68SAndrew Jeffery 			priv->client.dev->channel, data_len);
35055ab48b4SAndrew Jeffery 
351d4e7ac68SAndrew Jeffery 		spin_lock_irq(&priv->lock);
352d4e7ac68SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(priv);
353d4e7ac68SAndrew Jeffery 		spin_unlock_irq(&priv->lock);
35455ab48b4SAndrew Jeffery 
35555ab48b4SAndrew Jeffery 		ret = -EOVERFLOW;
35655ab48b4SAndrew Jeffery 		goto out_unlock;
35755ab48b4SAndrew Jeffery 	}
35855ab48b4SAndrew Jeffery 
359d4e7ac68SAndrew Jeffery 	if (copy_to_user(buf, priv->kbuffer, data_len)) {
36055ab48b4SAndrew Jeffery 		ret = -EFAULT;
36155ab48b4SAndrew Jeffery 		goto out_unlock;
36255ab48b4SAndrew Jeffery 	}
36355ab48b4SAndrew Jeffery 
36455ab48b4SAndrew Jeffery 	ret = data_len;
36555ab48b4SAndrew Jeffery 
366d4e7ac68SAndrew Jeffery 	spin_lock_irq(&priv->lock);
367d4e7ac68SAndrew Jeffery 	if (priv->phase == KCS_PHASE_WRITE_DONE) {
368d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_WAIT_READ;
369d4e7ac68SAndrew Jeffery 		priv->data_in_avail = false;
370d4e7ac68SAndrew Jeffery 		priv->data_in_idx = 0;
37155ab48b4SAndrew Jeffery 	} else {
37255ab48b4SAndrew Jeffery 		ret = -EAGAIN;
37355ab48b4SAndrew Jeffery 	}
374d4e7ac68SAndrew Jeffery 	spin_unlock_irq(&priv->lock);
37555ab48b4SAndrew Jeffery 
37655ab48b4SAndrew Jeffery out_unlock:
377d4e7ac68SAndrew Jeffery 	mutex_unlock(&priv->mutex);
37855ab48b4SAndrew Jeffery 
37955ab48b4SAndrew Jeffery 	return ret;
38055ab48b4SAndrew Jeffery }
38155ab48b4SAndrew Jeffery 
38255ab48b4SAndrew Jeffery static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
38355ab48b4SAndrew Jeffery 			     size_t count, loff_t *ppos)
38455ab48b4SAndrew Jeffery {
385d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
38655ab48b4SAndrew Jeffery 	ssize_t ret;
38755ab48b4SAndrew Jeffery 
38855ab48b4SAndrew Jeffery 	/* a minimum response size '3' : netfn + cmd + ccode */
38955ab48b4SAndrew Jeffery 	if (count < 3 || count > KCS_MSG_BUFSIZ)
39055ab48b4SAndrew Jeffery 		return -EINVAL;
39155ab48b4SAndrew Jeffery 
392d4e7ac68SAndrew Jeffery 	mutex_lock(&priv->mutex);
39355ab48b4SAndrew Jeffery 
394d4e7ac68SAndrew Jeffery 	if (copy_from_user(priv->kbuffer, buf, count)) {
39555ab48b4SAndrew Jeffery 		ret = -EFAULT;
39655ab48b4SAndrew Jeffery 		goto out_unlock;
39755ab48b4SAndrew Jeffery 	}
39855ab48b4SAndrew Jeffery 
399d4e7ac68SAndrew Jeffery 	spin_lock_irq(&priv->lock);
400d4e7ac68SAndrew Jeffery 	if (priv->phase == KCS_PHASE_WAIT_READ) {
401d4e7ac68SAndrew Jeffery 		priv->phase = KCS_PHASE_READ;
402d4e7ac68SAndrew Jeffery 		priv->data_out_idx = 1;
403d4e7ac68SAndrew Jeffery 		priv->data_out_len = count;
404d4e7ac68SAndrew Jeffery 		memcpy(priv->data_out, priv->kbuffer, count);
405d4e7ac68SAndrew Jeffery 		kcs_bmc_write_data(priv->client.dev, priv->data_out[0]);
40655ab48b4SAndrew Jeffery 		ret = count;
40755ab48b4SAndrew Jeffery 	} else {
40855ab48b4SAndrew Jeffery 		ret = -EINVAL;
40955ab48b4SAndrew Jeffery 	}
410d4e7ac68SAndrew Jeffery 	spin_unlock_irq(&priv->lock);
41155ab48b4SAndrew Jeffery 
41255ab48b4SAndrew Jeffery out_unlock:
413d4e7ac68SAndrew Jeffery 	mutex_unlock(&priv->mutex);
41455ab48b4SAndrew Jeffery 
41555ab48b4SAndrew Jeffery 	return ret;
41655ab48b4SAndrew Jeffery }
41755ab48b4SAndrew Jeffery 
41855ab48b4SAndrew Jeffery static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
41955ab48b4SAndrew Jeffery 			  unsigned long arg)
42055ab48b4SAndrew Jeffery {
421d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
42255ab48b4SAndrew Jeffery 	long ret = 0;
42355ab48b4SAndrew Jeffery 
424d4e7ac68SAndrew Jeffery 	spin_lock_irq(&priv->lock);
42555ab48b4SAndrew Jeffery 
42655ab48b4SAndrew Jeffery 	switch (cmd) {
42755ab48b4SAndrew Jeffery 	case IPMI_BMC_IOCTL_SET_SMS_ATN:
428d4e7ac68SAndrew Jeffery 		kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
42955ab48b4SAndrew Jeffery 		break;
43055ab48b4SAndrew Jeffery 
43155ab48b4SAndrew Jeffery 	case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
432d4e7ac68SAndrew Jeffery 		kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, 0);
43355ab48b4SAndrew Jeffery 		break;
43455ab48b4SAndrew Jeffery 
43555ab48b4SAndrew Jeffery 	case IPMI_BMC_IOCTL_FORCE_ABORT:
436d4e7ac68SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(priv);
43755ab48b4SAndrew Jeffery 		break;
43855ab48b4SAndrew Jeffery 
43955ab48b4SAndrew Jeffery 	default:
44055ab48b4SAndrew Jeffery 		ret = -EINVAL;
44155ab48b4SAndrew Jeffery 		break;
44255ab48b4SAndrew Jeffery 	}
44355ab48b4SAndrew Jeffery 
444d4e7ac68SAndrew Jeffery 	spin_unlock_irq(&priv->lock);
44555ab48b4SAndrew Jeffery 
44655ab48b4SAndrew Jeffery 	return ret;
44755ab48b4SAndrew Jeffery }
44855ab48b4SAndrew Jeffery 
44955ab48b4SAndrew Jeffery static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
45055ab48b4SAndrew Jeffery {
451d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
45255ab48b4SAndrew Jeffery 
453d4e7ac68SAndrew Jeffery 	kcs_bmc_ipmi_force_abort(priv);
454d4e7ac68SAndrew Jeffery 	kcs_bmc_disable_device(priv->client.dev, &priv->client);
45555ab48b4SAndrew Jeffery 
45655ab48b4SAndrew Jeffery 	return 0;
45755ab48b4SAndrew Jeffery }
45855ab48b4SAndrew Jeffery 
459d7096970SAndrew Jeffery static const struct file_operations kcs_bmc_ipmi_fops = {
46055ab48b4SAndrew Jeffery 	.owner          = THIS_MODULE,
46155ab48b4SAndrew Jeffery 	.open           = kcs_bmc_ipmi_open,
46255ab48b4SAndrew Jeffery 	.read           = kcs_bmc_ipmi_read,
46355ab48b4SAndrew Jeffery 	.write          = kcs_bmc_ipmi_write,
46455ab48b4SAndrew Jeffery 	.release        = kcs_bmc_ipmi_release,
46555ab48b4SAndrew Jeffery 	.poll           = kcs_bmc_ipmi_poll,
46655ab48b4SAndrew Jeffery 	.unlocked_ioctl = kcs_bmc_ipmi_ioctl,
46755ab48b4SAndrew Jeffery };
46855ab48b4SAndrew Jeffery 
469d4e7ac68SAndrew Jeffery static DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
470d4e7ac68SAndrew Jeffery static LIST_HEAD(kcs_bmc_ipmi_instances);
471d4e7ac68SAndrew Jeffery 
4727cafff99SAndrew Jeffery static int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc)
47355ab48b4SAndrew Jeffery {
474d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv;
475d7096970SAndrew Jeffery 	int rc;
47655ab48b4SAndrew Jeffery 
477d4e7ac68SAndrew Jeffery 	priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
478d4e7ac68SAndrew Jeffery 	if (!priv)
479d7096970SAndrew Jeffery 		return -ENOMEM;
48055ab48b4SAndrew Jeffery 
481d4e7ac68SAndrew Jeffery 	spin_lock_init(&priv->lock);
482d4e7ac68SAndrew Jeffery 	mutex_init(&priv->mutex);
483d7096970SAndrew Jeffery 
484d4e7ac68SAndrew Jeffery 	init_waitqueue_head(&priv->queue);
485d4e7ac68SAndrew Jeffery 
486d4e7ac68SAndrew Jeffery 	priv->client.dev = kcs_bmc;
487d4e7ac68SAndrew Jeffery 	priv->client.ops = &kcs_bmc_ipmi_client_ops;
488d4e7ac68SAndrew Jeffery 	priv->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
489d4e7ac68SAndrew Jeffery 	priv->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
490d4e7ac68SAndrew Jeffery 	priv->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
491d4e7ac68SAndrew Jeffery 
492d4e7ac68SAndrew Jeffery 	priv->miscdev.minor = MISC_DYNAMIC_MINOR;
493d4e7ac68SAndrew Jeffery 	priv->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u", DEVICE_NAME,
494d4e7ac68SAndrew Jeffery 					   kcs_bmc->channel);
495d4e7ac68SAndrew Jeffery 	if (!priv->data_in || !priv->data_out || !priv->kbuffer || !priv->miscdev.name)
496d4e7ac68SAndrew Jeffery 		return -EINVAL;
497d4e7ac68SAndrew Jeffery 
498d4e7ac68SAndrew Jeffery 	priv->miscdev.fops = &kcs_bmc_ipmi_fops;
499d4e7ac68SAndrew Jeffery 
500d4e7ac68SAndrew Jeffery 	rc = misc_register(&priv->miscdev);
501d7096970SAndrew Jeffery 	if (rc) {
502d7096970SAndrew Jeffery 		dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
503d7096970SAndrew Jeffery 		return rc;
50455ab48b4SAndrew Jeffery 	}
505d7096970SAndrew Jeffery 
506d4e7ac68SAndrew Jeffery 	spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
507d4e7ac68SAndrew Jeffery 	list_add(&priv->entry, &kcs_bmc_ipmi_instances);
508d4e7ac68SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
509d4e7ac68SAndrew Jeffery 
510d7096970SAndrew Jeffery 	dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
511d7096970SAndrew Jeffery 
512d7096970SAndrew Jeffery 	return 0;
513d7096970SAndrew Jeffery }
514d7096970SAndrew Jeffery 
5157cafff99SAndrew Jeffery static int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc)
516d7096970SAndrew Jeffery {
517d4e7ac68SAndrew Jeffery 	struct kcs_bmc_ipmi *priv = NULL, *pos;
518d7096970SAndrew Jeffery 
519d4e7ac68SAndrew Jeffery 	spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
520d4e7ac68SAndrew Jeffery 	list_for_each_entry(pos, &kcs_bmc_ipmi_instances, entry) {
521d4e7ac68SAndrew Jeffery 		if (pos->client.dev == kcs_bmc) {
522d4e7ac68SAndrew Jeffery 			priv = pos;
523d4e7ac68SAndrew Jeffery 			list_del(&pos->entry);
524d4e7ac68SAndrew Jeffery 			break;
525d4e7ac68SAndrew Jeffery 		}
526d4e7ac68SAndrew Jeffery 	}
527d4e7ac68SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
528d7096970SAndrew Jeffery 
529d4e7ac68SAndrew Jeffery 	if (!priv)
530d4e7ac68SAndrew Jeffery 		return -ENODEV;
531d4e7ac68SAndrew Jeffery 
532d4e7ac68SAndrew Jeffery 	misc_deregister(&priv->miscdev);
533d4e7ac68SAndrew Jeffery 	kcs_bmc_disable_device(priv->client.dev, &priv->client);
534d4e7ac68SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, priv->kbuffer);
535d4e7ac68SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, priv->data_out);
536d4e7ac68SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, priv->data_in);
537d4e7ac68SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, priv);
538d7096970SAndrew Jeffery 
539d7096970SAndrew Jeffery 	return 0;
540d7096970SAndrew Jeffery }
5417cafff99SAndrew Jeffery 
5427cafff99SAndrew Jeffery static const struct kcs_bmc_driver_ops kcs_bmc_ipmi_driver_ops = {
5437cafff99SAndrew Jeffery 	.add_device = kcs_bmc_ipmi_add_device,
5447cafff99SAndrew Jeffery 	.remove_device = kcs_bmc_ipmi_remove_device,
5457cafff99SAndrew Jeffery };
5467cafff99SAndrew Jeffery 
5477cafff99SAndrew Jeffery static struct kcs_bmc_driver kcs_bmc_ipmi_driver = {
5487cafff99SAndrew Jeffery 	.ops = &kcs_bmc_ipmi_driver_ops,
5497cafff99SAndrew Jeffery };
5507cafff99SAndrew Jeffery 
551*ec7174f6SXiu Jianfeng static int __init kcs_bmc_ipmi_init(void)
5527cafff99SAndrew Jeffery {
5537cafff99SAndrew Jeffery 	kcs_bmc_register_driver(&kcs_bmc_ipmi_driver);
5547cafff99SAndrew Jeffery 
5557cafff99SAndrew Jeffery 	return 0;
5567cafff99SAndrew Jeffery }
5577cafff99SAndrew Jeffery module_init(kcs_bmc_ipmi_init);
5587cafff99SAndrew Jeffery 
559*ec7174f6SXiu Jianfeng static void __exit kcs_bmc_ipmi_exit(void)
5607cafff99SAndrew Jeffery {
5617cafff99SAndrew Jeffery 	kcs_bmc_unregister_driver(&kcs_bmc_ipmi_driver);
5627cafff99SAndrew Jeffery }
5637cafff99SAndrew Jeffery module_exit(kcs_bmc_ipmi_exit);
56455ab48b4SAndrew Jeffery 
56555ab48b4SAndrew Jeffery MODULE_LICENSE("GPL v2");
56655ab48b4SAndrew Jeffery MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
567d4e7ac68SAndrew Jeffery MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
56855ab48b4SAndrew Jeffery MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
569