xref: /linux/drivers/char/ipmi/kcs_bmc_cdev_ipmi.c (revision d7096970075ef47c9906fd241cc4939cc11ddd01)
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>
1155ab48b4SAndrew Jeffery #include <linux/module.h>
1255ab48b4SAndrew Jeffery #include <linux/platform_device.h>
1355ab48b4SAndrew Jeffery #include <linux/poll.h>
1455ab48b4SAndrew Jeffery #include <linux/sched.h>
1555ab48b4SAndrew Jeffery #include <linux/slab.h>
1655ab48b4SAndrew Jeffery 
1755ab48b4SAndrew Jeffery #include "kcs_bmc.h"
1855ab48b4SAndrew Jeffery 
1955ab48b4SAndrew Jeffery #define DEVICE_NAME "ipmi-kcs"
2055ab48b4SAndrew Jeffery 
2155ab48b4SAndrew Jeffery #define KCS_MSG_BUFSIZ    1000
2255ab48b4SAndrew Jeffery 
2355ab48b4SAndrew Jeffery #define KCS_ZERO_DATA     0
2455ab48b4SAndrew Jeffery 
2555ab48b4SAndrew Jeffery 
2655ab48b4SAndrew Jeffery /* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
2755ab48b4SAndrew Jeffery #define KCS_STATUS_STATE(state) (state << 6)
2855ab48b4SAndrew Jeffery #define KCS_STATUS_STATE_MASK   GENMASK(7, 6)
2955ab48b4SAndrew Jeffery #define KCS_STATUS_CMD_DAT      BIT(3)
3055ab48b4SAndrew Jeffery #define KCS_STATUS_SMS_ATN      BIT(2)
3155ab48b4SAndrew Jeffery #define KCS_STATUS_IBF          BIT(1)
3255ab48b4SAndrew Jeffery #define KCS_STATUS_OBF          BIT(0)
3355ab48b4SAndrew Jeffery 
3455ab48b4SAndrew Jeffery /* IPMI 2.0 - Table 9-2, KCS Interface State Bits */
3555ab48b4SAndrew Jeffery enum kcs_states {
3655ab48b4SAndrew Jeffery 	IDLE_STATE  = 0,
3755ab48b4SAndrew Jeffery 	READ_STATE  = 1,
3855ab48b4SAndrew Jeffery 	WRITE_STATE = 2,
3955ab48b4SAndrew Jeffery 	ERROR_STATE = 3,
4055ab48b4SAndrew Jeffery };
4155ab48b4SAndrew Jeffery 
4255ab48b4SAndrew Jeffery /* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */
4355ab48b4SAndrew Jeffery #define KCS_CMD_GET_STATUS_ABORT  0x60
4455ab48b4SAndrew Jeffery #define KCS_CMD_WRITE_START       0x61
4555ab48b4SAndrew Jeffery #define KCS_CMD_WRITE_END         0x62
4655ab48b4SAndrew Jeffery #define KCS_CMD_READ_BYTE         0x68
4755ab48b4SAndrew Jeffery 
4855ab48b4SAndrew Jeffery static inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
4955ab48b4SAndrew Jeffery {
5055ab48b4SAndrew Jeffery 	kcs_bmc_update_status(kcs_bmc, KCS_STATUS_STATE_MASK,
5155ab48b4SAndrew Jeffery 					KCS_STATUS_STATE(state));
5255ab48b4SAndrew Jeffery }
5355ab48b4SAndrew Jeffery 
5455ab48b4SAndrew Jeffery static void kcs_bmc_ipmi_force_abort(struct kcs_bmc *kcs_bmc)
5555ab48b4SAndrew Jeffery {
5655ab48b4SAndrew Jeffery 	set_state(kcs_bmc, ERROR_STATE);
5755ab48b4SAndrew Jeffery 	kcs_bmc_read_data(kcs_bmc);
5855ab48b4SAndrew Jeffery 	kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
5955ab48b4SAndrew Jeffery 
6055ab48b4SAndrew Jeffery 	kcs_bmc->phase = KCS_PHASE_ERROR;
6155ab48b4SAndrew Jeffery 	kcs_bmc->data_in_avail = false;
6255ab48b4SAndrew Jeffery 	kcs_bmc->data_in_idx = 0;
6355ab48b4SAndrew Jeffery }
6455ab48b4SAndrew Jeffery 
6555ab48b4SAndrew Jeffery static void kcs_bmc_ipmi_handle_data(struct kcs_bmc *kcs_bmc)
6655ab48b4SAndrew Jeffery {
6755ab48b4SAndrew Jeffery 	u8 data;
6855ab48b4SAndrew Jeffery 
6955ab48b4SAndrew Jeffery 	switch (kcs_bmc->phase) {
7055ab48b4SAndrew Jeffery 	case KCS_PHASE_WRITE_START:
7155ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
7255ab48b4SAndrew Jeffery 		fallthrough;
7355ab48b4SAndrew Jeffery 
7455ab48b4SAndrew Jeffery 	case KCS_PHASE_WRITE_DATA:
7555ab48b4SAndrew Jeffery 		if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
7655ab48b4SAndrew Jeffery 			set_state(kcs_bmc, WRITE_STATE);
7755ab48b4SAndrew Jeffery 			kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
7855ab48b4SAndrew Jeffery 			kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
7955ab48b4SAndrew Jeffery 						kcs_bmc_read_data(kcs_bmc);
8055ab48b4SAndrew Jeffery 		} else {
8155ab48b4SAndrew Jeffery 			kcs_bmc_ipmi_force_abort(kcs_bmc);
8255ab48b4SAndrew Jeffery 			kcs_bmc->error = KCS_LENGTH_ERROR;
8355ab48b4SAndrew Jeffery 		}
8455ab48b4SAndrew Jeffery 		break;
8555ab48b4SAndrew Jeffery 
8655ab48b4SAndrew Jeffery 	case KCS_PHASE_WRITE_END_CMD:
8755ab48b4SAndrew Jeffery 		if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
8855ab48b4SAndrew Jeffery 			set_state(kcs_bmc, READ_STATE);
8955ab48b4SAndrew Jeffery 			kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
9055ab48b4SAndrew Jeffery 						kcs_bmc_read_data(kcs_bmc);
9155ab48b4SAndrew Jeffery 			kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
9255ab48b4SAndrew Jeffery 			kcs_bmc->data_in_avail = true;
9355ab48b4SAndrew Jeffery 			wake_up_interruptible(&kcs_bmc->queue);
9455ab48b4SAndrew Jeffery 		} else {
9555ab48b4SAndrew Jeffery 			kcs_bmc_ipmi_force_abort(kcs_bmc);
9655ab48b4SAndrew Jeffery 			kcs_bmc->error = KCS_LENGTH_ERROR;
9755ab48b4SAndrew Jeffery 		}
9855ab48b4SAndrew Jeffery 		break;
9955ab48b4SAndrew Jeffery 
10055ab48b4SAndrew Jeffery 	case KCS_PHASE_READ:
10155ab48b4SAndrew Jeffery 		if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
10255ab48b4SAndrew Jeffery 			set_state(kcs_bmc, IDLE_STATE);
10355ab48b4SAndrew Jeffery 
10455ab48b4SAndrew Jeffery 		data = kcs_bmc_read_data(kcs_bmc);
10555ab48b4SAndrew Jeffery 		if (data != KCS_CMD_READ_BYTE) {
10655ab48b4SAndrew Jeffery 			set_state(kcs_bmc, ERROR_STATE);
10755ab48b4SAndrew Jeffery 			kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
10855ab48b4SAndrew Jeffery 			break;
10955ab48b4SAndrew Jeffery 		}
11055ab48b4SAndrew Jeffery 
11155ab48b4SAndrew Jeffery 		if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
11255ab48b4SAndrew Jeffery 			kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
11355ab48b4SAndrew Jeffery 			kcs_bmc->phase = KCS_PHASE_IDLE;
11455ab48b4SAndrew Jeffery 			break;
11555ab48b4SAndrew Jeffery 		}
11655ab48b4SAndrew Jeffery 
11755ab48b4SAndrew Jeffery 		kcs_bmc_write_data(kcs_bmc,
11855ab48b4SAndrew Jeffery 			kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
11955ab48b4SAndrew Jeffery 		break;
12055ab48b4SAndrew Jeffery 
12155ab48b4SAndrew Jeffery 	case KCS_PHASE_ABORT_ERROR1:
12255ab48b4SAndrew Jeffery 		set_state(kcs_bmc, READ_STATE);
12355ab48b4SAndrew Jeffery 		kcs_bmc_read_data(kcs_bmc);
12455ab48b4SAndrew Jeffery 		kcs_bmc_write_data(kcs_bmc, kcs_bmc->error);
12555ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
12655ab48b4SAndrew Jeffery 		break;
12755ab48b4SAndrew Jeffery 
12855ab48b4SAndrew Jeffery 	case KCS_PHASE_ABORT_ERROR2:
12955ab48b4SAndrew Jeffery 		set_state(kcs_bmc, IDLE_STATE);
13055ab48b4SAndrew Jeffery 		kcs_bmc_read_data(kcs_bmc);
13155ab48b4SAndrew Jeffery 		kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
13255ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_IDLE;
13355ab48b4SAndrew Jeffery 		break;
13455ab48b4SAndrew Jeffery 
13555ab48b4SAndrew Jeffery 	default:
13655ab48b4SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(kcs_bmc);
13755ab48b4SAndrew Jeffery 		break;
13855ab48b4SAndrew Jeffery 	}
13955ab48b4SAndrew Jeffery }
14055ab48b4SAndrew Jeffery 
14155ab48b4SAndrew Jeffery static void kcs_bmc_ipmi_handle_cmd(struct kcs_bmc *kcs_bmc)
14255ab48b4SAndrew Jeffery {
14355ab48b4SAndrew Jeffery 	u8 cmd;
14455ab48b4SAndrew Jeffery 
14555ab48b4SAndrew Jeffery 	set_state(kcs_bmc, WRITE_STATE);
14655ab48b4SAndrew Jeffery 	kcs_bmc_write_data(kcs_bmc, KCS_ZERO_DATA);
14755ab48b4SAndrew Jeffery 
14855ab48b4SAndrew Jeffery 	cmd = kcs_bmc_read_data(kcs_bmc);
14955ab48b4SAndrew Jeffery 	switch (cmd) {
15055ab48b4SAndrew Jeffery 	case KCS_CMD_WRITE_START:
15155ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_WRITE_START;
15255ab48b4SAndrew Jeffery 		kcs_bmc->error = KCS_NO_ERROR;
15355ab48b4SAndrew Jeffery 		kcs_bmc->data_in_avail = false;
15455ab48b4SAndrew Jeffery 		kcs_bmc->data_in_idx = 0;
15555ab48b4SAndrew Jeffery 		break;
15655ab48b4SAndrew Jeffery 
15755ab48b4SAndrew Jeffery 	case KCS_CMD_WRITE_END:
15855ab48b4SAndrew Jeffery 		if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
15955ab48b4SAndrew Jeffery 			kcs_bmc_ipmi_force_abort(kcs_bmc);
16055ab48b4SAndrew Jeffery 			break;
16155ab48b4SAndrew Jeffery 		}
16255ab48b4SAndrew Jeffery 
16355ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
16455ab48b4SAndrew Jeffery 		break;
16555ab48b4SAndrew Jeffery 
16655ab48b4SAndrew Jeffery 	case KCS_CMD_GET_STATUS_ABORT:
16755ab48b4SAndrew Jeffery 		if (kcs_bmc->error == KCS_NO_ERROR)
16855ab48b4SAndrew Jeffery 			kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
16955ab48b4SAndrew Jeffery 
17055ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
17155ab48b4SAndrew Jeffery 		kcs_bmc->data_in_avail = false;
17255ab48b4SAndrew Jeffery 		kcs_bmc->data_in_idx = 0;
17355ab48b4SAndrew Jeffery 		break;
17455ab48b4SAndrew Jeffery 
17555ab48b4SAndrew Jeffery 	default:
17655ab48b4SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(kcs_bmc);
17755ab48b4SAndrew Jeffery 		kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
17855ab48b4SAndrew Jeffery 		break;
17955ab48b4SAndrew Jeffery 	}
18055ab48b4SAndrew Jeffery }
18155ab48b4SAndrew Jeffery 
18255ab48b4SAndrew Jeffery int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc);
18355ab48b4SAndrew Jeffery int kcs_bmc_ipmi_event(struct kcs_bmc *kcs_bmc)
18455ab48b4SAndrew Jeffery {
18555ab48b4SAndrew Jeffery 	unsigned long flags;
18655ab48b4SAndrew Jeffery 	int ret = -ENODATA;
18755ab48b4SAndrew Jeffery 	u8 status;
18855ab48b4SAndrew Jeffery 
18955ab48b4SAndrew Jeffery 	spin_lock_irqsave(&kcs_bmc->lock, flags);
19055ab48b4SAndrew Jeffery 
19155ab48b4SAndrew Jeffery 	status = kcs_bmc_read_status(kcs_bmc);
19255ab48b4SAndrew Jeffery 	if (status & KCS_STATUS_IBF) {
19355ab48b4SAndrew Jeffery 		if (!kcs_bmc->running)
19455ab48b4SAndrew Jeffery 			kcs_bmc_ipmi_force_abort(kcs_bmc);
19555ab48b4SAndrew Jeffery 		else if (status & KCS_STATUS_CMD_DAT)
19655ab48b4SAndrew Jeffery 			kcs_bmc_ipmi_handle_cmd(kcs_bmc);
19755ab48b4SAndrew Jeffery 		else
19855ab48b4SAndrew Jeffery 			kcs_bmc_ipmi_handle_data(kcs_bmc);
19955ab48b4SAndrew Jeffery 
20055ab48b4SAndrew Jeffery 		ret = 0;
20155ab48b4SAndrew Jeffery 	}
20255ab48b4SAndrew Jeffery 
20355ab48b4SAndrew Jeffery 	spin_unlock_irqrestore(&kcs_bmc->lock, flags);
20455ab48b4SAndrew Jeffery 
20555ab48b4SAndrew Jeffery 	return ret;
20655ab48b4SAndrew Jeffery }
20755ab48b4SAndrew Jeffery EXPORT_SYMBOL(kcs_bmc_ipmi_event);
20855ab48b4SAndrew Jeffery 
20955ab48b4SAndrew Jeffery static inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
21055ab48b4SAndrew Jeffery {
21155ab48b4SAndrew Jeffery 	return container_of(filp->private_data, struct kcs_bmc, miscdev);
21255ab48b4SAndrew Jeffery }
21355ab48b4SAndrew Jeffery 
21455ab48b4SAndrew Jeffery static int kcs_bmc_ipmi_open(struct inode *inode, struct file *filp)
21555ab48b4SAndrew Jeffery {
21655ab48b4SAndrew Jeffery 	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
21755ab48b4SAndrew Jeffery 	int ret = 0;
21855ab48b4SAndrew Jeffery 
21955ab48b4SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
22055ab48b4SAndrew Jeffery 	if (!kcs_bmc->running)
22155ab48b4SAndrew Jeffery 		kcs_bmc->running = 1;
22255ab48b4SAndrew Jeffery 	else
22355ab48b4SAndrew Jeffery 		ret = -EBUSY;
22455ab48b4SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
22555ab48b4SAndrew Jeffery 
22655ab48b4SAndrew Jeffery 	return ret;
22755ab48b4SAndrew Jeffery }
22855ab48b4SAndrew Jeffery 
22955ab48b4SAndrew Jeffery static __poll_t kcs_bmc_ipmi_poll(struct file *filp, poll_table *wait)
23055ab48b4SAndrew Jeffery {
23155ab48b4SAndrew Jeffery 	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
23255ab48b4SAndrew Jeffery 	__poll_t mask = 0;
23355ab48b4SAndrew Jeffery 
23455ab48b4SAndrew Jeffery 	poll_wait(filp, &kcs_bmc->queue, wait);
23555ab48b4SAndrew Jeffery 
23655ab48b4SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
23755ab48b4SAndrew Jeffery 	if (kcs_bmc->data_in_avail)
23855ab48b4SAndrew Jeffery 		mask |= EPOLLIN;
23955ab48b4SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
24055ab48b4SAndrew Jeffery 
24155ab48b4SAndrew Jeffery 	return mask;
24255ab48b4SAndrew Jeffery }
24355ab48b4SAndrew Jeffery 
24455ab48b4SAndrew Jeffery static ssize_t kcs_bmc_ipmi_read(struct file *filp, char __user *buf,
24555ab48b4SAndrew Jeffery 			    size_t count, loff_t *ppos)
24655ab48b4SAndrew Jeffery {
24755ab48b4SAndrew Jeffery 	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
24855ab48b4SAndrew Jeffery 	bool data_avail;
24955ab48b4SAndrew Jeffery 	size_t data_len;
25055ab48b4SAndrew Jeffery 	ssize_t ret;
25155ab48b4SAndrew Jeffery 
25255ab48b4SAndrew Jeffery 	if (!(filp->f_flags & O_NONBLOCK))
25355ab48b4SAndrew Jeffery 		wait_event_interruptible(kcs_bmc->queue,
25455ab48b4SAndrew Jeffery 					 kcs_bmc->data_in_avail);
25555ab48b4SAndrew Jeffery 
25655ab48b4SAndrew Jeffery 	mutex_lock(&kcs_bmc->mutex);
25755ab48b4SAndrew Jeffery 
25855ab48b4SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
25955ab48b4SAndrew Jeffery 	data_avail = kcs_bmc->data_in_avail;
26055ab48b4SAndrew Jeffery 	if (data_avail) {
26155ab48b4SAndrew Jeffery 		data_len = kcs_bmc->data_in_idx;
26255ab48b4SAndrew Jeffery 		memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
26355ab48b4SAndrew Jeffery 	}
26455ab48b4SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
26555ab48b4SAndrew Jeffery 
26655ab48b4SAndrew Jeffery 	if (!data_avail) {
26755ab48b4SAndrew Jeffery 		ret = -EAGAIN;
26855ab48b4SAndrew Jeffery 		goto out_unlock;
26955ab48b4SAndrew Jeffery 	}
27055ab48b4SAndrew Jeffery 
27155ab48b4SAndrew Jeffery 	if (count < data_len) {
27255ab48b4SAndrew Jeffery 		pr_err("channel=%u with too large data : %zu\n",
27355ab48b4SAndrew Jeffery 			kcs_bmc->channel, data_len);
27455ab48b4SAndrew Jeffery 
27555ab48b4SAndrew Jeffery 		spin_lock_irq(&kcs_bmc->lock);
27655ab48b4SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(kcs_bmc);
27755ab48b4SAndrew Jeffery 		spin_unlock_irq(&kcs_bmc->lock);
27855ab48b4SAndrew Jeffery 
27955ab48b4SAndrew Jeffery 		ret = -EOVERFLOW;
28055ab48b4SAndrew Jeffery 		goto out_unlock;
28155ab48b4SAndrew Jeffery 	}
28255ab48b4SAndrew Jeffery 
28355ab48b4SAndrew Jeffery 	if (copy_to_user(buf, kcs_bmc->kbuffer, data_len)) {
28455ab48b4SAndrew Jeffery 		ret = -EFAULT;
28555ab48b4SAndrew Jeffery 		goto out_unlock;
28655ab48b4SAndrew Jeffery 	}
28755ab48b4SAndrew Jeffery 
28855ab48b4SAndrew Jeffery 	ret = data_len;
28955ab48b4SAndrew Jeffery 
29055ab48b4SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
29155ab48b4SAndrew Jeffery 	if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
29255ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_WAIT_READ;
29355ab48b4SAndrew Jeffery 		kcs_bmc->data_in_avail = false;
29455ab48b4SAndrew Jeffery 		kcs_bmc->data_in_idx = 0;
29555ab48b4SAndrew Jeffery 	} else {
29655ab48b4SAndrew Jeffery 		ret = -EAGAIN;
29755ab48b4SAndrew Jeffery 	}
29855ab48b4SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
29955ab48b4SAndrew Jeffery 
30055ab48b4SAndrew Jeffery out_unlock:
30155ab48b4SAndrew Jeffery 	mutex_unlock(&kcs_bmc->mutex);
30255ab48b4SAndrew Jeffery 
30355ab48b4SAndrew Jeffery 	return ret;
30455ab48b4SAndrew Jeffery }
30555ab48b4SAndrew Jeffery 
30655ab48b4SAndrew Jeffery static ssize_t kcs_bmc_ipmi_write(struct file *filp, const char __user *buf,
30755ab48b4SAndrew Jeffery 			     size_t count, loff_t *ppos)
30855ab48b4SAndrew Jeffery {
30955ab48b4SAndrew Jeffery 	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
31055ab48b4SAndrew Jeffery 	ssize_t ret;
31155ab48b4SAndrew Jeffery 
31255ab48b4SAndrew Jeffery 	/* a minimum response size '3' : netfn + cmd + ccode */
31355ab48b4SAndrew Jeffery 	if (count < 3 || count > KCS_MSG_BUFSIZ)
31455ab48b4SAndrew Jeffery 		return -EINVAL;
31555ab48b4SAndrew Jeffery 
31655ab48b4SAndrew Jeffery 	mutex_lock(&kcs_bmc->mutex);
31755ab48b4SAndrew Jeffery 
31855ab48b4SAndrew Jeffery 	if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
31955ab48b4SAndrew Jeffery 		ret = -EFAULT;
32055ab48b4SAndrew Jeffery 		goto out_unlock;
32155ab48b4SAndrew Jeffery 	}
32255ab48b4SAndrew Jeffery 
32355ab48b4SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
32455ab48b4SAndrew Jeffery 	if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
32555ab48b4SAndrew Jeffery 		kcs_bmc->phase = KCS_PHASE_READ;
32655ab48b4SAndrew Jeffery 		kcs_bmc->data_out_idx = 1;
32755ab48b4SAndrew Jeffery 		kcs_bmc->data_out_len = count;
32855ab48b4SAndrew Jeffery 		memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
32955ab48b4SAndrew Jeffery 		kcs_bmc_write_data(kcs_bmc, kcs_bmc->data_out[0]);
33055ab48b4SAndrew Jeffery 		ret = count;
33155ab48b4SAndrew Jeffery 	} else {
33255ab48b4SAndrew Jeffery 		ret = -EINVAL;
33355ab48b4SAndrew Jeffery 	}
33455ab48b4SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
33555ab48b4SAndrew Jeffery 
33655ab48b4SAndrew Jeffery out_unlock:
33755ab48b4SAndrew Jeffery 	mutex_unlock(&kcs_bmc->mutex);
33855ab48b4SAndrew Jeffery 
33955ab48b4SAndrew Jeffery 	return ret;
34055ab48b4SAndrew Jeffery }
34155ab48b4SAndrew Jeffery 
34255ab48b4SAndrew Jeffery static long kcs_bmc_ipmi_ioctl(struct file *filp, unsigned int cmd,
34355ab48b4SAndrew Jeffery 			  unsigned long arg)
34455ab48b4SAndrew Jeffery {
34555ab48b4SAndrew Jeffery 	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
34655ab48b4SAndrew Jeffery 	long ret = 0;
34755ab48b4SAndrew Jeffery 
34855ab48b4SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
34955ab48b4SAndrew Jeffery 
35055ab48b4SAndrew Jeffery 	switch (cmd) {
35155ab48b4SAndrew Jeffery 	case IPMI_BMC_IOCTL_SET_SMS_ATN:
35255ab48b4SAndrew Jeffery 		kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, KCS_STATUS_SMS_ATN);
35355ab48b4SAndrew Jeffery 		break;
35455ab48b4SAndrew Jeffery 
35555ab48b4SAndrew Jeffery 	case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
35655ab48b4SAndrew Jeffery 		kcs_bmc_update_status(kcs_bmc, KCS_STATUS_SMS_ATN, 0);
35755ab48b4SAndrew Jeffery 		break;
35855ab48b4SAndrew Jeffery 
35955ab48b4SAndrew Jeffery 	case IPMI_BMC_IOCTL_FORCE_ABORT:
36055ab48b4SAndrew Jeffery 		kcs_bmc_ipmi_force_abort(kcs_bmc);
36155ab48b4SAndrew Jeffery 		break;
36255ab48b4SAndrew Jeffery 
36355ab48b4SAndrew Jeffery 	default:
36455ab48b4SAndrew Jeffery 		ret = -EINVAL;
36555ab48b4SAndrew Jeffery 		break;
36655ab48b4SAndrew Jeffery 	}
36755ab48b4SAndrew Jeffery 
36855ab48b4SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
36955ab48b4SAndrew Jeffery 
37055ab48b4SAndrew Jeffery 	return ret;
37155ab48b4SAndrew Jeffery }
37255ab48b4SAndrew Jeffery 
37355ab48b4SAndrew Jeffery static int kcs_bmc_ipmi_release(struct inode *inode, struct file *filp)
37455ab48b4SAndrew Jeffery {
37555ab48b4SAndrew Jeffery 	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
37655ab48b4SAndrew Jeffery 
37755ab48b4SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
37855ab48b4SAndrew Jeffery 	kcs_bmc->running = 0;
37955ab48b4SAndrew Jeffery 	kcs_bmc_ipmi_force_abort(kcs_bmc);
38055ab48b4SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
38155ab48b4SAndrew Jeffery 
38255ab48b4SAndrew Jeffery 	return 0;
38355ab48b4SAndrew Jeffery }
38455ab48b4SAndrew Jeffery 
385*d7096970SAndrew Jeffery static const struct file_operations kcs_bmc_ipmi_fops = {
38655ab48b4SAndrew Jeffery 	.owner          = THIS_MODULE,
38755ab48b4SAndrew Jeffery 	.open           = kcs_bmc_ipmi_open,
38855ab48b4SAndrew Jeffery 	.read           = kcs_bmc_ipmi_read,
38955ab48b4SAndrew Jeffery 	.write          = kcs_bmc_ipmi_write,
39055ab48b4SAndrew Jeffery 	.release        = kcs_bmc_ipmi_release,
39155ab48b4SAndrew Jeffery 	.poll           = kcs_bmc_ipmi_poll,
39255ab48b4SAndrew Jeffery 	.unlocked_ioctl = kcs_bmc_ipmi_ioctl,
39355ab48b4SAndrew Jeffery };
39455ab48b4SAndrew Jeffery 
395*d7096970SAndrew Jeffery int kcs_bmc_ipmi_add_device(struct kcs_bmc *kcs_bmc);
396*d7096970SAndrew Jeffery int kcs_bmc_ipmi_add_device(struct kcs_bmc *kcs_bmc)
39755ab48b4SAndrew Jeffery {
398*d7096970SAndrew Jeffery 	int rc;
39955ab48b4SAndrew Jeffery 
40055ab48b4SAndrew Jeffery 	spin_lock_init(&kcs_bmc->lock);
40155ab48b4SAndrew Jeffery 	mutex_init(&kcs_bmc->mutex);
40255ab48b4SAndrew Jeffery 	init_waitqueue_head(&kcs_bmc->queue);
40355ab48b4SAndrew Jeffery 
404*d7096970SAndrew Jeffery 	kcs_bmc->data_in = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
405*d7096970SAndrew Jeffery 	kcs_bmc->data_out = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
406*d7096970SAndrew Jeffery 	kcs_bmc->kbuffer = devm_kmalloc(kcs_bmc->dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
40755ab48b4SAndrew Jeffery 
40855ab48b4SAndrew Jeffery 	kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
409*d7096970SAndrew Jeffery 	kcs_bmc->miscdev.name = devm_kasprintf(kcs_bmc->dev, GFP_KERNEL, "%s%u",
410*d7096970SAndrew Jeffery 					       DEVICE_NAME, kcs_bmc->channel);
41155ab48b4SAndrew Jeffery 	if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
41255ab48b4SAndrew Jeffery 	    !kcs_bmc->miscdev.name)
413*d7096970SAndrew Jeffery 		return -ENOMEM;
41455ab48b4SAndrew Jeffery 
415*d7096970SAndrew Jeffery 	kcs_bmc->miscdev.fops = &kcs_bmc_ipmi_fops;
416*d7096970SAndrew Jeffery 
417*d7096970SAndrew Jeffery 	rc = misc_register(&kcs_bmc->miscdev);
418*d7096970SAndrew Jeffery 	if (rc) {
419*d7096970SAndrew Jeffery 		dev_err(kcs_bmc->dev, "Unable to register device: %d\n", rc);
420*d7096970SAndrew Jeffery 		return rc;
42155ab48b4SAndrew Jeffery 	}
422*d7096970SAndrew Jeffery 
423*d7096970SAndrew Jeffery 	dev_info(kcs_bmc->dev, "Initialised IPMI client for channel %d", kcs_bmc->channel);
424*d7096970SAndrew Jeffery 
425*d7096970SAndrew Jeffery 	return 0;
426*d7096970SAndrew Jeffery }
427*d7096970SAndrew Jeffery EXPORT_SYMBOL(kcs_bmc_ipmi_add_device);
428*d7096970SAndrew Jeffery 
429*d7096970SAndrew Jeffery int kcs_bmc_ipmi_remove_device(struct kcs_bmc *kcs_bmc);
430*d7096970SAndrew Jeffery int kcs_bmc_ipmi_remove_device(struct kcs_bmc *kcs_bmc)
431*d7096970SAndrew Jeffery {
432*d7096970SAndrew Jeffery 	misc_deregister(&kcs_bmc->miscdev);
433*d7096970SAndrew Jeffery 
434*d7096970SAndrew Jeffery 	spin_lock_irq(&kcs_bmc->lock);
435*d7096970SAndrew Jeffery 	kcs_bmc->running = 0;
436*d7096970SAndrew Jeffery 	kcs_bmc_ipmi_force_abort(kcs_bmc);
437*d7096970SAndrew Jeffery 	spin_unlock_irq(&kcs_bmc->lock);
438*d7096970SAndrew Jeffery 
439*d7096970SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, kcs_bmc->kbuffer);
440*d7096970SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, kcs_bmc->data_out);
441*d7096970SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, kcs_bmc->data_in);
442*d7096970SAndrew Jeffery 	devm_kfree(kcs_bmc->dev, kcs_bmc);
443*d7096970SAndrew Jeffery 
444*d7096970SAndrew Jeffery 	return 0;
445*d7096970SAndrew Jeffery }
446*d7096970SAndrew Jeffery EXPORT_SYMBOL(kcs_bmc_ipmi_remove_device);
44755ab48b4SAndrew Jeffery 
44855ab48b4SAndrew Jeffery MODULE_LICENSE("GPL v2");
44955ab48b4SAndrew Jeffery MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
45055ab48b4SAndrew Jeffery MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
451