cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

kcs_bmc_serio.c (3803B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/* Copyright (c) 2021 IBM Corp. */
      3
      4#include <linux/delay.h>
      5#include <linux/device.h>
      6#include <linux/errno.h>
      7#include <linux/list.h>
      8#include <linux/module.h>
      9#include <linux/sched/signal.h>
     10#include <linux/serio.h>
     11#include <linux/slab.h>
     12
     13#include "kcs_bmc_client.h"
     14
     15struct kcs_bmc_serio {
     16	struct list_head entry;
     17
     18	struct kcs_bmc_client client;
     19	struct serio *port;
     20
     21	spinlock_t lock;
     22};
     23
     24static inline struct kcs_bmc_serio *client_to_kcs_bmc_serio(struct kcs_bmc_client *client)
     25{
     26	return container_of(client, struct kcs_bmc_serio, client);
     27}
     28
     29static irqreturn_t kcs_bmc_serio_event(struct kcs_bmc_client *client)
     30{
     31	struct kcs_bmc_serio *priv;
     32	u8 handled = IRQ_NONE;
     33	u8 status;
     34
     35	priv = client_to_kcs_bmc_serio(client);
     36
     37	spin_lock(&priv->lock);
     38
     39	status = kcs_bmc_read_status(client->dev);
     40
     41	if (status & KCS_BMC_STR_IBF)
     42		handled = serio_interrupt(priv->port, kcs_bmc_read_data(client->dev), 0);
     43
     44	spin_unlock(&priv->lock);
     45
     46	return handled;
     47}
     48
     49static const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = {
     50	.event = kcs_bmc_serio_event,
     51};
     52
     53static int kcs_bmc_serio_open(struct serio *port)
     54{
     55	struct kcs_bmc_serio *priv = port->port_data;
     56
     57	return kcs_bmc_enable_device(priv->client.dev, &priv->client);
     58}
     59
     60static void kcs_bmc_serio_close(struct serio *port)
     61{
     62	struct kcs_bmc_serio *priv = port->port_data;
     63
     64	kcs_bmc_disable_device(priv->client.dev, &priv->client);
     65}
     66
     67static DEFINE_SPINLOCK(kcs_bmc_serio_instances_lock);
     68static LIST_HEAD(kcs_bmc_serio_instances);
     69
     70static int kcs_bmc_serio_add_device(struct kcs_bmc_device *kcs_bmc)
     71{
     72	struct kcs_bmc_serio *priv;
     73	struct serio *port;
     74
     75	priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
     76	if (!priv)
     77		return -ENOMEM;
     78
     79	/* Use kzalloc() as the allocation is cleaned up with kfree() via serio_unregister_port() */
     80	port = kzalloc(sizeof(*port), GFP_KERNEL);
     81	if (!port)
     82		return -ENOMEM;
     83
     84	port->id.type = SERIO_8042;
     85	port->open = kcs_bmc_serio_open;
     86	port->close = kcs_bmc_serio_close;
     87	port->port_data = priv;
     88	port->dev.parent = kcs_bmc->dev;
     89
     90	spin_lock_init(&priv->lock);
     91	priv->port = port;
     92	priv->client.dev = kcs_bmc;
     93	priv->client.ops = &kcs_bmc_serio_client_ops;
     94
     95	spin_lock_irq(&kcs_bmc_serio_instances_lock);
     96	list_add(&priv->entry, &kcs_bmc_serio_instances);
     97	spin_unlock_irq(&kcs_bmc_serio_instances_lock);
     98
     99	serio_register_port(port);
    100
    101	dev_info(kcs_bmc->dev, "Initialised serio client for channel %d", kcs_bmc->channel);
    102
    103	return 0;
    104}
    105
    106static int kcs_bmc_serio_remove_device(struct kcs_bmc_device *kcs_bmc)
    107{
    108	struct kcs_bmc_serio *priv = NULL, *pos;
    109
    110	spin_lock_irq(&kcs_bmc_serio_instances_lock);
    111	list_for_each_entry(pos, &kcs_bmc_serio_instances, entry) {
    112		if (pos->client.dev == kcs_bmc) {
    113			priv = pos;
    114			list_del(&pos->entry);
    115			break;
    116		}
    117	}
    118	spin_unlock_irq(&kcs_bmc_serio_instances_lock);
    119
    120	if (!priv)
    121		return -ENODEV;
    122
    123	/* kfree()s priv->port via put_device() */
    124	serio_unregister_port(priv->port);
    125
    126	/* Ensure the IBF IRQ is disabled if we were the active client */
    127	kcs_bmc_disable_device(kcs_bmc, &priv->client);
    128
    129	devm_kfree(priv->client.dev->dev, priv);
    130
    131	return 0;
    132}
    133
    134static const struct kcs_bmc_driver_ops kcs_bmc_serio_driver_ops = {
    135	.add_device = kcs_bmc_serio_add_device,
    136	.remove_device = kcs_bmc_serio_remove_device,
    137};
    138
    139static struct kcs_bmc_driver kcs_bmc_serio_driver = {
    140	.ops = &kcs_bmc_serio_driver_ops,
    141};
    142
    143static int kcs_bmc_serio_init(void)
    144{
    145	kcs_bmc_register_driver(&kcs_bmc_serio_driver);
    146
    147	return 0;
    148}
    149module_init(kcs_bmc_serio_init);
    150
    151static void kcs_bmc_serio_exit(void)
    152{
    153	kcs_bmc_unregister_driver(&kcs_bmc_serio_driver);
    154}
    155module_exit(kcs_bmc_serio_exit);
    156
    157MODULE_LICENSE("GPL v2");
    158MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
    159MODULE_DESCRIPTION("Adapter driver for serio access to BMC KCS devices");