162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2015-2018, Intel Corporation.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#define pr_fmt(fmt) "kcs-bmc: " fmt
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/errno.h>
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/ipmi_bmc.h>
1162306a36Sopenharmony_ci#include <linux/list.h>
1262306a36Sopenharmony_ci#include <linux/miscdevice.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/mutex.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/poll.h>
1762306a36Sopenharmony_ci#include <linux/sched.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "kcs_bmc_client.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/* Different phases of the KCS BMC module.
2362306a36Sopenharmony_ci *  KCS_PHASE_IDLE:
2462306a36Sopenharmony_ci *            BMC should not be expecting nor sending any data.
2562306a36Sopenharmony_ci *  KCS_PHASE_WRITE_START:
2662306a36Sopenharmony_ci *            BMC is receiving a WRITE_START command from system software.
2762306a36Sopenharmony_ci *  KCS_PHASE_WRITE_DATA:
2862306a36Sopenharmony_ci *            BMC is receiving a data byte from system software.
2962306a36Sopenharmony_ci *  KCS_PHASE_WRITE_END_CMD:
3062306a36Sopenharmony_ci *            BMC is waiting a last data byte from system software.
3162306a36Sopenharmony_ci *  KCS_PHASE_WRITE_DONE:
3262306a36Sopenharmony_ci *            BMC has received the whole request from system software.
3362306a36Sopenharmony_ci *  KCS_PHASE_WAIT_READ:
3462306a36Sopenharmony_ci *            BMC is waiting the response from the upper IPMI service.
3562306a36Sopenharmony_ci *  KCS_PHASE_READ:
3662306a36Sopenharmony_ci *            BMC is transferring the response to system software.
3762306a36Sopenharmony_ci *  KCS_PHASE_ABORT_ERROR1:
3862306a36Sopenharmony_ci *            BMC is waiting error status request from system software.
3962306a36Sopenharmony_ci *  KCS_PHASE_ABORT_ERROR2:
4062306a36Sopenharmony_ci *            BMC is waiting for idle status afer error from system software.
4162306a36Sopenharmony_ci *  KCS_PHASE_ERROR:
4262306a36Sopenharmony_ci *            BMC has detected a protocol violation at the interface level.
4362306a36Sopenharmony_ci */
4462306a36Sopenharmony_cienum kcs_ipmi_phases {
4562306a36Sopenharmony_ci	KCS_PHASE_IDLE,
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	KCS_PHASE_WRITE_START,
4862306a36Sopenharmony_ci	KCS_PHASE_WRITE_DATA,
4962306a36Sopenharmony_ci	KCS_PHASE_WRITE_END_CMD,
5062306a36Sopenharmony_ci	KCS_PHASE_WRITE_DONE,
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	KCS_PHASE_WAIT_READ,
5362306a36Sopenharmony_ci	KCS_PHASE_READ,
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	KCS_PHASE_ABORT_ERROR1,
5662306a36Sopenharmony_ci	KCS_PHASE_ABORT_ERROR2,
5762306a36Sopenharmony_ci	KCS_PHASE_ERROR
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci/* IPMI 2.0 - Table 9-4, KCS Interface Status Codes */
6162306a36Sopenharmony_cienum kcs_ipmi_errors {
6262306a36Sopenharmony_ci	KCS_NO_ERROR                = 0x00,
6362306a36Sopenharmony_ci	KCS_ABORTED_BY_COMMAND      = 0x01,
6462306a36Sopenharmony_ci	KCS_ILLEGAL_CONTROL_CODE    = 0x02,
6562306a36Sopenharmony_ci	KCS_LENGTH_ERROR            = 0x06,
6662306a36Sopenharmony_ci	KCS_UNSPECIFIED_ERROR       = 0xFF
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistruct kcs_bmc_ipmi {
7062306a36Sopenharmony_ci	struct list_head entry;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	struct kcs_bmc_client client;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	spinlock_t lock;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	enum kcs_ipmi_phases phase;
7762306a36Sopenharmony_ci	enum kcs_ipmi_errors error;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	wait_queue_head_t queue;
8062306a36Sopenharmony_ci	bool data_in_avail;
8162306a36Sopenharmony_ci	int  data_in_idx;
8262306a36Sopenharmony_ci	u8  *data_in;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	int  data_out_idx;
8562306a36Sopenharmony_ci	int  data_out_len;
8662306a36Sopenharmony_ci	u8  *data_out;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	struct mutex mutex;
8962306a36Sopenharmony_ci	u8 *kbuffer;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	struct miscdevice miscdev;
9262306a36Sopenharmony_ci};
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci#define DEVICE_NAME "ipmi-kcs"
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci#define KCS_MSG_BUFSIZ    1000
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci#define KCS_ZERO_DATA     0
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci/* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
10162306a36Sopenharmony_ci#define KCS_STATUS_STATE(state) (state << 6)
10262306a36Sopenharmony_ci#define KCS_STATUS_STATE_MASK   GENMASK(7, 6)
10362306a36Sopenharmony_ci#define KCS_STATUS_CMD_DAT      BIT(3)
10462306a36Sopenharmony_ci#define KCS_STATUS_SMS_ATN      BIT(2)
10562306a36Sopenharmony_ci#define KCS_STATUS_IBF          BIT(1)
10662306a36Sopenharmony_ci#define KCS_STATUS_OBF          BIT(0)
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/* IPMI 2.0 - Table 9-2, KCS Interface State Bits */
10962306a36Sopenharmony_cienum kcs_states {
11062306a36Sopenharmony_ci	IDLE_STATE  = 0,
11162306a36Sopenharmony_ci	READ_STATE  = 1,
11262306a36Sopenharmony_ci	WRITE_STATE = 2,
11362306a36Sopenharmony_ci	ERROR_STATE = 3,
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci/* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */
11762306a36Sopenharmony_ci#define KCS_CMD_GET_STATUS_ABORT  0x60
11862306a36Sopenharmony_ci#define KCS_CMD_WRITE_START       0x61
11962306a36Sopenharmony_ci#define KCS_CMD_WRITE_END         0x62
12062306a36Sopenharmony_ci#define KCS_CMD_READ_BYTE         0x68
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic inline void set_state(struct kcs_bmc_ipmi *priv, u8 state)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	kcs_bmc_update_status(priv->client.dev, KCS_STATUS_STATE_MASK, KCS_STATUS_STATE(state));
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic void kcs_bmc_ipmi_force_abort(struct kcs_bmc_ipmi *priv)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	set_state(priv, ERROR_STATE);
13062306a36Sopenharmony_ci	kcs_bmc_read_data(priv->client.dev);
13162306a36Sopenharmony_ci	kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	priv->phase = KCS_PHASE_ERROR;
13462306a36Sopenharmony_ci	priv->data_in_avail = false;
13562306a36Sopenharmony_ci	priv->data_in_idx = 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic void kcs_bmc_ipmi_handle_data(struct kcs_bmc_ipmi *priv)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct kcs_bmc_device *dev;
14162306a36Sopenharmony_ci	u8 data;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	dev = priv->client.dev;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	switch (priv->phase) {
14662306a36Sopenharmony_ci	case KCS_PHASE_WRITE_START:
14762306a36Sopenharmony_ci		priv->phase = KCS_PHASE_WRITE_DATA;
14862306a36Sopenharmony_ci		fallthrough;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	case KCS_PHASE_WRITE_DATA:
15162306a36Sopenharmony_ci		if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
15262306a36Sopenharmony_ci			set_state(priv, WRITE_STATE);
15362306a36Sopenharmony_ci			kcs_bmc_write_data(dev, KCS_ZERO_DATA);
15462306a36Sopenharmony_ci			priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
15562306a36Sopenharmony_ci		} else {
15662306a36Sopenharmony_ci			kcs_bmc_ipmi_force_abort(priv);
15762306a36Sopenharmony_ci			priv->error = KCS_LENGTH_ERROR;
15862306a36Sopenharmony_ci		}
15962306a36Sopenharmony_ci		break;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	case KCS_PHASE_WRITE_END_CMD:
16262306a36Sopenharmony_ci		if (priv->data_in_idx < KCS_MSG_BUFSIZ) {
16362306a36Sopenharmony_ci			set_state(priv, READ_STATE);
16462306a36Sopenharmony_ci			priv->data_in[priv->data_in_idx++] = kcs_bmc_read_data(dev);
16562306a36Sopenharmony_ci			priv->phase = KCS_PHASE_WRITE_DONE;
16662306a36Sopenharmony_ci			priv->data_in_avail = true;
16762306a36Sopenharmony_ci			wake_up_interruptible(&priv->queue);
16862306a36Sopenharmony_ci		} else {
16962306a36Sopenharmony_ci			kcs_bmc_ipmi_force_abort(priv);
17062306a36Sopenharmony_ci			priv->error = KCS_LENGTH_ERROR;
17162306a36Sopenharmony_ci		}
17262306a36Sopenharmony_ci		break;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	case KCS_PHASE_READ:
17562306a36Sopenharmony_ci		if (priv->data_out_idx == priv->data_out_len)
17662306a36Sopenharmony_ci			set_state(priv, IDLE_STATE);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		data = kcs_bmc_read_data(dev);
17962306a36Sopenharmony_ci		if (data != KCS_CMD_READ_BYTE) {
18062306a36Sopenharmony_ci			set_state(priv, ERROR_STATE);
18162306a36Sopenharmony_ci			kcs_bmc_write_data(dev, KCS_ZERO_DATA);
18262306a36Sopenharmony_ci			break;
18362306a36Sopenharmony_ci		}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci		if (priv->data_out_idx == priv->data_out_len) {
18662306a36Sopenharmony_ci			kcs_bmc_write_data(dev, KCS_ZERO_DATA);
18762306a36Sopenharmony_ci			priv->phase = KCS_PHASE_IDLE;
18862306a36Sopenharmony_ci			break;
18962306a36Sopenharmony_ci		}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci		kcs_bmc_write_data(dev, priv->data_out[priv->data_out_idx++]);
19262306a36Sopenharmony_ci		break;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	case KCS_PHASE_ABORT_ERROR1:
19562306a36Sopenharmony_ci		set_state(priv, READ_STATE);
19662306a36Sopenharmony_ci		kcs_bmc_read_data(dev);
19762306a36Sopenharmony_ci		kcs_bmc_write_data(dev, priv->error);
19862306a36Sopenharmony_ci		priv->phase = KCS_PHASE_ABORT_ERROR2;
19962306a36Sopenharmony_ci		break;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	case KCS_PHASE_ABORT_ERROR2:
20262306a36Sopenharmony_ci		set_state(priv, IDLE_STATE);
20362306a36Sopenharmony_ci		kcs_bmc_read_data(dev);
20462306a36Sopenharmony_ci		kcs_bmc_write_data(dev, KCS_ZERO_DATA);
20562306a36Sopenharmony_ci		priv->phase = KCS_PHASE_IDLE;
20662306a36Sopenharmony_ci		break;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	default:
20962306a36Sopenharmony_ci		kcs_bmc_ipmi_force_abort(priv);
21062306a36Sopenharmony_ci		break;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc_ipmi *priv)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	u8 cmd;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	set_state(priv, WRITE_STATE);
21962306a36Sopenharmony_ci	kcs_bmc_write_data(priv->client.dev, KCS_ZERO_DATA);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	cmd = kcs_bmc_read_data(priv->client.dev);
22262306a36Sopenharmony_ci	switch (cmd) {
22362306a36Sopenharmony_ci	case KCS_CMD_WRITE_START:
22462306a36Sopenharmony_ci		priv->phase = KCS_PHASE_WRITE_START;
22562306a36Sopenharmony_ci		priv->error = KCS_NO_ERROR;
22662306a36Sopenharmony_ci		priv->data_in_avail = false;
22762306a36Sopenharmony_ci		priv->data_in_idx = 0;
22862306a36Sopenharmony_ci		break;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	case KCS_CMD_WRITE_END:
23162306a36Sopenharmony_ci		if (priv->phase != KCS_PHASE_WRITE_DATA) {
23262306a36Sopenharmony_ci			kcs_bmc_ipmi_force_abort(priv);
23362306a36Sopenharmony_ci			break;
23462306a36Sopenharmony_ci		}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci		priv->phase = KCS_PHASE_WRITE_END_CMD;
23762306a36Sopenharmony_ci		break;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	case KCS_CMD_GET_STATUS_ABORT:
24062306a36Sopenharmony_ci		if (priv->error == KCS_NO_ERROR)
24162306a36Sopenharmony_ci			priv->error = KCS_ABORTED_BY_COMMAND;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci		priv->phase = KCS_PHASE_ABORT_ERROR1;
24462306a36Sopenharmony_ci		priv->data_in_avail = false;
24562306a36Sopenharmony_ci		priv->data_in_idx = 0;
24662306a36Sopenharmony_ci		break;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	default:
24962306a36Sopenharmony_ci		kcs_bmc_ipmi_force_abort(priv);
25062306a36Sopenharmony_ci		priv->error = KCS_ILLEGAL_CONTROL_CODE;
25162306a36Sopenharmony_ci		break;
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic inline struct kcs_bmc_ipmi *client_to_kcs_bmc_ipmi(struct kcs_bmc_client *client)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	return container_of(client, struct kcs_bmc_ipmi, client);
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_cistatic irqreturn_t kcs_bmc_ipmi_event(struct kcs_bmc_client *client)
26162306a36Sopenharmony_ci{
26262306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv;
26362306a36Sopenharmony_ci	u8 status;
26462306a36Sopenharmony_ci	int ret;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	priv = client_to_kcs_bmc_ipmi(client);
26762306a36Sopenharmony_ci	if (!priv)
26862306a36Sopenharmony_ci		return IRQ_NONE;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	spin_lock(&priv->lock);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	status = kcs_bmc_read_status(client->dev);
27362306a36Sopenharmony_ci	if (status & KCS_STATUS_IBF) {
27462306a36Sopenharmony_ci		if (status & KCS_STATUS_CMD_DAT)
27562306a36Sopenharmony_ci			kcs_bmc_ipmi_handle_cmd(priv);
27662306a36Sopenharmony_ci		else
27762306a36Sopenharmony_ci			kcs_bmc_ipmi_handle_data(priv);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci		ret = IRQ_HANDLED;
28062306a36Sopenharmony_ci	} else {
28162306a36Sopenharmony_ci		ret = IRQ_NONE;
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	spin_unlock(&priv->lock);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	return ret;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic const struct kcs_bmc_client_ops kcs_bmc_ipmi_client_ops = {
29062306a36Sopenharmony_ci	.event = kcs_bmc_ipmi_event,
29162306a36Sopenharmony_ci};
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic inline struct kcs_bmc_ipmi *to_kcs_bmc(struct file *filp)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	return container_of(filp->private_data, struct kcs_bmc_ipmi, miscdev);
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	return kcs_bmc_enable_device(priv->client.dev, &priv->client);
30362306a36Sopenharmony_ci}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_cistatic __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
30662306a36Sopenharmony_ci{
30762306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
30862306a36Sopenharmony_ci	__poll_t mask = 0;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	poll_wait(filp, &priv->queue, wait);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	spin_lock_irq(&priv->lock);
31362306a36Sopenharmony_ci	if (priv->data_in_avail)
31462306a36Sopenharmony_ci		mask |= EPOLLIN;
31562306a36Sopenharmony_ci	spin_unlock_irq(&priv->lock);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	return mask;
31862306a36Sopenharmony_ci}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_cistatic ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
32162306a36Sopenharmony_ci			    size_t count, loff_t *ppos)
32262306a36Sopenharmony_ci{
32362306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
32462306a36Sopenharmony_ci	bool data_avail;
32562306a36Sopenharmony_ci	size_t data_len;
32662306a36Sopenharmony_ci	ssize_t ret;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	if (!(filp->f_flags & O_NONBLOCK))
32962306a36Sopenharmony_ci		wait_event_interruptible(priv->queue,
33062306a36Sopenharmony_ci					 priv->data_in_avail);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	mutex_lock(&priv->mutex);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	spin_lock_irq(&priv->lock);
33562306a36Sopenharmony_ci	data_avail = priv->data_in_avail;
33662306a36Sopenharmony_ci	if (data_avail) {
33762306a36Sopenharmony_ci		data_len = priv->data_in_idx;
33862306a36Sopenharmony_ci		memcpy(priv->kbuffer, priv->data_in, data_len);
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci	spin_unlock_irq(&priv->lock);
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	if (!data_avail) {
34362306a36Sopenharmony_ci		ret = -EAGAIN;
34462306a36Sopenharmony_ci		goto out_unlock;
34562306a36Sopenharmony_ci	}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	if (count < data_len) {
34862306a36Sopenharmony_ci		pr_err("channel=%u with too large data : %zu\n",
34962306a36Sopenharmony_ci			priv->client.dev->channel, data_len);
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci		spin_lock_irq(&priv->lock);
35262306a36Sopenharmony_ci		kcs_bmc_ipmi_force_abort(priv);
35362306a36Sopenharmony_ci		spin_unlock_irq(&priv->lock);
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci		ret = -EOVERFLOW;
35662306a36Sopenharmony_ci		goto out_unlock;
35762306a36Sopenharmony_ci	}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	if (copy_to_user(buf, priv->kbuffer, data_len)) {
36062306a36Sopenharmony_ci		ret = -EFAULT;
36162306a36Sopenharmony_ci		goto out_unlock;
36262306a36Sopenharmony_ci	}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	ret = data_len;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	spin_lock_irq(&priv->lock);
36762306a36Sopenharmony_ci	if (priv->phase == KCS_PHASE_WRITE_DONE) {
36862306a36Sopenharmony_ci		priv->phase = KCS_PHASE_WAIT_READ;
36962306a36Sopenharmony_ci		priv->data_in_avail = false;
37062306a36Sopenharmony_ci		priv->data_in_idx = 0;
37162306a36Sopenharmony_ci	} else {
37262306a36Sopenharmony_ci		ret = -EAGAIN;
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci	spin_unlock_irq(&priv->lock);
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ciout_unlock:
37762306a36Sopenharmony_ci	mutex_unlock(&priv->mutex);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	return ret;
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_cistatic ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
38362306a36Sopenharmony_ci			     size_t count, loff_t *ppos)
38462306a36Sopenharmony_ci{
38562306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
38662306a36Sopenharmony_ci	ssize_t ret;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	/* a minimum response size '3' : netfn + cmd + ccode */
38962306a36Sopenharmony_ci	if (count < 3 || count > KCS_MSG_BUFSIZ)
39062306a36Sopenharmony_ci		return -EINVAL;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	mutex_lock(&priv->mutex);
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	if (copy_from_user(priv->kbuffer, buf, count)) {
39562306a36Sopenharmony_ci		ret = -EFAULT;
39662306a36Sopenharmony_ci		goto out_unlock;
39762306a36Sopenharmony_ci	}
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	spin_lock_irq(&priv->lock);
40062306a36Sopenharmony_ci	if (priv->phase == KCS_PHASE_WAIT_READ) {
40162306a36Sopenharmony_ci		priv->phase = KCS_PHASE_READ;
40262306a36Sopenharmony_ci		priv->data_out_idx = 1;
40362306a36Sopenharmony_ci		priv->data_out_len = count;
40462306a36Sopenharmony_ci		memcpy(priv->data_out, priv->kbuffer, count);
40562306a36Sopenharmony_ci		kcs_bmc_write_data(priv->client.dev, priv->data_out[0]);
40662306a36Sopenharmony_ci		ret = count;
40762306a36Sopenharmony_ci	} else {
40862306a36Sopenharmony_ci		ret = -EINVAL;
40962306a36Sopenharmony_ci	}
41062306a36Sopenharmony_ci	spin_unlock_irq(&priv->lock);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ciout_unlock:
41362306a36Sopenharmony_ci	mutex_unlock(&priv->mutex);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	return ret;
41662306a36Sopenharmony_ci}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_cistatic long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
41962306a36Sopenharmony_ci			  unsigned long arg)
42062306a36Sopenharmony_ci{
42162306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
42262306a36Sopenharmony_ci	long ret = 0;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	spin_lock_irq(&priv->lock);
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	switch (cmd) {
42762306a36Sopenharmony_ci	case IPMI_BMC_IOCTL_SET_SMS_ATN:
42862306a36Sopenharmony_ci		kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
42962306a36Sopenharmony_ci		break;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
43262306a36Sopenharmony_ci		kcs_bmc_update_status(priv->client.dev, KCS_STATUS_SMS_ATN, 0);
43362306a36Sopenharmony_ci		break;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	case IPMI_BMC_IOCTL_FORCE_ABORT:
43662306a36Sopenharmony_ci		kcs_bmc_ipmi_force_abort(priv);
43762306a36Sopenharmony_ci		break;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	default:
44062306a36Sopenharmony_ci		ret = -EINVAL;
44162306a36Sopenharmony_ci		break;
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	spin_unlock_irq(&priv->lock);
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	return ret;
44762306a36Sopenharmony_ci}
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_cistatic int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv = to_kcs_bmc(filp);
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	kcs_bmc_ipmi_force_abort(priv);
45462306a36Sopenharmony_ci	kcs_bmc_disable_device(priv->client.dev, &priv->client);
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	return 0;
45762306a36Sopenharmony_ci}
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_cistatic const struct file_operations kcs_bmc_ipmi_fops = {
46062306a36Sopenharmony_ci	.owner          = THIS_MODULE,
46162306a36Sopenharmony_ci	.open           = kcs_bmc_ipmi_open,
46262306a36Sopenharmony_ci	.read           = kcs_bmc_ipmi_read,
46362306a36Sopenharmony_ci	.write          = kcs_bmc_ipmi_write,
46462306a36Sopenharmony_ci	.release        = kcs_bmc_ipmi_release,
46562306a36Sopenharmony_ci	.poll           = kcs_bmc_ipmi_poll,
46662306a36Sopenharmony_ci	.unlocked_ioctl = kcs_bmc_ipmi_ioctl,
46762306a36Sopenharmony_ci};
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_cistatic DEFINE_SPINLOCK(kcs_bmc_ipmi_instances_lock);
47062306a36Sopenharmony_cistatic LIST_HEAD(kcs_bmc_ipmi_instances);
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_cistatic int kcs_bmc_ipmi_add_device(struct kcs_bmc_device *kcs_bmc)
47362306a36Sopenharmony_ci{
47462306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv;
47562306a36Sopenharmony_ci	int rc;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
47862306a36Sopenharmony_ci	if (!priv)
47962306a36Sopenharmony_ci		return -ENOMEM;
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	spin_lock_init(&priv->lock);
48262306a36Sopenharmony_ci	mutex_init(&priv->mutex);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	init_waitqueue_head(&priv->queue);
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	priv->client.dev = kcs_bmc;
48762306a36Sopenharmony_ci	priv->client.ops = &kcs_bmc_ipmi_client_ops;
48862306a36Sopenharmony_ci	priv->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
48962306a36Sopenharmony_ci	priv->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
49062306a36Sopenharmony_ci	priv->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	priv->miscdev.minor = MISC_DYNAMIC_MINOR;
49362306a36Sopenharmony_ci	priv->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u", DEVICE_NAME,
49462306a36Sopenharmony_ci					   kcs_bmc->channel);
49562306a36Sopenharmony_ci	if (!priv->data_in || !priv->data_out || !priv->kbuffer || !priv->miscdev.name)
49662306a36Sopenharmony_ci		return -EINVAL;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	priv->miscdev.fops = &kcs_bmc_ipmi_fops;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	rc = misc_register(&priv->miscdev);
50162306a36Sopenharmony_ci	if (rc) {
50262306a36Sopenharmony_ci		dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
50362306a36Sopenharmony_ci		return rc;
50462306a36Sopenharmony_ci	}
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
50762306a36Sopenharmony_ci	list_add(&priv->entry, &kcs_bmc_ipmi_instances);
50862306a36Sopenharmony_ci	spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	return 0;
51362306a36Sopenharmony_ci}
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_cistatic int kcs_bmc_ipmi_remove_device(struct kcs_bmc_device *kcs_bmc)
51662306a36Sopenharmony_ci{
51762306a36Sopenharmony_ci	struct kcs_bmc_ipmi *priv = NULL, *pos;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	spin_lock_irq(&kcs_bmc_ipmi_instances_lock);
52062306a36Sopenharmony_ci	list_for_each_entry(pos, &kcs_bmc_ipmi_instances, entry) {
52162306a36Sopenharmony_ci		if (pos->client.dev == kcs_bmc) {
52262306a36Sopenharmony_ci			priv = pos;
52362306a36Sopenharmony_ci			list_del(&pos->entry);
52462306a36Sopenharmony_ci			break;
52562306a36Sopenharmony_ci		}
52662306a36Sopenharmony_ci	}
52762306a36Sopenharmony_ci	spin_unlock_irq(&kcs_bmc_ipmi_instances_lock);
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	if (!priv)
53062306a36Sopenharmony_ci		return -ENODEV;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	misc_deregister(&priv->miscdev);
53362306a36Sopenharmony_ci	kcs_bmc_disable_device(priv->client.dev, &priv->client);
53462306a36Sopenharmony_ci	devm_kfree(kcs_bmc->dev, priv->kbuffer);
53562306a36Sopenharmony_ci	devm_kfree(kcs_bmc->dev, priv->data_out);
53662306a36Sopenharmony_ci	devm_kfree(kcs_bmc->dev, priv->data_in);
53762306a36Sopenharmony_ci	devm_kfree(kcs_bmc->dev, priv);
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	return 0;
54062306a36Sopenharmony_ci}
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_cistatic const struct kcs_bmc_driver_ops kcs_bmc_ipmi_driver_ops = {
54362306a36Sopenharmony_ci	.add_device = kcs_bmc_ipmi_add_device,
54462306a36Sopenharmony_ci	.remove_device = kcs_bmc_ipmi_remove_device,
54562306a36Sopenharmony_ci};
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_cistatic struct kcs_bmc_driver kcs_bmc_ipmi_driver = {
54862306a36Sopenharmony_ci	.ops = &kcs_bmc_ipmi_driver_ops,
54962306a36Sopenharmony_ci};
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_cistatic int __init kcs_bmc_ipmi_init(void)
55262306a36Sopenharmony_ci{
55362306a36Sopenharmony_ci	kcs_bmc_register_driver(&kcs_bmc_ipmi_driver);
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	return 0;
55662306a36Sopenharmony_ci}
55762306a36Sopenharmony_cimodule_init(kcs_bmc_ipmi_init);
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_cistatic void __exit kcs_bmc_ipmi_exit(void)
56062306a36Sopenharmony_ci{
56162306a36Sopenharmony_ci	kcs_bmc_unregister_driver(&kcs_bmc_ipmi_driver);
56262306a36Sopenharmony_ci}
56362306a36Sopenharmony_cimodule_exit(kcs_bmc_ipmi_exit);
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
56662306a36Sopenharmony_ciMODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
56762306a36Sopenharmony_ciMODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
56862306a36Sopenharmony_ciMODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
569