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