18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2015-2018, Intel Corporation.
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "kcs-bmc: " fmt
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/errno.h>
98c2ecf20Sopenharmony_ci#include <linux/io.h>
108c2ecf20Sopenharmony_ci#include <linux/ipmi_bmc.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/poll.h>
148c2ecf20Sopenharmony_ci#include <linux/sched.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include "kcs_bmc.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define DEVICE_NAME "ipmi-kcs"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define KCS_MSG_BUFSIZ    1000
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define KCS_ZERO_DATA     0
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/* IPMI 2.0 - Table 9-1, KCS Interface Status Register Bits */
278c2ecf20Sopenharmony_ci#define KCS_STATUS_STATE(state) (state << 6)
288c2ecf20Sopenharmony_ci#define KCS_STATUS_STATE_MASK   GENMASK(7, 6)
298c2ecf20Sopenharmony_ci#define KCS_STATUS_CMD_DAT      BIT(3)
308c2ecf20Sopenharmony_ci#define KCS_STATUS_SMS_ATN      BIT(2)
318c2ecf20Sopenharmony_ci#define KCS_STATUS_IBF          BIT(1)
328c2ecf20Sopenharmony_ci#define KCS_STATUS_OBF          BIT(0)
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/* IPMI 2.0 - Table 9-2, KCS Interface State Bits */
358c2ecf20Sopenharmony_cienum kcs_states {
368c2ecf20Sopenharmony_ci	IDLE_STATE  = 0,
378c2ecf20Sopenharmony_ci	READ_STATE  = 1,
388c2ecf20Sopenharmony_ci	WRITE_STATE = 2,
398c2ecf20Sopenharmony_ci	ERROR_STATE = 3,
408c2ecf20Sopenharmony_ci};
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/* IPMI 2.0 - Table 9-3, KCS Interface Control Codes */
438c2ecf20Sopenharmony_ci#define KCS_CMD_GET_STATUS_ABORT  0x60
448c2ecf20Sopenharmony_ci#define KCS_CMD_WRITE_START       0x61
458c2ecf20Sopenharmony_ci#define KCS_CMD_WRITE_END         0x62
468c2ecf20Sopenharmony_ci#define KCS_CMD_READ_BYTE         0x68
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic inline u8 read_data(struct kcs_bmc *kcs_bmc)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
518c2ecf20Sopenharmony_ci}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic inline void write_data(struct kcs_bmc *kcs_bmc, u8 data)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic inline u8 read_status(struct kcs_bmc *kcs_bmc)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	return kcs_bmc->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic inline void write_status(struct kcs_bmc *kcs_bmc, u8 data)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	kcs_bmc->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic void update_status_bits(struct kcs_bmc *kcs_bmc, u8 mask, u8 val)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	u8 tmp = read_status(kcs_bmc);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	tmp &= ~mask;
738c2ecf20Sopenharmony_ci	tmp |= val & mask;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	write_status(kcs_bmc, tmp);
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic inline void set_state(struct kcs_bmc *kcs_bmc, u8 state)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	update_status_bits(kcs_bmc, KCS_STATUS_STATE_MASK,
818c2ecf20Sopenharmony_ci					KCS_STATUS_STATE(state));
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic void kcs_force_abort(struct kcs_bmc *kcs_bmc)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	set_state(kcs_bmc, ERROR_STATE);
878c2ecf20Sopenharmony_ci	read_data(kcs_bmc);
888c2ecf20Sopenharmony_ci	write_data(kcs_bmc, KCS_ZERO_DATA);
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	kcs_bmc->phase = KCS_PHASE_ERROR;
918c2ecf20Sopenharmony_ci	kcs_bmc->data_in_avail = false;
928c2ecf20Sopenharmony_ci	kcs_bmc->data_in_idx = 0;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic void kcs_bmc_handle_data(struct kcs_bmc *kcs_bmc)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	u8 data;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	switch (kcs_bmc->phase) {
1008c2ecf20Sopenharmony_ci	case KCS_PHASE_WRITE_START:
1018c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_WRITE_DATA;
1028c2ecf20Sopenharmony_ci		fallthrough;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	case KCS_PHASE_WRITE_DATA:
1058c2ecf20Sopenharmony_ci		if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
1068c2ecf20Sopenharmony_ci			set_state(kcs_bmc, WRITE_STATE);
1078c2ecf20Sopenharmony_ci			write_data(kcs_bmc, KCS_ZERO_DATA);
1088c2ecf20Sopenharmony_ci			kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
1098c2ecf20Sopenharmony_ci						read_data(kcs_bmc);
1108c2ecf20Sopenharmony_ci		} else {
1118c2ecf20Sopenharmony_ci			kcs_force_abort(kcs_bmc);
1128c2ecf20Sopenharmony_ci			kcs_bmc->error = KCS_LENGTH_ERROR;
1138c2ecf20Sopenharmony_ci		}
1148c2ecf20Sopenharmony_ci		break;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	case KCS_PHASE_WRITE_END_CMD:
1178c2ecf20Sopenharmony_ci		if (kcs_bmc->data_in_idx < KCS_MSG_BUFSIZ) {
1188c2ecf20Sopenharmony_ci			set_state(kcs_bmc, READ_STATE);
1198c2ecf20Sopenharmony_ci			kcs_bmc->data_in[kcs_bmc->data_in_idx++] =
1208c2ecf20Sopenharmony_ci						read_data(kcs_bmc);
1218c2ecf20Sopenharmony_ci			kcs_bmc->phase = KCS_PHASE_WRITE_DONE;
1228c2ecf20Sopenharmony_ci			kcs_bmc->data_in_avail = true;
1238c2ecf20Sopenharmony_ci			wake_up_interruptible(&kcs_bmc->queue);
1248c2ecf20Sopenharmony_ci		} else {
1258c2ecf20Sopenharmony_ci			kcs_force_abort(kcs_bmc);
1268c2ecf20Sopenharmony_ci			kcs_bmc->error = KCS_LENGTH_ERROR;
1278c2ecf20Sopenharmony_ci		}
1288c2ecf20Sopenharmony_ci		break;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	case KCS_PHASE_READ:
1318c2ecf20Sopenharmony_ci		if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len)
1328c2ecf20Sopenharmony_ci			set_state(kcs_bmc, IDLE_STATE);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci		data = read_data(kcs_bmc);
1358c2ecf20Sopenharmony_ci		if (data != KCS_CMD_READ_BYTE) {
1368c2ecf20Sopenharmony_ci			set_state(kcs_bmc, ERROR_STATE);
1378c2ecf20Sopenharmony_ci			write_data(kcs_bmc, KCS_ZERO_DATA);
1388c2ecf20Sopenharmony_ci			break;
1398c2ecf20Sopenharmony_ci		}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci		if (kcs_bmc->data_out_idx == kcs_bmc->data_out_len) {
1428c2ecf20Sopenharmony_ci			write_data(kcs_bmc, KCS_ZERO_DATA);
1438c2ecf20Sopenharmony_ci			kcs_bmc->phase = KCS_PHASE_IDLE;
1448c2ecf20Sopenharmony_ci			break;
1458c2ecf20Sopenharmony_ci		}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci		write_data(kcs_bmc,
1488c2ecf20Sopenharmony_ci			kcs_bmc->data_out[kcs_bmc->data_out_idx++]);
1498c2ecf20Sopenharmony_ci		break;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	case KCS_PHASE_ABORT_ERROR1:
1528c2ecf20Sopenharmony_ci		set_state(kcs_bmc, READ_STATE);
1538c2ecf20Sopenharmony_ci		read_data(kcs_bmc);
1548c2ecf20Sopenharmony_ci		write_data(kcs_bmc, kcs_bmc->error);
1558c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_ABORT_ERROR2;
1568c2ecf20Sopenharmony_ci		break;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	case KCS_PHASE_ABORT_ERROR2:
1598c2ecf20Sopenharmony_ci		set_state(kcs_bmc, IDLE_STATE);
1608c2ecf20Sopenharmony_ci		read_data(kcs_bmc);
1618c2ecf20Sopenharmony_ci		write_data(kcs_bmc, KCS_ZERO_DATA);
1628c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_IDLE;
1638c2ecf20Sopenharmony_ci		break;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	default:
1668c2ecf20Sopenharmony_ci		kcs_force_abort(kcs_bmc);
1678c2ecf20Sopenharmony_ci		break;
1688c2ecf20Sopenharmony_ci	}
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic void kcs_bmc_handle_cmd(struct kcs_bmc *kcs_bmc)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	u8 cmd;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	set_state(kcs_bmc, WRITE_STATE);
1768c2ecf20Sopenharmony_ci	write_data(kcs_bmc, KCS_ZERO_DATA);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	cmd = read_data(kcs_bmc);
1798c2ecf20Sopenharmony_ci	switch (cmd) {
1808c2ecf20Sopenharmony_ci	case KCS_CMD_WRITE_START:
1818c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_WRITE_START;
1828c2ecf20Sopenharmony_ci		kcs_bmc->error = KCS_NO_ERROR;
1838c2ecf20Sopenharmony_ci		kcs_bmc->data_in_avail = false;
1848c2ecf20Sopenharmony_ci		kcs_bmc->data_in_idx = 0;
1858c2ecf20Sopenharmony_ci		break;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	case KCS_CMD_WRITE_END:
1888c2ecf20Sopenharmony_ci		if (kcs_bmc->phase != KCS_PHASE_WRITE_DATA) {
1898c2ecf20Sopenharmony_ci			kcs_force_abort(kcs_bmc);
1908c2ecf20Sopenharmony_ci			break;
1918c2ecf20Sopenharmony_ci		}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_WRITE_END_CMD;
1948c2ecf20Sopenharmony_ci		break;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	case KCS_CMD_GET_STATUS_ABORT:
1978c2ecf20Sopenharmony_ci		if (kcs_bmc->error == KCS_NO_ERROR)
1988c2ecf20Sopenharmony_ci			kcs_bmc->error = KCS_ABORTED_BY_COMMAND;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_ABORT_ERROR1;
2018c2ecf20Sopenharmony_ci		kcs_bmc->data_in_avail = false;
2028c2ecf20Sopenharmony_ci		kcs_bmc->data_in_idx = 0;
2038c2ecf20Sopenharmony_ci		break;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	default:
2068c2ecf20Sopenharmony_ci		kcs_force_abort(kcs_bmc);
2078c2ecf20Sopenharmony_ci		kcs_bmc->error = KCS_ILLEGAL_CONTROL_CODE;
2088c2ecf20Sopenharmony_ci		break;
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ciint kcs_bmc_handle_event(struct kcs_bmc *kcs_bmc)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	unsigned long flags;
2158c2ecf20Sopenharmony_ci	int ret = -ENODATA;
2168c2ecf20Sopenharmony_ci	u8 status;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	spin_lock_irqsave(&kcs_bmc->lock, flags);
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	status = read_status(kcs_bmc);
2218c2ecf20Sopenharmony_ci	if (status & KCS_STATUS_IBF) {
2228c2ecf20Sopenharmony_ci		if (!kcs_bmc->running)
2238c2ecf20Sopenharmony_ci			kcs_force_abort(kcs_bmc);
2248c2ecf20Sopenharmony_ci		else if (status & KCS_STATUS_CMD_DAT)
2258c2ecf20Sopenharmony_ci			kcs_bmc_handle_cmd(kcs_bmc);
2268c2ecf20Sopenharmony_ci		else
2278c2ecf20Sopenharmony_ci			kcs_bmc_handle_data(kcs_bmc);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci		ret = 0;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&kcs_bmc->lock, flags);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	return ret;
2358c2ecf20Sopenharmony_ci}
2368c2ecf20Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_handle_event);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic inline struct kcs_bmc *to_kcs_bmc(struct file *filp)
2398c2ecf20Sopenharmony_ci{
2408c2ecf20Sopenharmony_ci	return container_of(filp->private_data, struct kcs_bmc, miscdev);
2418c2ecf20Sopenharmony_ci}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_cistatic int kcs_bmc_open(struct inode *inode, struct file *filp)
2448c2ecf20Sopenharmony_ci{
2458c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
2468c2ecf20Sopenharmony_ci	int ret = 0;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
2498c2ecf20Sopenharmony_ci	if (!kcs_bmc->running)
2508c2ecf20Sopenharmony_ci		kcs_bmc->running = 1;
2518c2ecf20Sopenharmony_ci	else
2528c2ecf20Sopenharmony_ci		ret = -EBUSY;
2538c2ecf20Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	return ret;
2568c2ecf20Sopenharmony_ci}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_cistatic __poll_t kcs_bmc_poll(struct file *filp, poll_table *wait)
2598c2ecf20Sopenharmony_ci{
2608c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
2618c2ecf20Sopenharmony_ci	__poll_t mask = 0;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	poll_wait(filp, &kcs_bmc->queue, wait);
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
2668c2ecf20Sopenharmony_ci	if (kcs_bmc->data_in_avail)
2678c2ecf20Sopenharmony_ci		mask |= EPOLLIN;
2688c2ecf20Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	return mask;
2718c2ecf20Sopenharmony_ci}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_cistatic ssize_t kcs_bmc_read(struct file *filp, char __user *buf,
2748c2ecf20Sopenharmony_ci			    size_t count, loff_t *ppos)
2758c2ecf20Sopenharmony_ci{
2768c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
2778c2ecf20Sopenharmony_ci	bool data_avail;
2788c2ecf20Sopenharmony_ci	size_t data_len;
2798c2ecf20Sopenharmony_ci	ssize_t ret;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	if (!(filp->f_flags & O_NONBLOCK))
2828c2ecf20Sopenharmony_ci		wait_event_interruptible(kcs_bmc->queue,
2838c2ecf20Sopenharmony_ci					 kcs_bmc->data_in_avail);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	mutex_lock(&kcs_bmc->mutex);
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
2888c2ecf20Sopenharmony_ci	data_avail = kcs_bmc->data_in_avail;
2898c2ecf20Sopenharmony_ci	if (data_avail) {
2908c2ecf20Sopenharmony_ci		data_len = kcs_bmc->data_in_idx;
2918c2ecf20Sopenharmony_ci		memcpy(kcs_bmc->kbuffer, kcs_bmc->data_in, data_len);
2928c2ecf20Sopenharmony_ci	}
2938c2ecf20Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci	if (!data_avail) {
2968c2ecf20Sopenharmony_ci		ret = -EAGAIN;
2978c2ecf20Sopenharmony_ci		goto out_unlock;
2988c2ecf20Sopenharmony_ci	}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	if (count < data_len) {
3018c2ecf20Sopenharmony_ci		pr_err("channel=%u with too large data : %zu\n",
3028c2ecf20Sopenharmony_ci			kcs_bmc->channel, data_len);
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci		spin_lock_irq(&kcs_bmc->lock);
3058c2ecf20Sopenharmony_ci		kcs_force_abort(kcs_bmc);
3068c2ecf20Sopenharmony_ci		spin_unlock_irq(&kcs_bmc->lock);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci		ret = -EOVERFLOW;
3098c2ecf20Sopenharmony_ci		goto out_unlock;
3108c2ecf20Sopenharmony_ci	}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	if (copy_to_user(buf, kcs_bmc->kbuffer, data_len)) {
3138c2ecf20Sopenharmony_ci		ret = -EFAULT;
3148c2ecf20Sopenharmony_ci		goto out_unlock;
3158c2ecf20Sopenharmony_ci	}
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	ret = data_len;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
3208c2ecf20Sopenharmony_ci	if (kcs_bmc->phase == KCS_PHASE_WRITE_DONE) {
3218c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_WAIT_READ;
3228c2ecf20Sopenharmony_ci		kcs_bmc->data_in_avail = false;
3238c2ecf20Sopenharmony_ci		kcs_bmc->data_in_idx = 0;
3248c2ecf20Sopenharmony_ci	} else {
3258c2ecf20Sopenharmony_ci		ret = -EAGAIN;
3268c2ecf20Sopenharmony_ci	}
3278c2ecf20Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ciout_unlock:
3308c2ecf20Sopenharmony_ci	mutex_unlock(&kcs_bmc->mutex);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	return ret;
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic ssize_t kcs_bmc_write(struct file *filp, const char __user *buf,
3368c2ecf20Sopenharmony_ci			     size_t count, loff_t *ppos)
3378c2ecf20Sopenharmony_ci{
3388c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
3398c2ecf20Sopenharmony_ci	ssize_t ret;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	/* a minimum response size '3' : netfn + cmd + ccode */
3428c2ecf20Sopenharmony_ci	if (count < 3 || count > KCS_MSG_BUFSIZ)
3438c2ecf20Sopenharmony_ci		return -EINVAL;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	mutex_lock(&kcs_bmc->mutex);
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	if (copy_from_user(kcs_bmc->kbuffer, buf, count)) {
3488c2ecf20Sopenharmony_ci		ret = -EFAULT;
3498c2ecf20Sopenharmony_ci		goto out_unlock;
3508c2ecf20Sopenharmony_ci	}
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
3538c2ecf20Sopenharmony_ci	if (kcs_bmc->phase == KCS_PHASE_WAIT_READ) {
3548c2ecf20Sopenharmony_ci		kcs_bmc->phase = KCS_PHASE_READ;
3558c2ecf20Sopenharmony_ci		kcs_bmc->data_out_idx = 1;
3568c2ecf20Sopenharmony_ci		kcs_bmc->data_out_len = count;
3578c2ecf20Sopenharmony_ci		memcpy(kcs_bmc->data_out, kcs_bmc->kbuffer, count);
3588c2ecf20Sopenharmony_ci		write_data(kcs_bmc, kcs_bmc->data_out[0]);
3598c2ecf20Sopenharmony_ci		ret = count;
3608c2ecf20Sopenharmony_ci	} else {
3618c2ecf20Sopenharmony_ci		ret = -EINVAL;
3628c2ecf20Sopenharmony_ci	}
3638c2ecf20Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ciout_unlock:
3668c2ecf20Sopenharmony_ci	mutex_unlock(&kcs_bmc->mutex);
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	return ret;
3698c2ecf20Sopenharmony_ci}
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_cistatic long kcs_bmc_ioctl(struct file *filp, unsigned int cmd,
3728c2ecf20Sopenharmony_ci			  unsigned long arg)
3738c2ecf20Sopenharmony_ci{
3748c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
3758c2ecf20Sopenharmony_ci	long ret = 0;
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	switch (cmd) {
3808c2ecf20Sopenharmony_ci	case IPMI_BMC_IOCTL_SET_SMS_ATN:
3818c2ecf20Sopenharmony_ci		update_status_bits(kcs_bmc, KCS_STATUS_SMS_ATN,
3828c2ecf20Sopenharmony_ci				   KCS_STATUS_SMS_ATN);
3838c2ecf20Sopenharmony_ci		break;
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_ci	case IPMI_BMC_IOCTL_CLEAR_SMS_ATN:
3868c2ecf20Sopenharmony_ci		update_status_bits(kcs_bmc, KCS_STATUS_SMS_ATN,
3878c2ecf20Sopenharmony_ci				   0);
3888c2ecf20Sopenharmony_ci		break;
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	case IPMI_BMC_IOCTL_FORCE_ABORT:
3918c2ecf20Sopenharmony_ci		kcs_force_abort(kcs_bmc);
3928c2ecf20Sopenharmony_ci		break;
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	default:
3958c2ecf20Sopenharmony_ci		ret = -EINVAL;
3968c2ecf20Sopenharmony_ci		break;
3978c2ecf20Sopenharmony_ci	}
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	return ret;
4028c2ecf20Sopenharmony_ci}
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_cistatic int kcs_bmc_release(struct inode *inode, struct file *filp)
4058c2ecf20Sopenharmony_ci{
4068c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc = to_kcs_bmc(filp);
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
4098c2ecf20Sopenharmony_ci	kcs_bmc->running = 0;
4108c2ecf20Sopenharmony_ci	kcs_force_abort(kcs_bmc);
4118c2ecf20Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci	return 0;
4148c2ecf20Sopenharmony_ci}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_cistatic const struct file_operations kcs_bmc_fops = {
4178c2ecf20Sopenharmony_ci	.owner          = THIS_MODULE,
4188c2ecf20Sopenharmony_ci	.open           = kcs_bmc_open,
4198c2ecf20Sopenharmony_ci	.read           = kcs_bmc_read,
4208c2ecf20Sopenharmony_ci	.write          = kcs_bmc_write,
4218c2ecf20Sopenharmony_ci	.release        = kcs_bmc_release,
4228c2ecf20Sopenharmony_ci	.poll           = kcs_bmc_poll,
4238c2ecf20Sopenharmony_ci	.unlocked_ioctl = kcs_bmc_ioctl,
4248c2ecf20Sopenharmony_ci};
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_cistruct kcs_bmc *kcs_bmc_alloc(struct device *dev, int sizeof_priv, u32 channel)
4278c2ecf20Sopenharmony_ci{
4288c2ecf20Sopenharmony_ci	struct kcs_bmc *kcs_bmc;
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ci	kcs_bmc = devm_kzalloc(dev, sizeof(*kcs_bmc) + sizeof_priv, GFP_KERNEL);
4318c2ecf20Sopenharmony_ci	if (!kcs_bmc)
4328c2ecf20Sopenharmony_ci		return NULL;
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_ci	spin_lock_init(&kcs_bmc->lock);
4358c2ecf20Sopenharmony_ci	kcs_bmc->channel = channel;
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	mutex_init(&kcs_bmc->mutex);
4388c2ecf20Sopenharmony_ci	init_waitqueue_head(&kcs_bmc->queue);
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci	kcs_bmc->data_in = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
4418c2ecf20Sopenharmony_ci	kcs_bmc->data_out = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
4428c2ecf20Sopenharmony_ci	kcs_bmc->kbuffer = devm_kmalloc(dev, KCS_MSG_BUFSIZ, GFP_KERNEL);
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	kcs_bmc->miscdev.minor = MISC_DYNAMIC_MINOR;
4458c2ecf20Sopenharmony_ci	kcs_bmc->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s%u",
4468c2ecf20Sopenharmony_ci					       DEVICE_NAME, channel);
4478c2ecf20Sopenharmony_ci	if (!kcs_bmc->data_in || !kcs_bmc->data_out || !kcs_bmc->kbuffer ||
4488c2ecf20Sopenharmony_ci	    !kcs_bmc->miscdev.name)
4498c2ecf20Sopenharmony_ci		return NULL;
4508c2ecf20Sopenharmony_ci	kcs_bmc->miscdev.fops = &kcs_bmc_fops;
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	return kcs_bmc;
4538c2ecf20Sopenharmony_ci}
4548c2ecf20Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_alloc);
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
4578c2ecf20Sopenharmony_ciMODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
4588c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
459