/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Western Digital Corporation or its affiliates. * * Authors: * Anup Patel * Nick Kossifidis */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct sbi_ipi_data { unsigned long ipi_type; }; _Static_assert( 8 * sizeof(((struct sbi_ipi_data*)0)->ipi_type) == SBI_IPI_EVENT_MAX, "type of sbi_ipi_data.ipi_type has changed, please redefine SBI_IPI_EVENT_MAX" ); static unsigned long ipi_data_off; static const struct sbi_ipi_device *ipi_dev = NULL; static const struct sbi_ipi_event_ops *ipi_ops_array[SBI_IPI_EVENT_MAX]; static int sbi_ipi_send(struct sbi_scratch *scratch, u32 remote_hartindex, u32 event, void *data) { int ret = 0; struct sbi_scratch *remote_scratch = NULL; struct sbi_ipi_data *ipi_data; const struct sbi_ipi_event_ops *ipi_ops; if ((SBI_IPI_EVENT_MAX <= event) || !ipi_ops_array[event]) return SBI_EINVAL; ipi_ops = ipi_ops_array[event]; remote_scratch = sbi_hartindex_to_scratch(remote_hartindex); if (!remote_scratch) return SBI_EINVAL; ipi_data = sbi_scratch_offset_ptr(remote_scratch, ipi_data_off); if (ipi_ops->update) { ret = ipi_ops->update(scratch, remote_scratch, remote_hartindex, data); if (ret != SBI_IPI_UPDATE_SUCCESS) return ret; } else if (scratch == remote_scratch) { /* * IPI events with an update() callback are expected to return * SBI_IPI_UPDATE_BREAK for self-IPIs. For other events, check * for self-IPI and execute the callback directly here. */ ipi_ops->process(scratch); return 0; } /* * Set IPI type on remote hart's scratch area and * trigger the interrupt. * * Multiple harts may be trying to send IPI to the * remote hart so call sbi_ipi_raw_send() only when * the ipi_type was previously zero. */ if (!__atomic_fetch_or(&ipi_data->ipi_type, BIT(event), __ATOMIC_RELAXED)) ret = sbi_ipi_raw_send(remote_hartindex); sbi_pmu_ctr_incr_fw(SBI_PMU_FW_IPI_SENT); return ret; } static int sbi_ipi_sync(struct sbi_scratch *scratch, u32 event) { const struct sbi_ipi_event_ops *ipi_ops; if ((SBI_IPI_EVENT_MAX <= event) || !ipi_ops_array[event]) return SBI_EINVAL; ipi_ops = ipi_ops_array[event]; if (ipi_ops->sync) ipi_ops->sync(scratch); return 0; } /** * As this this function only handlers scalar values of hart mask, it must be * set to all online harts if the intention is to send IPIs to all the harts. * If hmask is zero, no IPIs will be sent. */ int sbi_ipi_send_many(ulong hmask, ulong hbase, u32 event, void *data) { int rc = 0; bool retry_needed; ulong i, m; struct sbi_hartmask target_mask = {0}; struct sbi_domain *dom = sbi_domain_thishart_ptr(); struct sbi_scratch *scratch = sbi_scratch_thishart_ptr(); /* Find the target harts */ if (hbase != -1UL) { rc = sbi_hsm_hart_interruptible_mask(dom, hbase, &m); if (rc) return rc; m &= hmask; for (i = hbase; m; i++, m >>= 1) { if (m & 1UL) sbi_hartmask_set_hartid(i, &target_mask); } } else { hbase = 0; while (!sbi_hsm_hart_interruptible_mask(dom, hbase, &m)) { for (i = hbase; m; i++, m >>= 1) { if (m & 1UL) sbi_hartmask_set_hartid(i, &target_mask); } hbase += BITS_PER_LONG; } } /* Send IPIs */ do { retry_needed = false; sbi_hartmask_for_each_hartindex(i, &target_mask) { rc = sbi_ipi_send(scratch, i, event, data); if (rc < 0) goto done; if (rc == SBI_IPI_UPDATE_RETRY) retry_needed = true; else sbi_hartmask_clear_hartindex(i, &target_mask); rc = 0; } } while (retry_needed); done: /* Sync IPIs */ sbi_ipi_sync(scratch, event); return rc; } int sbi_ipi_event_create(const struct sbi_ipi_event_ops *ops) { int i, ret = SBI_ENOSPC; if (!ops || !ops->process) return SBI_EINVAL; for (i = 0; i < SBI_IPI_EVENT_MAX; i++) { if (!ipi_ops_array[i]) { ret = i; ipi_ops_array[i] = ops; break; } } return ret; } void sbi_ipi_event_destroy(u32 event) { if (SBI_IPI_EVENT_MAX <= event) return; ipi_ops_array[event] = NULL; } static void sbi_ipi_process_smode(struct sbi_scratch *scratch) { csr_set(CSR_MIP, MIP_SSIP); } static struct sbi_ipi_event_ops ipi_smode_ops = { .name = "IPI_SMODE", .process = sbi_ipi_process_smode, }; static u32 ipi_smode_event = SBI_IPI_EVENT_MAX; int sbi_ipi_send_smode(ulong hmask, ulong hbase) { return sbi_ipi_send_many(hmask, hbase, ipi_smode_event, NULL); } void sbi_ipi_clear_smode(void) { csr_clear(CSR_MIP, MIP_SSIP); } static void sbi_ipi_process_halt(struct sbi_scratch *scratch) { sbi_hsm_hart_stop(scratch, true); } static struct sbi_ipi_event_ops ipi_halt_ops = { .name = "IPI_HALT", .process = sbi_ipi_process_halt, }; static u32 ipi_halt_event = SBI_IPI_EVENT_MAX; int sbi_ipi_send_halt(ulong hmask, ulong hbase) { return sbi_ipi_send_many(hmask, hbase, ipi_halt_event, NULL); } void sbi_ipi_process(void) { unsigned long ipi_type; unsigned int ipi_event; const struct sbi_ipi_event_ops *ipi_ops; struct sbi_scratch *scratch = sbi_scratch_thishart_ptr(); struct sbi_ipi_data *ipi_data = sbi_scratch_offset_ptr(scratch, ipi_data_off); u32 hartindex = sbi_hartid_to_hartindex(current_hartid()); sbi_pmu_ctr_incr_fw(SBI_PMU_FW_IPI_RECVD); sbi_ipi_raw_clear(hartindex); ipi_type = atomic_raw_xchg_ulong(&ipi_data->ipi_type, 0); ipi_event = 0; while (ipi_type) { if (ipi_type & 1UL) { ipi_ops = ipi_ops_array[ipi_event]; if (ipi_ops) ipi_ops->process(scratch); } ipi_type = ipi_type >> 1; ipi_event++; } } int sbi_ipi_raw_send(u32 hartindex) { if (!ipi_dev || !ipi_dev->ipi_send) return SBI_EINVAL; /* * Ensure that memory or MMIO writes done before * this function are not observed after the memory * or MMIO writes done by the ipi_send() device * callback. This also allows the ipi_send() device * callback to use relaxed MMIO writes. * * This pairs with the wmb() in sbi_ipi_raw_clear(). */ wmb(); ipi_dev->ipi_send(hartindex); return 0; } void sbi_ipi_raw_clear(u32 hartindex) { if (ipi_dev && ipi_dev->ipi_clear) ipi_dev->ipi_clear(hartindex); /* * Ensure that memory or MMIO writes after this * function returns are not observed before the * memory or MMIO writes done by the ipi_clear() * device callback. This also allows ipi_clear() * device callback to use relaxed MMIO writes. * * This pairs with the wmb() in sbi_ipi_raw_send(). */ wmb(); } const struct sbi_ipi_device *sbi_ipi_get_device(void) { return ipi_dev; } void sbi_ipi_set_device(const struct sbi_ipi_device *dev) { if (!dev || ipi_dev) return; ipi_dev = dev; } int sbi_ipi_init(struct sbi_scratch *scratch, bool cold_boot) { int ret; struct sbi_ipi_data *ipi_data; if (cold_boot) { ipi_data_off = sbi_scratch_alloc_offset(sizeof(*ipi_data)); if (!ipi_data_off) return SBI_ENOMEM; ret = sbi_ipi_event_create(&ipi_smode_ops); if (ret < 0) return ret; ipi_smode_event = ret; ret = sbi_ipi_event_create(&ipi_halt_ops); if (ret < 0) return ret; ipi_halt_event = ret; } else { if (!ipi_data_off) return SBI_ENOMEM; if (SBI_IPI_EVENT_MAX <= ipi_smode_event || SBI_IPI_EVENT_MAX <= ipi_halt_event) return SBI_ENOSPC; } ipi_data = sbi_scratch_offset_ptr(scratch, ipi_data_off); ipi_data->ipi_type = 0x00; /* * Initialize platform IPI support. This will also clear any * pending IPIs for current/calling HART. */ ret = sbi_platform_ipi_init(sbi_platform_ptr(scratch), cold_boot); if (ret) return ret; /* Enable software interrupts */ csr_set(CSR_MIE, MIP_MSIP); return 0; } void sbi_ipi_exit(struct sbi_scratch *scratch) { /* Disable software interrupts */ csr_clear(CSR_MIE, MIP_MSIP); /* Process pending IPIs */ sbi_ipi_process(); /* Platform exit */ sbi_platform_ipi_exit(sbi_platform_ptr(scratch)); }