| // 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"); |