b02bb79eee
As kcs_bmc_handle_event() is executed inside both a timer and a hardirq, it should disable irq before lock acquisition otherwise deadlock could happen if the timmer is preemtped by the irq. Possible deadlock scenario: aspeed_kcs_check_obe() (timer) -> kcs_bmc_handle_event() -> spin_lock(&kcs_bmc->lock) <irq interruption> -> aspeed_kcs_irq() -> kcs_bmc_handle_event() -> spin_lock(&kcs_bmc->lock) (deadlock here) This flaw was found using an experimental static analysis tool we are developing for irq-related deadlock. The tentative patch fix the potential deadlock by spin_lock_irqsave() Signed-off-by: Chengfeng Ye <dg573847474@gmail.com> Message-Id: <20230627152449.36093-1-dg573847474@gmail.com> Signed-off-by: Corey Minyard <minyard@acm.org>
191 lines
4.7 KiB
C
191 lines
4.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2015-2018, Intel Corporation.
|
|
* Copyright (c) 2021, IBM Corp.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
|
|
#include "kcs_bmc.h"
|
|
|
|
/* Implement both the device and client interfaces here */
|
|
#include "kcs_bmc_device.h"
|
|
#include "kcs_bmc_client.h"
|
|
|
|
/* Record registered devices and drivers */
|
|
static DEFINE_MUTEX(kcs_bmc_lock);
|
|
static LIST_HEAD(kcs_bmc_devices);
|
|
static LIST_HEAD(kcs_bmc_drivers);
|
|
|
|
/* Consumer data access */
|
|
|
|
u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
|
|
{
|
|
return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_read_data);
|
|
|
|
void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data)
|
|
{
|
|
kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_write_data);
|
|
|
|
u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc)
|
|
{
|
|
return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_read_status);
|
|
|
|
void kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data)
|
|
{
|
|
kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_write_status);
|
|
|
|
void kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val)
|
|
{
|
|
kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_update_status);
|
|
|
|
irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
|
|
{
|
|
struct kcs_bmc_client *client;
|
|
irqreturn_t rc = IRQ_NONE;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&kcs_bmc->lock, flags);
|
|
client = kcs_bmc->client;
|
|
if (client)
|
|
rc = client->ops->event(client);
|
|
spin_unlock_irqrestore(&kcs_bmc->lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_handle_event);
|
|
|
|
int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
|
|
{
|
|
int rc;
|
|
|
|
spin_lock_irq(&kcs_bmc->lock);
|
|
if (kcs_bmc->client) {
|
|
rc = -EBUSY;
|
|
} else {
|
|
u8 mask = KCS_BMC_EVENT_TYPE_IBF;
|
|
|
|
kcs_bmc->client = client;
|
|
kcs_bmc_update_event_mask(kcs_bmc, mask, mask);
|
|
rc = 0;
|
|
}
|
|
spin_unlock_irq(&kcs_bmc->lock);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_enable_device);
|
|
|
|
void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
|
|
{
|
|
spin_lock_irq(&kcs_bmc->lock);
|
|
if (client == kcs_bmc->client) {
|
|
u8 mask = KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE;
|
|
|
|
kcs_bmc_update_event_mask(kcs_bmc, mask, 0);
|
|
kcs_bmc->client = NULL;
|
|
}
|
|
spin_unlock_irq(&kcs_bmc->lock);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_disable_device);
|
|
|
|
int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
|
|
{
|
|
struct kcs_bmc_driver *drv;
|
|
int error = 0;
|
|
int rc;
|
|
|
|
spin_lock_init(&kcs_bmc->lock);
|
|
kcs_bmc->client = NULL;
|
|
|
|
mutex_lock(&kcs_bmc_lock);
|
|
list_add(&kcs_bmc->entry, &kcs_bmc_devices);
|
|
list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
|
|
rc = drv->ops->add_device(kcs_bmc);
|
|
if (!rc)
|
|
continue;
|
|
|
|
dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
|
|
kcs_bmc->channel, rc);
|
|
error = rc;
|
|
}
|
|
mutex_unlock(&kcs_bmc_lock);
|
|
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_add_device);
|
|
|
|
void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
|
|
{
|
|
struct kcs_bmc_driver *drv;
|
|
int rc;
|
|
|
|
mutex_lock(&kcs_bmc_lock);
|
|
list_del(&kcs_bmc->entry);
|
|
list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
|
|
rc = drv->ops->remove_device(kcs_bmc);
|
|
if (rc)
|
|
dev_err(kcs_bmc->dev, "Failed to remove chardev for KCS channel %d: %d",
|
|
kcs_bmc->channel, rc);
|
|
}
|
|
mutex_unlock(&kcs_bmc_lock);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_remove_device);
|
|
|
|
void kcs_bmc_register_driver(struct kcs_bmc_driver *drv)
|
|
{
|
|
struct kcs_bmc_device *kcs_bmc;
|
|
int rc;
|
|
|
|
mutex_lock(&kcs_bmc_lock);
|
|
list_add(&drv->entry, &kcs_bmc_drivers);
|
|
list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
|
|
rc = drv->ops->add_device(kcs_bmc);
|
|
if (rc)
|
|
dev_err(kcs_bmc->dev, "Failed to add driver for KCS channel %d: %d",
|
|
kcs_bmc->channel, rc);
|
|
}
|
|
mutex_unlock(&kcs_bmc_lock);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_register_driver);
|
|
|
|
void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv)
|
|
{
|
|
struct kcs_bmc_device *kcs_bmc;
|
|
int rc;
|
|
|
|
mutex_lock(&kcs_bmc_lock);
|
|
list_del(&drv->entry);
|
|
list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
|
|
rc = drv->ops->remove_device(kcs_bmc);
|
|
if (rc)
|
|
dev_err(kcs_bmc->dev, "Failed to remove driver for KCS channel %d: %d",
|
|
kcs_bmc->channel, rc);
|
|
}
|
|
mutex_unlock(&kcs_bmc_lock);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_unregister_driver);
|
|
|
|
void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events)
|
|
{
|
|
kcs_bmc->ops->irq_mask_update(kcs_bmc, mask, events);
|
|
}
|
|
EXPORT_SYMBOL(kcs_bmc_update_event_mask);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
|
|
MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
|
|
MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
|