mirror of https://github.com/torvalds/linux.git
1020 lines
26 KiB
C
1020 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* RISC-V SBI Message Proxy (MPXY) mailbox controller driver
|
|
*
|
|
* Copyright (C) 2025 Ventana Micro Systems Inc.
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/irqchip/riscv-imsic.h>
|
|
#include <linux/mailbox_controller.h>
|
|
#include <linux/mailbox/riscv-rpmi-message.h>
|
|
#include <linux/minmax.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/msi.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/string.h>
|
|
#include <linux/types.h>
|
|
#include <asm/byteorder.h>
|
|
#include <asm/sbi.h>
|
|
|
|
/* ====== SBI MPXY extension data structures ====== */
|
|
|
|
/* SBI MPXY MSI related channel attributes */
|
|
struct sbi_mpxy_msi_info {
|
|
/* Lower 32-bits of the MSI target address */
|
|
u32 msi_addr_lo;
|
|
/* Upper 32-bits of the MSI target address */
|
|
u32 msi_addr_hi;
|
|
/* MSI data value */
|
|
u32 msi_data;
|
|
};
|
|
|
|
/*
|
|
* SBI MPXY standard channel attributes.
|
|
*
|
|
* NOTE: The sequence of attribute fields are as-per the
|
|
* defined sequence in the attribute table in spec (or
|
|
* as-per the enum sbi_mpxy_attribute_id).
|
|
*/
|
|
struct sbi_mpxy_channel_attrs {
|
|
/* Message protocol ID */
|
|
u32 msg_proto_id;
|
|
/* Message protocol version */
|
|
u32 msg_proto_version;
|
|
/* Message protocol maximum message length */
|
|
u32 msg_max_len;
|
|
/* Message protocol message send timeout in microseconds */
|
|
u32 msg_send_timeout;
|
|
/* Message protocol message completion timeout in microseconds */
|
|
u32 msg_completion_timeout;
|
|
/* Bit array for channel capabilities */
|
|
u32 capability;
|
|
/* SSE event ID */
|
|
u32 sse_event_id;
|
|
/* MSI enable/disable control knob */
|
|
u32 msi_control;
|
|
/* Channel MSI info */
|
|
struct sbi_mpxy_msi_info msi_info;
|
|
/* Events state control */
|
|
u32 events_state_ctrl;
|
|
};
|
|
|
|
/*
|
|
* RPMI specific SBI MPXY channel attributes.
|
|
*
|
|
* NOTE: The sequence of attribute fields are as-per the
|
|
* defined sequence in the attribute table in spec (or
|
|
* as-per the enum sbi_mpxy_rpmi_attribute_id).
|
|
*/
|
|
struct sbi_mpxy_rpmi_channel_attrs {
|
|
/* RPMI service group ID */
|
|
u32 servicegroup_id;
|
|
/* RPMI service group version */
|
|
u32 servicegroup_version;
|
|
/* RPMI implementation ID */
|
|
u32 impl_id;
|
|
/* RPMI implementation version */
|
|
u32 impl_version;
|
|
};
|
|
|
|
/* SBI MPXY channel IDs data in shared memory */
|
|
struct sbi_mpxy_channel_ids_data {
|
|
/* Remaining number of channel ids */
|
|
__le32 remaining;
|
|
/* Returned channel ids in current function call */
|
|
__le32 returned;
|
|
/* Returned channel id array */
|
|
__le32 channel_array[];
|
|
};
|
|
|
|
/* SBI MPXY notification data in shared memory */
|
|
struct sbi_mpxy_notification_data {
|
|
/* Remaining number of notification events */
|
|
__le32 remaining;
|
|
/* Number of notification events returned */
|
|
__le32 returned;
|
|
/* Number of notification events lost */
|
|
__le32 lost;
|
|
/* Reserved for future use */
|
|
__le32 reserved;
|
|
/* Returned channel id array */
|
|
u8 events_data[];
|
|
};
|
|
|
|
/* ====== MPXY data structures & helper routines ====== */
|
|
|
|
/* MPXY Per-CPU or local context */
|
|
struct mpxy_local {
|
|
/* Shared memory base address */
|
|
void *shmem;
|
|
/* Shared memory physical address */
|
|
phys_addr_t shmem_phys_addr;
|
|
/* Flag representing whether shared memory is active or not */
|
|
bool shmem_active;
|
|
};
|
|
|
|
static DEFINE_PER_CPU(struct mpxy_local, mpxy_local);
|
|
static unsigned long mpxy_shmem_size;
|
|
static bool mpxy_shmem_init_done;
|
|
|
|
static int mpxy_get_channel_count(u32 *channel_count)
|
|
{
|
|
struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local);
|
|
struct sbi_mpxy_channel_ids_data *sdata = mpxy->shmem;
|
|
u32 remaining, returned;
|
|
struct sbiret sret;
|
|
|
|
if (!mpxy->shmem_active)
|
|
return -ENODEV;
|
|
if (!channel_count)
|
|
return -EINVAL;
|
|
|
|
get_cpu();
|
|
|
|
/* Get the remaining and returned fields to calculate total */
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_CHANNEL_IDS,
|
|
0, 0, 0, 0, 0, 0);
|
|
if (sret.error)
|
|
goto err_put_cpu;
|
|
|
|
remaining = le32_to_cpu(sdata->remaining);
|
|
returned = le32_to_cpu(sdata->returned);
|
|
*channel_count = remaining + returned;
|
|
|
|
err_put_cpu:
|
|
put_cpu();
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
static int mpxy_get_channel_ids(u32 channel_count, u32 *channel_ids)
|
|
{
|
|
struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local);
|
|
struct sbi_mpxy_channel_ids_data *sdata = mpxy->shmem;
|
|
u32 remaining, returned, count, start_index = 0;
|
|
struct sbiret sret;
|
|
|
|
if (!mpxy->shmem_active)
|
|
return -ENODEV;
|
|
if (!channel_count || !channel_ids)
|
|
return -EINVAL;
|
|
|
|
get_cpu();
|
|
|
|
do {
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_CHANNEL_IDS,
|
|
start_index, 0, 0, 0, 0, 0);
|
|
if (sret.error)
|
|
goto err_put_cpu;
|
|
|
|
remaining = le32_to_cpu(sdata->remaining);
|
|
returned = le32_to_cpu(sdata->returned);
|
|
|
|
count = returned < (channel_count - start_index) ?
|
|
returned : (channel_count - start_index);
|
|
memcpy_from_le32(&channel_ids[start_index], sdata->channel_array, count);
|
|
start_index += count;
|
|
} while (remaining && start_index < channel_count);
|
|
|
|
err_put_cpu:
|
|
put_cpu();
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
static int mpxy_read_attrs(u32 channel_id, u32 base_attrid, u32 attr_count,
|
|
u32 *attrs_buf)
|
|
{
|
|
struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local);
|
|
struct sbiret sret;
|
|
|
|
if (!mpxy->shmem_active)
|
|
return -ENODEV;
|
|
if (!attr_count || !attrs_buf)
|
|
return -EINVAL;
|
|
|
|
get_cpu();
|
|
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_READ_ATTRS,
|
|
channel_id, base_attrid, attr_count, 0, 0, 0);
|
|
if (sret.error)
|
|
goto err_put_cpu;
|
|
|
|
memcpy_from_le32(attrs_buf, (__le32 *)mpxy->shmem, attr_count);
|
|
|
|
err_put_cpu:
|
|
put_cpu();
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
static int mpxy_write_attrs(u32 channel_id, u32 base_attrid, u32 attr_count,
|
|
u32 *attrs_buf)
|
|
{
|
|
struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local);
|
|
struct sbiret sret;
|
|
|
|
if (!mpxy->shmem_active)
|
|
return -ENODEV;
|
|
if (!attr_count || !attrs_buf)
|
|
return -EINVAL;
|
|
|
|
get_cpu();
|
|
|
|
memcpy_to_le32((__le32 *)mpxy->shmem, attrs_buf, attr_count);
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_WRITE_ATTRS,
|
|
channel_id, base_attrid, attr_count, 0, 0, 0);
|
|
|
|
put_cpu();
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
static int mpxy_send_message_with_resp(u32 channel_id, u32 msg_id,
|
|
void *tx, unsigned long tx_len,
|
|
void *rx, unsigned long max_rx_len,
|
|
unsigned long *rx_len)
|
|
{
|
|
struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local);
|
|
unsigned long rx_bytes;
|
|
struct sbiret sret;
|
|
|
|
if (!mpxy->shmem_active)
|
|
return -ENODEV;
|
|
if (!tx && tx_len)
|
|
return -EINVAL;
|
|
|
|
get_cpu();
|
|
|
|
/* Message protocols allowed to have no data in messages */
|
|
if (tx_len)
|
|
memcpy(mpxy->shmem, tx, tx_len);
|
|
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_SEND_MSG_WITH_RESP,
|
|
channel_id, msg_id, tx_len, 0, 0, 0);
|
|
if (rx && !sret.error) {
|
|
rx_bytes = sret.value;
|
|
if (rx_bytes > max_rx_len) {
|
|
put_cpu();
|
|
return -ENOSPC;
|
|
}
|
|
|
|
memcpy(rx, mpxy->shmem, rx_bytes);
|
|
if (rx_len)
|
|
*rx_len = rx_bytes;
|
|
}
|
|
|
|
put_cpu();
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
static int mpxy_send_message_without_resp(u32 channel_id, u32 msg_id,
|
|
void *tx, unsigned long tx_len)
|
|
{
|
|
struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local);
|
|
struct sbiret sret;
|
|
|
|
if (!mpxy->shmem_active)
|
|
return -ENODEV;
|
|
if (!tx && tx_len)
|
|
return -EINVAL;
|
|
|
|
get_cpu();
|
|
|
|
/* Message protocols allowed to have no data in messages */
|
|
if (tx_len)
|
|
memcpy(mpxy->shmem, tx, tx_len);
|
|
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_SEND_MSG_WITHOUT_RESP,
|
|
channel_id, msg_id, tx_len, 0, 0, 0);
|
|
|
|
put_cpu();
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
static int mpxy_get_notifications(u32 channel_id,
|
|
struct sbi_mpxy_notification_data *notif_data,
|
|
unsigned long *events_data_len)
|
|
{
|
|
struct mpxy_local *mpxy = this_cpu_ptr(&mpxy_local);
|
|
struct sbiret sret;
|
|
|
|
if (!mpxy->shmem_active)
|
|
return -ENODEV;
|
|
if (!notif_data || !events_data_len)
|
|
return -EINVAL;
|
|
|
|
get_cpu();
|
|
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_NOTIFICATION_EVENTS,
|
|
channel_id, 0, 0, 0, 0, 0);
|
|
if (sret.error)
|
|
goto err_put_cpu;
|
|
|
|
memcpy(notif_data, mpxy->shmem, sret.value + 16);
|
|
*events_data_len = sret.value;
|
|
|
|
err_put_cpu:
|
|
put_cpu();
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
static int mpxy_get_shmem_size(unsigned long *shmem_size)
|
|
{
|
|
struct sbiret sret;
|
|
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_GET_SHMEM_SIZE,
|
|
0, 0, 0, 0, 0, 0);
|
|
if (sret.error)
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
if (shmem_size)
|
|
*shmem_size = sret.value;
|
|
return 0;
|
|
}
|
|
|
|
static int mpxy_setup_shmem(unsigned int cpu)
|
|
{
|
|
struct page *shmem_page;
|
|
struct mpxy_local *mpxy;
|
|
struct sbiret sret;
|
|
|
|
mpxy = per_cpu_ptr(&mpxy_local, cpu);
|
|
if (mpxy->shmem_active)
|
|
return 0;
|
|
|
|
shmem_page = alloc_pages(GFP_KERNEL | __GFP_ZERO, get_order(mpxy_shmem_size));
|
|
if (!shmem_page)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Linux setup of shmem is done in mpxy OVERWRITE mode.
|
|
* flags[1:0] = 00b
|
|
*/
|
|
sret = sbi_ecall(SBI_EXT_MPXY, SBI_EXT_MPXY_SET_SHMEM,
|
|
page_to_phys(shmem_page), 0, 0, 0, 0, 0);
|
|
if (sret.error) {
|
|
free_pages((unsigned long)page_to_virt(shmem_page),
|
|
get_order(mpxy_shmem_size));
|
|
return sbi_err_map_linux_errno(sret.error);
|
|
}
|
|
|
|
mpxy->shmem = page_to_virt(shmem_page);
|
|
mpxy->shmem_phys_addr = page_to_phys(shmem_page);
|
|
mpxy->shmem_active = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ====== MPXY mailbox data structures ====== */
|
|
|
|
/* MPXY mailbox channel */
|
|
struct mpxy_mbox_channel {
|
|
struct mpxy_mbox *mbox;
|
|
u32 channel_id;
|
|
struct sbi_mpxy_channel_attrs attrs;
|
|
struct sbi_mpxy_rpmi_channel_attrs rpmi_attrs;
|
|
struct sbi_mpxy_notification_data *notif;
|
|
u32 max_xfer_len;
|
|
bool have_events_state;
|
|
u32 msi_index;
|
|
u32 msi_irq;
|
|
bool started;
|
|
};
|
|
|
|
/* MPXY mailbox */
|
|
struct mpxy_mbox {
|
|
struct device *dev;
|
|
u32 channel_count;
|
|
struct mpxy_mbox_channel *channels;
|
|
u32 msi_count;
|
|
struct mpxy_mbox_channel **msi_index_to_channel;
|
|
struct mbox_controller controller;
|
|
};
|
|
|
|
/* ====== MPXY RPMI processing ====== */
|
|
|
|
static void mpxy_mbox_send_rpmi_data(struct mpxy_mbox_channel *mchan,
|
|
struct rpmi_mbox_message *msg)
|
|
{
|
|
msg->error = 0;
|
|
switch (msg->type) {
|
|
case RPMI_MBOX_MSG_TYPE_GET_ATTRIBUTE:
|
|
switch (msg->attr.id) {
|
|
case RPMI_MBOX_ATTR_SPEC_VERSION:
|
|
msg->attr.value = mchan->attrs.msg_proto_version;
|
|
break;
|
|
case RPMI_MBOX_ATTR_MAX_MSG_DATA_SIZE:
|
|
msg->attr.value = mchan->max_xfer_len;
|
|
break;
|
|
case RPMI_MBOX_ATTR_SERVICEGROUP_ID:
|
|
msg->attr.value = mchan->rpmi_attrs.servicegroup_id;
|
|
break;
|
|
case RPMI_MBOX_ATTR_SERVICEGROUP_VERSION:
|
|
msg->attr.value = mchan->rpmi_attrs.servicegroup_version;
|
|
break;
|
|
case RPMI_MBOX_ATTR_IMPL_ID:
|
|
msg->attr.value = mchan->rpmi_attrs.impl_id;
|
|
break;
|
|
case RPMI_MBOX_ATTR_IMPL_VERSION:
|
|
msg->attr.value = mchan->rpmi_attrs.impl_version;
|
|
break;
|
|
default:
|
|
msg->error = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
break;
|
|
case RPMI_MBOX_MSG_TYPE_SET_ATTRIBUTE:
|
|
/* None of the RPMI linux mailbox attributes are writeable */
|
|
msg->error = -EOPNOTSUPP;
|
|
break;
|
|
case RPMI_MBOX_MSG_TYPE_SEND_WITH_RESPONSE:
|
|
if ((!msg->data.request && msg->data.request_len) ||
|
|
(msg->data.request && msg->data.request_len > mchan->max_xfer_len) ||
|
|
(!msg->data.response && msg->data.max_response_len)) {
|
|
msg->error = -EINVAL;
|
|
break;
|
|
}
|
|
if (!(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_SEND_WITH_RESP)) {
|
|
msg->error = -EIO;
|
|
break;
|
|
}
|
|
msg->error = mpxy_send_message_with_resp(mchan->channel_id,
|
|
msg->data.service_id,
|
|
msg->data.request,
|
|
msg->data.request_len,
|
|
msg->data.response,
|
|
msg->data.max_response_len,
|
|
&msg->data.out_response_len);
|
|
break;
|
|
case RPMI_MBOX_MSG_TYPE_SEND_WITHOUT_RESPONSE:
|
|
if ((!msg->data.request && msg->data.request_len) ||
|
|
(msg->data.request && msg->data.request_len > mchan->max_xfer_len)) {
|
|
msg->error = -EINVAL;
|
|
break;
|
|
}
|
|
if (!(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_SEND_WITHOUT_RESP)) {
|
|
msg->error = -EIO;
|
|
break;
|
|
}
|
|
msg->error = mpxy_send_message_without_resp(mchan->channel_id,
|
|
msg->data.service_id,
|
|
msg->data.request,
|
|
msg->data.request_len);
|
|
break;
|
|
default:
|
|
msg->error = -EOPNOTSUPP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void mpxy_mbox_peek_rpmi_data(struct mbox_chan *chan,
|
|
struct mpxy_mbox_channel *mchan,
|
|
struct sbi_mpxy_notification_data *notif,
|
|
unsigned long events_data_len)
|
|
{
|
|
struct rpmi_notification_event *event;
|
|
struct rpmi_mbox_message msg;
|
|
unsigned long pos = 0;
|
|
|
|
while (pos < events_data_len && (events_data_len - pos) <= sizeof(*event)) {
|
|
event = (struct rpmi_notification_event *)(notif->events_data + pos);
|
|
|
|
msg.type = RPMI_MBOX_MSG_TYPE_NOTIFICATION_EVENT;
|
|
msg.notif.event_datalen = le16_to_cpu(event->event_datalen);
|
|
msg.notif.event_id = event->event_id;
|
|
msg.notif.event_data = event->event_data;
|
|
msg.error = 0;
|
|
|
|
mbox_chan_received_data(chan, &msg);
|
|
pos += sizeof(*event) + msg.notif.event_datalen;
|
|
}
|
|
}
|
|
|
|
static int mpxy_mbox_read_rpmi_attrs(struct mpxy_mbox_channel *mchan)
|
|
{
|
|
return mpxy_read_attrs(mchan->channel_id,
|
|
SBI_MPXY_ATTR_MSGPROTO_ATTR_START,
|
|
sizeof(mchan->rpmi_attrs) / sizeof(u32),
|
|
(u32 *)&mchan->rpmi_attrs);
|
|
}
|
|
|
|
/* ====== MPXY mailbox callbacks ====== */
|
|
|
|
static int mpxy_mbox_send_data(struct mbox_chan *chan, void *data)
|
|
{
|
|
struct mpxy_mbox_channel *mchan = chan->con_priv;
|
|
|
|
if (mchan->attrs.msg_proto_id == SBI_MPXY_MSGPROTO_RPMI_ID) {
|
|
mpxy_mbox_send_rpmi_data(mchan, data);
|
|
return 0;
|
|
}
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static bool mpxy_mbox_peek_data(struct mbox_chan *chan)
|
|
{
|
|
struct mpxy_mbox_channel *mchan = chan->con_priv;
|
|
struct sbi_mpxy_notification_data *notif = mchan->notif;
|
|
bool have_notifications = false;
|
|
unsigned long data_len;
|
|
int rc;
|
|
|
|
if (!(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_GET_NOTIFICATIONS))
|
|
return false;
|
|
|
|
do {
|
|
rc = mpxy_get_notifications(mchan->channel_id, notif, &data_len);
|
|
if (rc || !data_len)
|
|
break;
|
|
|
|
if (mchan->attrs.msg_proto_id == SBI_MPXY_MSGPROTO_RPMI_ID)
|
|
mpxy_mbox_peek_rpmi_data(chan, mchan, notif, data_len);
|
|
|
|
have_notifications = true;
|
|
} while (1);
|
|
|
|
return have_notifications;
|
|
}
|
|
|
|
static irqreturn_t mpxy_mbox_irq_thread(int irq, void *dev_id)
|
|
{
|
|
mpxy_mbox_peek_data(dev_id);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int mpxy_mbox_setup_msi(struct mbox_chan *chan,
|
|
struct mpxy_mbox_channel *mchan)
|
|
{
|
|
struct device *dev = mchan->mbox->dev;
|
|
int rc;
|
|
|
|
/* Do nothing if MSI not supported */
|
|
if (mchan->msi_irq == U32_MAX)
|
|
return 0;
|
|
|
|
/* Fail if MSI already enabled */
|
|
if (mchan->attrs.msi_control)
|
|
return -EALREADY;
|
|
|
|
/* Request channel MSI handler */
|
|
rc = request_threaded_irq(mchan->msi_irq, NULL, mpxy_mbox_irq_thread,
|
|
0, dev_name(dev), chan);
|
|
if (rc) {
|
|
dev_err(dev, "failed to request MPXY channel 0x%x IRQ\n",
|
|
mchan->channel_id);
|
|
return rc;
|
|
}
|
|
|
|
/* Enable channel MSI control */
|
|
mchan->attrs.msi_control = 1;
|
|
rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSI_CONTROL,
|
|
1, &mchan->attrs.msi_control);
|
|
if (rc) {
|
|
dev_err(dev, "enable MSI control failed for MPXY channel 0x%x\n",
|
|
mchan->channel_id);
|
|
mchan->attrs.msi_control = 0;
|
|
free_irq(mchan->msi_irq, chan);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mpxy_mbox_cleanup_msi(struct mbox_chan *chan,
|
|
struct mpxy_mbox_channel *mchan)
|
|
{
|
|
struct device *dev = mchan->mbox->dev;
|
|
int rc;
|
|
|
|
/* Do nothing if MSI not supported */
|
|
if (mchan->msi_irq == U32_MAX)
|
|
return;
|
|
|
|
/* Do nothing if MSI already disabled */
|
|
if (!mchan->attrs.msi_control)
|
|
return;
|
|
|
|
/* Disable channel MSI control */
|
|
mchan->attrs.msi_control = 0;
|
|
rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSI_CONTROL,
|
|
1, &mchan->attrs.msi_control);
|
|
if (rc) {
|
|
dev_err(dev, "disable MSI control failed for MPXY channel 0x%x\n",
|
|
mchan->channel_id);
|
|
}
|
|
|
|
/* Free channel MSI handler */
|
|
free_irq(mchan->msi_irq, chan);
|
|
}
|
|
|
|
static int mpxy_mbox_setup_events(struct mpxy_mbox_channel *mchan)
|
|
{
|
|
struct device *dev = mchan->mbox->dev;
|
|
int rc;
|
|
|
|
/* Do nothing if events state not supported */
|
|
if (!mchan->have_events_state)
|
|
return 0;
|
|
|
|
/* Fail if events state already enabled */
|
|
if (mchan->attrs.events_state_ctrl)
|
|
return -EALREADY;
|
|
|
|
/* Enable channel events state */
|
|
mchan->attrs.events_state_ctrl = 1;
|
|
rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_EVENTS_STATE_CONTROL,
|
|
1, &mchan->attrs.events_state_ctrl);
|
|
if (rc) {
|
|
dev_err(dev, "enable events state failed for MPXY channel 0x%x\n",
|
|
mchan->channel_id);
|
|
mchan->attrs.events_state_ctrl = 0;
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mpxy_mbox_cleanup_events(struct mpxy_mbox_channel *mchan)
|
|
{
|
|
struct device *dev = mchan->mbox->dev;
|
|
int rc;
|
|
|
|
/* Do nothing if events state not supported */
|
|
if (!mchan->have_events_state)
|
|
return;
|
|
|
|
/* Do nothing if events state already disabled */
|
|
if (!mchan->attrs.events_state_ctrl)
|
|
return;
|
|
|
|
/* Disable channel events state */
|
|
mchan->attrs.events_state_ctrl = 0;
|
|
rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_EVENTS_STATE_CONTROL,
|
|
1, &mchan->attrs.events_state_ctrl);
|
|
if (rc)
|
|
dev_err(dev, "disable events state failed for MPXY channel 0x%x\n",
|
|
mchan->channel_id);
|
|
}
|
|
|
|
static int mpxy_mbox_startup(struct mbox_chan *chan)
|
|
{
|
|
struct mpxy_mbox_channel *mchan = chan->con_priv;
|
|
int rc;
|
|
|
|
if (mchan->started)
|
|
return -EALREADY;
|
|
|
|
/* Setup channel MSI */
|
|
rc = mpxy_mbox_setup_msi(chan, mchan);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Setup channel notification events */
|
|
rc = mpxy_mbox_setup_events(mchan);
|
|
if (rc) {
|
|
mpxy_mbox_cleanup_msi(chan, mchan);
|
|
return rc;
|
|
}
|
|
|
|
/* Mark the channel as started */
|
|
mchan->started = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mpxy_mbox_shutdown(struct mbox_chan *chan)
|
|
{
|
|
struct mpxy_mbox_channel *mchan = chan->con_priv;
|
|
|
|
if (!mchan->started)
|
|
return;
|
|
|
|
/* Mark the channel as stopped */
|
|
mchan->started = false;
|
|
|
|
/* Cleanup channel notification events */
|
|
mpxy_mbox_cleanup_events(mchan);
|
|
|
|
/* Cleanup channel MSI */
|
|
mpxy_mbox_cleanup_msi(chan, mchan);
|
|
}
|
|
|
|
static const struct mbox_chan_ops mpxy_mbox_ops = {
|
|
.send_data = mpxy_mbox_send_data,
|
|
.peek_data = mpxy_mbox_peek_data,
|
|
.startup = mpxy_mbox_startup,
|
|
.shutdown = mpxy_mbox_shutdown,
|
|
};
|
|
|
|
/* ====== MPXY platform driver ===== */
|
|
|
|
static void mpxy_mbox_msi_write(struct msi_desc *desc, struct msi_msg *msg)
|
|
{
|
|
struct device *dev = msi_desc_to_dev(desc);
|
|
struct mpxy_mbox *mbox = dev_get_drvdata(dev);
|
|
struct mpxy_mbox_channel *mchan;
|
|
struct sbi_mpxy_msi_info *minfo;
|
|
int rc;
|
|
|
|
mchan = mbox->msi_index_to_channel[desc->msi_index];
|
|
if (!mchan) {
|
|
dev_warn(dev, "MPXY channel not available for MSI index %d\n",
|
|
desc->msi_index);
|
|
return;
|
|
}
|
|
|
|
minfo = &mchan->attrs.msi_info;
|
|
minfo->msi_addr_lo = msg->address_lo;
|
|
minfo->msi_addr_hi = msg->address_hi;
|
|
minfo->msi_data = msg->data;
|
|
|
|
rc = mpxy_write_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSI_ADDR_LO,
|
|
sizeof(*minfo) / sizeof(u32), (u32 *)minfo);
|
|
if (rc) {
|
|
dev_warn(dev, "failed to write MSI info for MPXY channel 0x%x\n",
|
|
mchan->channel_id);
|
|
}
|
|
}
|
|
|
|
static struct mbox_chan *mpxy_mbox_fw_xlate(struct mbox_controller *ctlr,
|
|
const struct fwnode_reference_args *pa)
|
|
{
|
|
struct mpxy_mbox *mbox = container_of(ctlr, struct mpxy_mbox, controller);
|
|
struct mpxy_mbox_channel *mchan;
|
|
u32 i;
|
|
|
|
if (pa->nargs != 2)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
for (i = 0; i < mbox->channel_count; i++) {
|
|
mchan = &mbox->channels[i];
|
|
if (mchan->channel_id == pa->args[0] &&
|
|
mchan->attrs.msg_proto_id == pa->args[1])
|
|
return &mbox->controller.chans[i];
|
|
}
|
|
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
static int mpxy_mbox_populate_channels(struct mpxy_mbox *mbox)
|
|
{
|
|
u32 i, *channel_ids __free(kfree) = NULL;
|
|
struct mpxy_mbox_channel *mchan;
|
|
int rc;
|
|
|
|
/* Find-out of number of channels */
|
|
rc = mpxy_get_channel_count(&mbox->channel_count);
|
|
if (rc)
|
|
return dev_err_probe(mbox->dev, rc, "failed to get number of MPXY channels\n");
|
|
if (!mbox->channel_count)
|
|
return dev_err_probe(mbox->dev, -ENODEV, "no MPXY channels available\n");
|
|
|
|
/* Allocate and fetch all channel IDs */
|
|
channel_ids = kcalloc(mbox->channel_count, sizeof(*channel_ids), GFP_KERNEL);
|
|
if (!channel_ids)
|
|
return -ENOMEM;
|
|
rc = mpxy_get_channel_ids(mbox->channel_count, channel_ids);
|
|
if (rc)
|
|
return dev_err_probe(mbox->dev, rc, "failed to get MPXY channel IDs\n");
|
|
|
|
/* Populate all channels */
|
|
mbox->channels = devm_kcalloc(mbox->dev, mbox->channel_count,
|
|
sizeof(*mbox->channels), GFP_KERNEL);
|
|
if (!mbox->channels)
|
|
return -ENOMEM;
|
|
for (i = 0; i < mbox->channel_count; i++) {
|
|
mchan = &mbox->channels[i];
|
|
mchan->mbox = mbox;
|
|
mchan->channel_id = channel_ids[i];
|
|
|
|
rc = mpxy_read_attrs(mchan->channel_id, SBI_MPXY_ATTR_MSG_PROT_ID,
|
|
sizeof(mchan->attrs) / sizeof(u32),
|
|
(u32 *)&mchan->attrs);
|
|
if (rc) {
|
|
return dev_err_probe(mbox->dev, rc,
|
|
"MPXY channel 0x%x read attrs failed\n",
|
|
mchan->channel_id);
|
|
}
|
|
|
|
if (mchan->attrs.msg_proto_id == SBI_MPXY_MSGPROTO_RPMI_ID) {
|
|
rc = mpxy_mbox_read_rpmi_attrs(mchan);
|
|
if (rc) {
|
|
return dev_err_probe(mbox->dev, rc,
|
|
"MPXY channel 0x%x read RPMI attrs failed\n",
|
|
mchan->channel_id);
|
|
}
|
|
}
|
|
|
|
mchan->notif = devm_kzalloc(mbox->dev, mpxy_shmem_size, GFP_KERNEL);
|
|
if (!mchan->notif)
|
|
return -ENOMEM;
|
|
|
|
mchan->max_xfer_len = min(mpxy_shmem_size, mchan->attrs.msg_max_len);
|
|
|
|
if ((mchan->attrs.capability & SBI_MPXY_CHAN_CAP_GET_NOTIFICATIONS) &&
|
|
(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_EVENTS_STATE))
|
|
mchan->have_events_state = true;
|
|
|
|
if ((mchan->attrs.capability & SBI_MPXY_CHAN_CAP_GET_NOTIFICATIONS) &&
|
|
(mchan->attrs.capability & SBI_MPXY_CHAN_CAP_MSI))
|
|
mchan->msi_index = mbox->msi_count++;
|
|
else
|
|
mchan->msi_index = U32_MAX;
|
|
mchan->msi_irq = U32_MAX;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mpxy_mbox_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct mpxy_mbox_channel *mchan;
|
|
struct mpxy_mbox *mbox;
|
|
int msi_idx, rc;
|
|
u32 i;
|
|
|
|
/*
|
|
* Initialize MPXY shared memory only once. This also ensures
|
|
* that SBI MPXY mailbox is probed only once.
|
|
*/
|
|
if (mpxy_shmem_init_done) {
|
|
dev_err(dev, "SBI MPXY mailbox already initialized\n");
|
|
return -EALREADY;
|
|
}
|
|
|
|
/* Probe for SBI MPXY extension */
|
|
if (sbi_spec_version < sbi_mk_version(1, 0) ||
|
|
sbi_probe_extension(SBI_EXT_MPXY) <= 0) {
|
|
dev_info(dev, "SBI MPXY extension not available\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Find-out shared memory size */
|
|
rc = mpxy_get_shmem_size(&mpxy_shmem_size);
|
|
if (rc)
|
|
return dev_err_probe(dev, rc, "failed to get MPXY shared memory size\n");
|
|
|
|
/*
|
|
* Setup MPXY shared memory on each CPU
|
|
*
|
|
* Note: Don't cleanup MPXY shared memory upon CPU power-down
|
|
* because the RPMI System MSI irqchip driver needs it to be
|
|
* available when migrating IRQs in CPU power-down path.
|
|
*/
|
|
cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/sbi-mpxy-shmem",
|
|
mpxy_setup_shmem, NULL);
|
|
|
|
/* Mark as MPXY shared memory initialization done */
|
|
mpxy_shmem_init_done = true;
|
|
|
|
/* Allocate mailbox instance */
|
|
mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
|
|
if (!mbox)
|
|
return -ENOMEM;
|
|
mbox->dev = dev;
|
|
platform_set_drvdata(pdev, mbox);
|
|
|
|
/* Populate mailbox channels */
|
|
rc = mpxy_mbox_populate_channels(mbox);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Initialize mailbox controller */
|
|
mbox->controller.txdone_irq = false;
|
|
mbox->controller.txdone_poll = false;
|
|
mbox->controller.ops = &mpxy_mbox_ops;
|
|
mbox->controller.dev = dev;
|
|
mbox->controller.num_chans = mbox->channel_count;
|
|
mbox->controller.fw_xlate = mpxy_mbox_fw_xlate;
|
|
mbox->controller.chans = devm_kcalloc(dev, mbox->channel_count,
|
|
sizeof(*mbox->controller.chans),
|
|
GFP_KERNEL);
|
|
if (!mbox->controller.chans)
|
|
return -ENOMEM;
|
|
for (i = 0; i < mbox->channel_count; i++)
|
|
mbox->controller.chans[i].con_priv = &mbox->channels[i];
|
|
|
|
/* Setup MSIs for mailbox (if required) */
|
|
if (mbox->msi_count) {
|
|
/*
|
|
* The device MSI domain for platform devices on RISC-V architecture
|
|
* is only available after the MSI controller driver is probed so,
|
|
* explicitly configure here.
|
|
*/
|
|
if (!dev_get_msi_domain(dev)) {
|
|
struct fwnode_handle *fwnode = dev_fwnode(dev);
|
|
|
|
/*
|
|
* The device MSI domain for OF devices is only set at the
|
|
* time of populating/creating OF device. If the device MSI
|
|
* domain is discovered later after the OF device is created
|
|
* then we need to set it explicitly before using any platform
|
|
* MSI functions.
|
|
*/
|
|
if (is_of_node(fwnode)) {
|
|
of_msi_configure(dev, dev_of_node(dev));
|
|
} else if (is_acpi_device_node(fwnode)) {
|
|
struct irq_domain *msi_domain;
|
|
|
|
msi_domain = irq_find_matching_fwnode(imsic_acpi_get_fwnode(dev),
|
|
DOMAIN_BUS_PLATFORM_MSI);
|
|
dev_set_msi_domain(dev, msi_domain);
|
|
}
|
|
|
|
if (!dev_get_msi_domain(dev))
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
mbox->msi_index_to_channel = devm_kcalloc(dev, mbox->msi_count,
|
|
sizeof(*mbox->msi_index_to_channel),
|
|
GFP_KERNEL);
|
|
if (!mbox->msi_index_to_channel)
|
|
return -ENOMEM;
|
|
|
|
for (msi_idx = 0; msi_idx < mbox->msi_count; msi_idx++) {
|
|
for (i = 0; i < mbox->channel_count; i++) {
|
|
mchan = &mbox->channels[i];
|
|
if (mchan->msi_index == msi_idx) {
|
|
mbox->msi_index_to_channel[msi_idx] = mchan;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = platform_device_msi_init_and_alloc_irqs(dev, mbox->msi_count,
|
|
mpxy_mbox_msi_write);
|
|
if (rc) {
|
|
return dev_err_probe(dev, rc, "Failed to allocate %d MSIs\n",
|
|
mbox->msi_count);
|
|
}
|
|
|
|
for (i = 0; i < mbox->channel_count; i++) {
|
|
mchan = &mbox->channels[i];
|
|
if (mchan->msi_index == U32_MAX)
|
|
continue;
|
|
mchan->msi_irq = msi_get_virq(dev, mchan->msi_index);
|
|
}
|
|
}
|
|
|
|
/* Register mailbox controller */
|
|
rc = devm_mbox_controller_register(dev, &mbox->controller);
|
|
if (rc) {
|
|
dev_err_probe(dev, rc, "Registering SBI MPXY mailbox failed\n");
|
|
if (mbox->msi_count)
|
|
platform_device_msi_free_irqs_all(dev);
|
|
return rc;
|
|
}
|
|
|
|
#ifdef CONFIG_ACPI
|
|
struct acpi_device *adev = ACPI_COMPANION(dev);
|
|
|
|
if (adev)
|
|
acpi_dev_clear_dependencies(adev);
|
|
#endif
|
|
|
|
dev_info(dev, "mailbox registered with %d channels\n",
|
|
mbox->channel_count);
|
|
return 0;
|
|
}
|
|
|
|
static void mpxy_mbox_remove(struct platform_device *pdev)
|
|
{
|
|
struct mpxy_mbox *mbox = platform_get_drvdata(pdev);
|
|
|
|
if (mbox->msi_count)
|
|
platform_device_msi_free_irqs_all(mbox->dev);
|
|
}
|
|
|
|
static const struct of_device_id mpxy_mbox_of_match[] = {
|
|
{ .compatible = "riscv,sbi-mpxy-mbox" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mpxy_mbox_of_match);
|
|
|
|
static const struct acpi_device_id mpxy_mbox_acpi_match[] = {
|
|
{ "RSCV0005" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, mpxy_mbox_acpi_match);
|
|
|
|
static struct platform_driver mpxy_mbox_driver = {
|
|
.driver = {
|
|
.name = "riscv-sbi-mpxy-mbox",
|
|
.of_match_table = mpxy_mbox_of_match,
|
|
.acpi_match_table = mpxy_mbox_acpi_match,
|
|
},
|
|
.probe = mpxy_mbox_probe,
|
|
.remove = mpxy_mbox_remove,
|
|
};
|
|
module_platform_driver(mpxy_mbox_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Anup Patel <apatel@ventanamicro.com>");
|
|
MODULE_DESCRIPTION("RISC-V SBI MPXY mailbox controller driver");
|