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