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
set_state(struct kcs_bmc_ipmi * priv,u8 state)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
kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi * priv)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
kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi * priv)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
kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi * priv)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
client_to_kcs_bmc_ipmi(struct kcs_bmc_client * client)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
kcs_bmc_ipmi_event(struct kcs_bmc_client * client)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
to_kcs_bmc(struct file * filp)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
kcs_bmc_ipmi_open(struct inode * inode,struct file * filp)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
kcs_bmc_ipmi_poll(struct file * filp,poll_table * wait)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
kcs_bmc_ipmi_read(struct file * filp,char __user * buf,size_t count,loff_t * ppos)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
kcs_bmc_ipmi_write(struct file * filp,const char __user * buf,size_t count,loff_t * ppos)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
kcs_bmc_ipmi_ioctl(struct file * filp,unsigned int cmd,unsigned long arg)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
kcs_bmc_ipmi_release(struct inode * inode,struct file * filp)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
kcs_bmc_ipmi_add_device(struct kcs_bmc_device * kcs_bmc)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
kcs_bmc_ipmi_remove_device(struct kcs_bmc_device * kcs_bmc)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
kcs_bmc_ipmi_init(void)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
kcs_bmc_ipmi_exit(void)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