* [PATCH v5 1/3] powerpc/book3s64/radix: Add H_RPT_INVALIDATE pgsize encodings to mmu_psize_def
From: Bharata B Rao @ 2021-02-24 8:25 UTC (permalink / raw)
To: kvm-ppc, linuxppc-dev
Cc: farosas, aneesh.kumar, npiggin, Bharata B Rao, david
In-Reply-To: <20210224082510.3962423-1-bharata@linux.ibm.com>
Add a field to mmu_psize_def to store the page size encodings
of H_RPT_INVALIDATE hcall. Initialize this while scanning the radix
AP encodings. This will be used when invalidating with required
page size encoding in the hcall.
Signed-off-by: Bharata B Rao <bharata@linux.ibm.com>
---
arch/powerpc/include/asm/book3s/64/mmu.h | 1 +
arch/powerpc/mm/book3s64/radix_pgtable.c | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/arch/powerpc/include/asm/book3s/64/mmu.h b/arch/powerpc/include/asm/book3s/64/mmu.h
index eace8c3f7b0a..c02f42d1031e 100644
--- a/arch/powerpc/include/asm/book3s/64/mmu.h
+++ b/arch/powerpc/include/asm/book3s/64/mmu.h
@@ -19,6 +19,7 @@ struct mmu_psize_def {
int penc[MMU_PAGE_COUNT]; /* HPTE encoding */
unsigned int tlbiel; /* tlbiel supported for that page size */
unsigned long avpnm; /* bits to mask out in AVPN in the HPTE */
+ unsigned long h_rpt_pgsize; /* H_RPT_INVALIDATE page size encoding */
union {
unsigned long sllp; /* SLB L||LP (exact mask to use in slbmte) */
unsigned long ap; /* Ap encoding used by PowerISA 3.0 */
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c b/arch/powerpc/mm/book3s64/radix_pgtable.c
index 98f0b243c1ab..1b749899016b 100644
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c
+++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -486,6 +486,7 @@ static int __init radix_dt_scan_page_sizes(unsigned long node,
def = &mmu_psize_defs[idx];
def->shift = shift;
def->ap = ap;
+ def->h_rpt_pgsize = psize_to_rpti_pgsize(idx);
}
/* needed ? */
@@ -560,9 +561,13 @@ void __init radix__early_init_devtree(void)
*/
mmu_psize_defs[MMU_PAGE_4K].shift = 12;
mmu_psize_defs[MMU_PAGE_4K].ap = 0x0;
+ mmu_psize_defs[MMU_PAGE_4K].h_rpt_pgsize =
+ psize_to_rpti_pgsize(MMU_PAGE_4K);
mmu_psize_defs[MMU_PAGE_64K].shift = 16;
mmu_psize_defs[MMU_PAGE_64K].ap = 0x5;
+ mmu_psize_defs[MMU_PAGE_64K].h_rpt_pgsize =
+ psize_to_rpti_pgsize(MMU_PAGE_64K);
}
/*
--
2.26.2
^ permalink raw reply related
* [PATCH v5 3/3] KVM: PPC: Book3S HV: Use H_RPT_INVALIDATE in nested KVM
From: Bharata B Rao @ 2021-02-24 8:25 UTC (permalink / raw)
To: kvm-ppc, linuxppc-dev
Cc: farosas, aneesh.kumar, npiggin, Bharata B Rao, david
In-Reply-To: <20210224082510.3962423-1-bharata@linux.ibm.com>
In the nested KVM case, replace H_TLB_INVALIDATE by the new hcall
H_RPT_INVALIDATE if available. The availability of this hcall
is determined from "hcall-rpt-invalidate" string in ibm,hypertas-functions
DT property.
Signed-off-by: Bharata B Rao <bharata@linux.ibm.com>
Reviewed-by: Fabiano Rosas <farosas@linux.ibm.com>
---
arch/powerpc/kvm/book3s_64_mmu_radix.c | 27 +++++++++++++++++++++-----
arch/powerpc/kvm/book3s_hv_nested.c | 12 ++++++++++--
2 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/arch/powerpc/kvm/book3s_64_mmu_radix.c b/arch/powerpc/kvm/book3s_64_mmu_radix.c
index bb35490400e9..7ea5459022cb 100644
--- a/arch/powerpc/kvm/book3s_64_mmu_radix.c
+++ b/arch/powerpc/kvm/book3s_64_mmu_radix.c
@@ -21,6 +21,7 @@
#include <asm/pte-walk.h>
#include <asm/ultravisor.h>
#include <asm/kvm_book3s_uvmem.h>
+#include <asm/plpar_wrappers.h>
/*
* Supported radix tree geometry.
@@ -318,9 +319,19 @@ void kvmppc_radix_tlbie_page(struct kvm *kvm, unsigned long addr,
}
psi = shift_to_mmu_psize(pshift);
- rb = addr | (mmu_get_ap(psi) << PPC_BITLSHIFT(58));
- rc = plpar_hcall_norets(H_TLB_INVALIDATE, H_TLBIE_P1_ENC(0, 0, 1),
- lpid, rb);
+
+ if (!firmware_has_feature(FW_FEATURE_RPT_INVALIDATE)) {
+ rb = addr | (mmu_get_ap(psi) << PPC_BITLSHIFT(58));
+ rc = plpar_hcall_norets(H_TLB_INVALIDATE, H_TLBIE_P1_ENC(0, 0, 1),
+ lpid, rb);
+ } else {
+ rc = pseries_rpt_invalidate(lpid, H_RPTI_TARGET_CMMU,
+ H_RPTI_TYPE_NESTED |
+ H_RPTI_TYPE_TLB,
+ psize_to_rpti_pgsize(psi),
+ addr, addr + psize);
+ }
+
if (rc)
pr_err("KVM: TLB page invalidation hcall failed, rc=%ld\n", rc);
}
@@ -334,8 +345,14 @@ static void kvmppc_radix_flush_pwc(struct kvm *kvm, unsigned int lpid)
return;
}
- rc = plpar_hcall_norets(H_TLB_INVALIDATE, H_TLBIE_P1_ENC(1, 0, 1),
- lpid, TLBIEL_INVAL_SET_LPID);
+ if (!firmware_has_feature(FW_FEATURE_RPT_INVALIDATE))
+ rc = plpar_hcall_norets(H_TLB_INVALIDATE, H_TLBIE_P1_ENC(1, 0, 1),
+ lpid, TLBIEL_INVAL_SET_LPID);
+ else
+ rc = pseries_rpt_invalidate(lpid, H_RPTI_TARGET_CMMU,
+ H_RPTI_TYPE_NESTED |
+ H_RPTI_TYPE_PWC, H_RPTI_PAGE_ALL,
+ 0, -1UL);
if (rc)
pr_err("KVM: TLB PWC invalidation hcall failed, rc=%ld\n", rc);
}
diff --git a/arch/powerpc/kvm/book3s_hv_nested.c b/arch/powerpc/kvm/book3s_hv_nested.c
index ca43b2d38dce..2a6570e6c2c4 100644
--- a/arch/powerpc/kvm/book3s_hv_nested.c
+++ b/arch/powerpc/kvm/book3s_hv_nested.c
@@ -19,6 +19,7 @@
#include <asm/pgalloc.h>
#include <asm/pte-walk.h>
#include <asm/reg.h>
+#include <asm/plpar_wrappers.h>
static struct patb_entry *pseries_partition_tb;
@@ -444,8 +445,15 @@ static void kvmhv_flush_lpid(unsigned int lpid)
return;
}
- rc = plpar_hcall_norets(H_TLB_INVALIDATE, H_TLBIE_P1_ENC(2, 0, 1),
- lpid, TLBIEL_INVAL_SET_LPID);
+ if (!firmware_has_feature(FW_FEATURE_RPT_INVALIDATE))
+ rc = plpar_hcall_norets(H_TLB_INVALIDATE, H_TLBIE_P1_ENC(2, 0, 1),
+ lpid, TLBIEL_INVAL_SET_LPID);
+ else
+ rc = pseries_rpt_invalidate(lpid, H_RPTI_TARGET_CMMU,
+ H_RPTI_TYPE_NESTED |
+ H_RPTI_TYPE_TLB | H_RPTI_TYPE_PWC |
+ H_RPTI_TYPE_PAT,
+ H_RPTI_PAGE_ALL, 0, -1UL);
if (rc)
pr_err("KVM: TLB LPID invalidation hcall failed, rc=%ld\n", rc);
}
--
2.26.2
^ permalink raw reply related
* Re: [PATCH v2] vio: make remove callback return void
From: Anatoly Pugachev @ 2021-02-24 11:01 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Cristobal Forno, Tyrel Datwyler, Sparc kernel list, target-devel,
Paul Mackerras, Breno Leitão, Peter Huewe,
Sukadev Bhattiprolu, Jiri Slaby, Herbert Xu, linux-scsi,
Nayna Jain, Jason Gunthorpe, Michael Cyr, Jakub Kicinski,
Arnd Bergmann, James E.J. Bottomley, linux-block, Lijun Pan,
Matt Mackall, Jens Axboe, Steven Royer, Martin K. Petersen,
Greg Kroah-Hartman, Linux Kernel list, Jarkko Sakkinen,
linux-crypto, netdev, Dany Madden, Paulo Flabiano Smorigo,
linux-integrity, linuxppc-dev, David S. Miller
In-Reply-To: <20210224072516.74696-1-uwe@kleine-koenig.org>
On Wed, Feb 24, 2021 at 11:17 AM Uwe Kleine-König <uwe@kleine-koenig.org> wrote:
>
> The driver core ignores the return value of struct bus_type::remove()
> because there is only little that can be done. To simplify the quest to
> make this function return void, let struct vio_driver::remove() return
> void, too. All users already unconditionally return 0, this commit makes
> it obvious that returning an error code is a bad idea and makes it
> obvious for future driver authors that returning an error code isn't
> intended.
>
> Note there are two nominally different implementations for a vio bus:
> one in arch/sparc/kernel/vio.c and the other in
> arch/powerpc/platforms/pseries/vio.c. I didn't care to check which
> driver is using which of these busses (or if even some of them can be
> used with both) and simply adapt all drivers and the two bus codes in
> one go.
Applied over current git kernel, boots on my sparc64 LDOM (sunvdc
block driver which uses vio).
Linux ttip 5.11.0-10201-gc03c21ba6f4e-dirty #189 SMP Wed Feb 24
13:48:37 MSK 2021 sparc64 GNU/Linux
boot logs (and kernel config) on [1] for "5.11.0-10201-gc03c21ba6f4e-dirty".
Up to you to add "tested-by".
Thanks.
1. https://github.com/mator/sparc64-dmesg
PS: going to check with ppc64 later as well on LPAR (uses vio).
^ permalink raw reply
* Re: [PATCH] powerpc/perf: Fix handling of privilege level checks in perf interrupt context
From: Athira Rajeev @ 2021-02-24 11:10 UTC (permalink / raw)
To: Michael Ellerman
Cc: Madhavan Srinivasan, Peter Zijlstra, omosnace, acme, Jiri Olsa,
linuxppc-dev, kan.liang
In-Reply-To: <87czwrt057.fsf@mpe.ellerman.id.au>
> On 23-Feb-2021, at 6:24 PM, Michael Ellerman <mpe@ellerman.id.au> wrote:
>
> Peter Zijlstra <peterz@infradead.org> writes:
>> On Tue, Feb 23, 2021 at 01:31:49AM -0500, Athira Rajeev wrote:
>>> Running "perf mem record" in powerpc platforms with selinux enabled
>>> resulted in soft lockup's. Below call-trace was seen in the logs:
> ...
>>>
>>> Since the purpose of this security hook is to control access to
>>> perf_event_open, it is not right to call this in interrupt context.
>>> But in case of powerpc PMU, we need the privilege checks for specific
>>> samples from branch history ring buffer and sampling register values.
>>
>> I'm confused... why would you need those checks at event time? Either
>> the event has perf_event_attr::exclude_kernel and it then isn't allowed
>> to expose kernel addresses, or it doesn't and it is.
>
> Well one of us is confused that's for sure ^_^
>
> I missed/forgot that we had that logic in open.
>
> I think the reason we got here is that in the past we didn't have the
> event in the low-level routines where we want to check,
> power_pmu_bhrb_read() and perf_get_data_addr(), so we hacked in a
> perf_paranoid_kernel() check. Which was wrong.
>
> Then Joel's patch plumbed the event through and switched those paranoid
> checks to perf_allow_kernel().
>
> Anyway, we'll just switch those to exclude_kernel checks.
>
>> There should never be an event-time question of permission like this. If
>> you allow creation of an event, you're allowing the data it generates.
>
> Ack.
Thanks for all the reviews. I will send a V2 with using 'event->attr.exclude_kernel' in the checks.
Athira
>
> cheers
^ permalink raw reply
* Re: [PASEMI] Nemo board doesn't boot anymore because of moving pas_pci_init
From: Christian Zigotzky @ 2021-02-24 12:15 UTC (permalink / raw)
To: Oliver O'Halloran, Michael Ellerman
Cc: Olof Johansson, Darren Stevens, linuxppc-dev, R.T.Dickinson
In-Reply-To: <CAOSf1CHQ=QDwH=J4kLYqboe481poa7EdbC6gzq29W7KYHhn1YQ@mail.gmail.com>
On 24 February 21 at 03:17am, Oliver O'Halloran wrote:
> On Wed, Feb 24, 2021 at 11:55 AM Michael Ellerman <mpe@ellerman.id.au> wrote:
>> Olof Johansson <olof@lixom.net> writes:
>>> Hi,
>>>
>>> On Tue, Feb 23, 2021 at 1:43 PM Christian Zigotzky
>>> <chzigotzky@xenosoft.de> wrote:
>>>> Hello,
>>>>
>>>> The Nemo board [1] with a P.A. Semi PA6T SoC doesn't boot anymore
>>>> because of moving "pas_pci_init" to the device tree adoption [2] in the
>>>> latest PowerPC updates 5.12-1 [3].
>>>>
>>>> Unfortunately the Nemo board doesn't have it in its device tree. I
>>>> reverted this commit and after that the Nemo board boots without any
>>>> problems.
>>>>
>>>> What do you think about this ifdef?
>>>>
>>>> #ifdef CONFIG_PPC_PASEMI_NEMO
>>>> /*
>>>> * Check for the Nemo motherboard here, if we are running on one
>>>> * then pas_pci_init()
>>>> */
>>>> if (of_machine_is_compatible("pasemi,nemo")) {
>>>> pas_pci_init();
>>>> }
>>>> #endif
>>> This is not a proper fix for the problem. Someone will need to debug
>>> what on the pas_pci_init() codepath still needs to happen early in the
>>> boot, even if the main PCI setup happens later.
>> I looked but don't see anything 100% obvious.
>>
>> Possibly it's the call to isa_bridge_find_early()?
> Looks like it. I think the problem stems from the use of the PIO
> helpers (mainly outb()) in i8259_init() which is called from
> nemo_init_IRQ(). The PIO helpers require the ISA space to be mapped
> and io_isa_base to be set since they take a PIO register address
> rather than an MMIO address. It looks like there's a few other legacy
> embedded platforms that might have the same problem.
>
> I guess the real fix would be to decouple the ISA base address
> discovery from the PHB discovery. That should be doable since it's all
> discovered via DT anyway and we only support one ISA address range,
> but it's a bit of work.
Sorry because of the false statement of the boot issue. It was too late
yesterday. If I understand it correctly then the position of the PCIE
devices scan is at a new place. Therefore it doesn't work anymore. It
hasn't nothing to do with the device tree adoption. We will use the
following patch for reverting this commit for further testing the new
kernels.
--- a/arch/powerpc/platforms/pasemi/setup.c 2021-02-23
21:40:04.835999708 +0100
+++ b/arch/powerpc/platforms/pasemi/setup.c 2021-02-23
21:46:04.560667045 +0100
@@ -144,6 +144,7 @@ static void __init pas_setup_arch(void)
/* Setup SMP callback */
smp_ops = &pas_smp_ops;
#endif
+ pas_pci_init();
/* Remap SDC register for doing reset */
/* XXXOJN This should maybe come out of the device tree */
@@ -444,7 +445,6 @@ define_machine(pasemi) {
.name = "PA Semi PWRficient",
.probe = pas_probe,
.setup_arch = pas_setup_arch,
- .discover_phbs = pas_pci_init,
.init_IRQ = pas_init_IRQ,
.get_irq = mpic_get_irq,
.restart = pas_restart,
^ permalink raw reply
* [PATCH] powerpc/perf: prevent mixed EBB and non-EBB events
From: Thadeu Lima de Souza Cascardo @ 2021-02-24 12:21 UTC (permalink / raw)
To: linuxppc-dev; +Cc: peterz, linux-kernel, cascardo
EBB events must be under exclusive groups, so there is no mix of EBB and
non-EBB events on the same PMU. This requirement worked fine as perf core
would not allow other pinned events to be scheduled together with exclusive
events.
This assumption was broken by commit 1908dc911792 ("perf: Tweak
perf_event_attr::exclusive semantics").
After that, the test cpu_event_pinned_vs_ebb_test started succeeding after
read_events, but worse, the task would not have given access to PMC1, so
when it tried to write to it, it was killed with "illegal instruction".
Preventing mixed EBB and non-EBB events from being add to the same PMU will
just revert to the previous behavior and the test will succeed.
Fixes: 1908dc911792 (perf: Tweak perf_event_attr::exclusive semantics)
Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@canonical.com>
---
arch/powerpc/perf/core-book3s.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/arch/powerpc/perf/core-book3s.c b/arch/powerpc/perf/core-book3s.c
index 43599e671d38..d767f7944f85 100644
--- a/arch/powerpc/perf/core-book3s.c
+++ b/arch/powerpc/perf/core-book3s.c
@@ -1010,9 +1010,25 @@ static int check_excludes(struct perf_event **ctrs, unsigned int cflags[],
int n_prev, int n_new)
{
int eu = 0, ek = 0, eh = 0;
+ bool ebb = false;
int i, n, first;
struct perf_event *event;
+ n = n_prev + n_new;
+ if (n <= 1)
+ return 0;
+
+ first = 1;
+ for (i = 0; i < n; ++i) {
+ event = ctrs[i];
+ if (first) {
+ ebb = is_ebb_event(event);
+ first = 0;
+ } else if (is_ebb_event(event) != ebb) {
+ return -EAGAIN;
+ }
+ }
+
/*
* If the PMU we're on supports per event exclude settings then we
* don't need to do any of this logic. NB. This assumes no PMU has both
@@ -1021,10 +1037,6 @@ static int check_excludes(struct perf_event **ctrs, unsigned int cflags[],
if (ppmu->flags & PPMU_ARCH_207S)
return 0;
- n = n_prev + n_new;
- if (n <= 1)
- return 0;
-
first = 1;
for (i = 0; i < n; ++i) {
if (cflags[i] & PPMU_LIMITED_PMC_OK) {
--
2.27.0
^ permalink raw reply related
* [PATCH 1/2] powerpc/perf: Infrastructure to support checking of attr.config*
From: Madhavan Srinivasan @ 2021-02-24 14:28 UTC (permalink / raw)
To: mpe; +Cc: Alexey Kardashevskiy, Madhavan Srinivasan, linuxppc-dev
Introduce code to support the checking of attr.config* for
values which are reserved for a given platform.
Performance Monitoring Unit (PMU) configuration registers
have fileds that are reserved and specific values to bit fields
as reserved. Writing a none zero values in these fields
or writing invalid value to bit fields will have unknown
behaviours.
Patch here add a generic call-back function "check_attr_config"
in "struct power_pmu", to be called in event_init to
check for attr.config* values for a given platform.
"check_attr_config" is valid only for raw event type.
Suggested-by: Alexey Kardashevskiy <aik@ozlabs.ru>
Signed-off-by: Madhavan Srinivasan <maddy@linux.ibm.com>
---
arch/powerpc/include/asm/perf_event_server.h | 6 ++++++
arch/powerpc/perf/core-book3s.c | 12 ++++++++++++
2 files changed, 18 insertions(+)
diff --git a/arch/powerpc/include/asm/perf_event_server.h b/arch/powerpc/include/asm/perf_event_server.h
index 00e7e671bb4b..dde97d7d9253 100644
--- a/arch/powerpc/include/asm/perf_event_server.h
+++ b/arch/powerpc/include/asm/perf_event_server.h
@@ -67,6 +67,12 @@ struct power_pmu {
* the pmu supports extended perf regs capability
*/
int capabilities;
+ /*
+ * Function to check event code for values which are
+ * reserved. Function takes struct perf_event as input,
+ * since event code could be spread in attr.config*
+ */
+ int (*check_attr_config)(struct perf_event *ev);
};
/*
diff --git a/arch/powerpc/perf/core-book3s.c b/arch/powerpc/perf/core-book3s.c
index 6817331e22ff..679d67506299 100644
--- a/arch/powerpc/perf/core-book3s.c
+++ b/arch/powerpc/perf/core-book3s.c
@@ -1958,6 +1958,18 @@ static int power_pmu_event_init(struct perf_event *event)
if (ppmu->blacklist_ev && is_event_blacklisted(ev))
return -EINVAL;
+ /*
+ * PMU config registers have fileds that are
+ * reserved and spacific values to bit fileds be reserved.
+ * This call-back will check the event code for same.
+ *
+ * Event type hardware and hw_cache will not value
+ * invalid values in the event code which is not true
+ * for raw event type.
+ */
+ if (ppmu->check_attr_config &&
+ ppmu->check_attr_config(event))
+ return -EINVAL;
break;
default:
return -ENOENT;
--
2.26.2
^ permalink raw reply related
* [PATCH 2/2] powerpc/perf: Add platform specific check_attr_config
From: Madhavan Srinivasan @ 2021-02-24 14:28 UTC (permalink / raw)
To: mpe; +Cc: Madhavan Srinivasan, linuxppc-dev
In-Reply-To: <20210224142840.1170088-1-maddy@linux.ibm.com>
Add platform specific attr.config value checks. Patch
includes checks for power9 and power10 platforms.
Signed-off-by: Madhavan Srinivasan <maddy@linux.ibm.com>
---
arch/powerpc/perf/isa207-common.c | 41 +++++++++++++++++++++++++++++++
arch/powerpc/perf/isa207-common.h | 2 ++
arch/powerpc/perf/power10-pmu.c | 13 ++++++++++
arch/powerpc/perf/power9-pmu.c | 13 ++++++++++
4 files changed, 69 insertions(+)
diff --git a/arch/powerpc/perf/isa207-common.c b/arch/powerpc/perf/isa207-common.c
index e4f577da33d8..b255799f5b51 100644
--- a/arch/powerpc/perf/isa207-common.c
+++ b/arch/powerpc/perf/isa207-common.c
@@ -694,3 +694,44 @@ int isa207_get_alternatives(u64 event, u64 alt[], int size, unsigned int flags,
return num_alt;
}
+
+int isa3_X_check_attr_config(struct perf_event *ev)
+{
+ u64 val, sample_mode;
+ u64 event = ev->attr.config;
+
+ val = (event >> EVENT_SAMPLE_SHIFT) & EVENT_SAMPLE_MASK;
+ sample_mode = val & 0x3;
+
+ /*
+ * MMCRA[61:62] is Randome Sampling Mode (SM).
+ * value of 0b11 is reserved.
+ */
+ if (sample_mode == 0x3)
+ return -1;
+
+ /*
+ * Check for all reserved value
+ */
+ switch (val) {
+ case 0x5:
+ case 0x9:
+ case 0xD:
+ case 0x19:
+ case 0x1D:
+ case 0x1A:
+ case 0x1E:
+ return -1;
+ }
+
+ /*
+ * MMCRA[48:51]/[52:55]) Threshold Start/Stop
+ * Events Selection.
+ * 0b11110000/0b00001111 is reserved.
+ */
+ val = (event >> EVENT_THR_CTL_SHIFT) & EVENT_THR_CTL_MASK;
+ if (((val & 0xF0) == 0xF0) || ((val & 0xF) == 0xF))
+ return -1;
+
+ return 0;
+}
diff --git a/arch/powerpc/perf/isa207-common.h b/arch/powerpc/perf/isa207-common.h
index 1af0e8c97ac7..ae8eaf05efd1 100644
--- a/arch/powerpc/perf/isa207-common.h
+++ b/arch/powerpc/perf/isa207-common.h
@@ -280,4 +280,6 @@ void isa207_get_mem_data_src(union perf_mem_data_src *dsrc, u32 flags,
struct pt_regs *regs);
void isa207_get_mem_weight(u64 *weight);
+int isa3_X_check_attr_config(struct perf_event *ev);
+
#endif
diff --git a/arch/powerpc/perf/power10-pmu.c b/arch/powerpc/perf/power10-pmu.c
index a901c1348cad..bc64354cab6a 100644
--- a/arch/powerpc/perf/power10-pmu.c
+++ b/arch/powerpc/perf/power10-pmu.c
@@ -106,6 +106,18 @@ static int power10_get_alternatives(u64 event, unsigned int flags, u64 alt[])
return num_alt;
}
+static int power10_check_attr_config(struct perf_event *ev)
+{
+ u64 val;
+ u64 event = ev->attr.config;
+
+ val = (event >> EVENT_SAMPLE_SHIFT) & EVENT_SAMPLE_MASK;
+ if (val == 0x10 || isa3_X_check_attr_config(ev))
+ return -1;
+
+ return 0;
+}
+
GENERIC_EVENT_ATTR(cpu-cycles, PM_RUN_CYC);
GENERIC_EVENT_ATTR(instructions, PM_RUN_INST_CMPL);
GENERIC_EVENT_ATTR(branch-instructions, PM_BR_CMPL);
@@ -559,6 +571,7 @@ static struct power_pmu power10_pmu = {
.attr_groups = power10_pmu_attr_groups,
.bhrb_nr = 32,
.capabilities = PERF_PMU_CAP_EXTENDED_REGS,
+ .check_attr_config = power10_check_attr_config,
};
int init_power10_pmu(void)
diff --git a/arch/powerpc/perf/power9-pmu.c b/arch/powerpc/perf/power9-pmu.c
index 2a57e93a79dc..b3b9b226d053 100644
--- a/arch/powerpc/perf/power9-pmu.c
+++ b/arch/powerpc/perf/power9-pmu.c
@@ -151,6 +151,18 @@ static int power9_get_alternatives(u64 event, unsigned int flags, u64 alt[])
return num_alt;
}
+static int power9_check_attr_config(struct perf_event *ev)
+{
+ u64 val;
+ u64 event = ev->attr.config;
+
+ val = (event >> EVENT_SAMPLE_SHIFT) & EVENT_SAMPLE_MASK;
+ if (val == 0xC || isa3_X_check_attr_config(ev))
+ return -1;
+
+ return 0;
+}
+
GENERIC_EVENT_ATTR(cpu-cycles, PM_CYC);
GENERIC_EVENT_ATTR(stalled-cycles-frontend, PM_ICT_NOSLOT_CYC);
GENERIC_EVENT_ATTR(stalled-cycles-backend, PM_CMPLU_STALL);
@@ -437,6 +449,7 @@ static struct power_pmu power9_pmu = {
.attr_groups = power9_pmu_attr_groups,
.bhrb_nr = 32,
.capabilities = PERF_PMU_CAP_EXTENDED_REGS,
+ .check_attr_config = power9_check_attr_config,
};
int init_power9_pmu(void)
--
2.26.2
^ permalink raw reply related
* Re: [PATCH 1/2] powerpc/perf: Infrastructure to support checking of attr.config*
From: Paul A. Clarke @ 2021-02-24 14:47 UTC (permalink / raw)
To: Madhavan Srinivasan; +Cc: Alexey Kardashevskiy, linuxppc-dev
In-Reply-To: <20210224142840.1170088-1-maddy@linux.ibm.com>
On Wed, Feb 24, 2021 at 07:58:39PM +0530, Madhavan Srinivasan wrote:
> Introduce code to support the checking of attr.config* for
> values which are reserved for a given platform.
> Performance Monitoring Unit (PMU) configuration registers
> have fileds that are reserved and specific values to bit fields
s/fileds/fields/
> as reserved. Writing a none zero values in these fields
Should the previous sentences say something like "required values
for specific bit fields" or "specific bit fields that are reserved"?
s/none zero/non-zero/
> or writing invalid value to bit fields will have unknown
> behaviours.
>
> Patch here add a generic call-back function "check_attr_config"
s/add/adds/ or "This patch adds ..." or just "Add ...".
> in "struct power_pmu", to be called in event_init to
> check for attr.config* values for a given platform.
> "check_attr_config" is valid only for raw event type.
>
> Suggested-by: Alexey Kardashevskiy <aik@ozlabs.ru>
> Signed-off-by: Madhavan Srinivasan <maddy@linux.ibm.com>
> ---
> arch/powerpc/include/asm/perf_event_server.h | 6 ++++++
> arch/powerpc/perf/core-book3s.c | 12 ++++++++++++
> 2 files changed, 18 insertions(+)
>
> diff --git a/arch/powerpc/include/asm/perf_event_server.h b/arch/powerpc/include/asm/perf_event_server.h
> index 00e7e671bb4b..dde97d7d9253 100644
> --- a/arch/powerpc/include/asm/perf_event_server.h
> +++ b/arch/powerpc/include/asm/perf_event_server.h
> @@ -67,6 +67,12 @@ struct power_pmu {
> * the pmu supports extended perf regs capability
> */
> int capabilities;
> + /*
> + * Function to check event code for values which are
> + * reserved. Function takes struct perf_event as input,
> + * since event code could be spread in attr.config*
> + */
> + int (*check_attr_config)(struct perf_event *ev);
> };
>
> /*
> diff --git a/arch/powerpc/perf/core-book3s.c b/arch/powerpc/perf/core-book3s.c
> index 6817331e22ff..679d67506299 100644
> --- a/arch/powerpc/perf/core-book3s.c
> +++ b/arch/powerpc/perf/core-book3s.c
> @@ -1958,6 +1958,18 @@ static int power_pmu_event_init(struct perf_event *event)
>
> if (ppmu->blacklist_ev && is_event_blacklisted(ev))
> return -EINVAL;
> + /*
> + * PMU config registers have fileds that are
> + * reserved and spacific values to bit fileds be reserved.
s/spacific/specific/
s/fileds/fields/
Same comment about "specific values to bit fields be reserved", and
rewording that to be more clear.
> + * This call-back will check the event code for same.
> + *
> + * Event type hardware and hw_cache will not value
> + * invalid values in the event code which is not true
> + * for raw event type.
I confess I don't understand what this means. (But it could be just me!)
> + */
> + if (ppmu->check_attr_config &&
> + ppmu->check_attr_config(event))
> + return -EINVAL;
> break;
> default:
> return -ENOENT;
> --
PC
^ permalink raw reply
* Re: [PATCH v5 2/3] KVM: PPC: Book3S HV: Add support for H_RPT_INVALIDATE
From: Fabiano Rosas @ 2021-02-24 15:58 UTC (permalink / raw)
To: Bharata B Rao, kvm-ppc, linuxppc-dev
Cc: aneesh.kumar, npiggin, Bharata B Rao, david
In-Reply-To: <20210224082510.3962423-3-bharata@linux.ibm.com>
Bharata B Rao <bharata@linux.ibm.com> writes:
> Implement H_RPT_INVALIDATE hcall and add KVM capability
> KVM_CAP_PPC_RPT_INVALIDATE to indicate the support for the same.
>
> This hcall does two types of TLB invalidations:
>
> 1. Process-scoped invalidations for guests with LPCR[GTSE]=0.
> This is currently not used in KVM as GTSE is not usually
> disabled in KVM.
> 2. Partition-scoped invalidations that an L1 hypervisor does on
> behalf of an L2 guest. This replaces the uses of the existing
> hcall H_TLB_INVALIDATE.
>
> In order to handle process scoped invalidations of L2, we
> intercept the nested exit handling code in L0 only to handle
> H_TLB_INVALIDATE hcall.
>
> Process scoped tlbie invalidations from L1 and nested guests
> need RS register for TLBIE instruction to contain both PID and
> LPID. This patch introduces primitives that execute tlbie
> instruction with both PID and LPID set in prepartion for
> H_RPT_INVALIDATE hcall.
>
> Signed-off-by: Bharata B Rao <bharata@linux.ibm.com>
> ---
> Documentation/virt/kvm/api.rst | 18 +++
> .../include/asm/book3s/64/tlbflush-radix.h | 4 +
> arch/powerpc/include/asm/kvm_book3s.h | 3 +
> arch/powerpc/include/asm/mmu_context.h | 11 ++
> arch/powerpc/kvm/book3s_hv.c | 90 +++++++++++
> arch/powerpc/kvm/book3s_hv_nested.c | 77 +++++++++
> arch/powerpc/kvm/powerpc.c | 3 +
> arch/powerpc/mm/book3s64/radix_tlb.c | 147 +++++++++++++++++-
> include/uapi/linux/kvm.h | 1 +
> 9 files changed, 350 insertions(+), 4 deletions(-)
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index 45fd862ac128..38ce3f21b21f 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -6225,6 +6225,24 @@ KVM_RUN_BUS_LOCK flag is used to distinguish between them.
> This capability can be used to check / enable 2nd DAWR feature provided
> by POWER10 processor.
>
> +7.23 KVM_CAP_PPC_RPT_INVALIDATE
> +------------------------------
> +
> +:Capability: KVM_CAP_PPC_RPT_INVALIDATE
> +:Architectures: ppc
> +:Type: vm
> +
> +This capability indicates that the kernel is capable of handling
> +H_RPT_INVALIDATE hcall.
> +
> +In order to enable the use of H_RPT_INVALIDATE in the guest,
> +user space might have to advertise it for the guest. For example,
> +IBM pSeries (sPAPR) guest starts using it if "hcall-rpt-invalidate" is
> +present in the "ibm,hypertas-functions" device-tree property.
> +
> +This capability is enabled for hypervisors on platforms like POWER9
> +that support radix MMU.
> +
> 8. Other capabilities.
> ======================
>
> diff --git a/arch/powerpc/include/asm/book3s/64/tlbflush-radix.h b/arch/powerpc/include/asm/book3s/64/tlbflush-radix.h
> index 8b33601cdb9d..a46fd37ad552 100644
> --- a/arch/powerpc/include/asm/book3s/64/tlbflush-radix.h
> +++ b/arch/powerpc/include/asm/book3s/64/tlbflush-radix.h
> @@ -4,6 +4,10 @@
>
> #include <asm/hvcall.h>
>
> +#define RIC_FLUSH_TLB 0
> +#define RIC_FLUSH_PWC 1
> +#define RIC_FLUSH_ALL 2
> +
> struct vm_area_struct;
> struct mm_struct;
> struct mmu_gather;
> diff --git a/arch/powerpc/include/asm/kvm_book3s.h b/arch/powerpc/include/asm/kvm_book3s.h
> index 2f5f919f6cd3..a1515f94400e 100644
> --- a/arch/powerpc/include/asm/kvm_book3s.h
> +++ b/arch/powerpc/include/asm/kvm_book3s.h
> @@ -305,6 +305,9 @@ void kvmhv_set_ptbl_entry(unsigned int lpid, u64 dw0, u64 dw1);
> void kvmhv_release_all_nested(struct kvm *kvm);
> long kvmhv_enter_nested_guest(struct kvm_vcpu *vcpu);
> long kvmhv_do_nested_tlbie(struct kvm_vcpu *vcpu);
> +long kvmhv_h_rpti_nested(struct kvm_vcpu *vcpu, unsigned long lpid,
> + unsigned long type, unsigned long pg_sizes,
> + unsigned long start, unsigned long end);
> int kvmhv_run_single_vcpu(struct kvm_vcpu *vcpu,
> u64 time_limit, unsigned long lpcr);
> void kvmhv_save_hv_regs(struct kvm_vcpu *vcpu, struct hv_guest_state *hr);
> diff --git a/arch/powerpc/include/asm/mmu_context.h b/arch/powerpc/include/asm/mmu_context.h
> index 652ce85f9410..820caf4e01b7 100644
> --- a/arch/powerpc/include/asm/mmu_context.h
> +++ b/arch/powerpc/include/asm/mmu_context.h
> @@ -124,8 +124,19 @@ static inline bool need_extra_context(struct mm_struct *mm, unsigned long ea)
>
> #if defined(CONFIG_KVM_BOOK3S_HV_POSSIBLE) && defined(CONFIG_PPC_RADIX_MMU)
> extern void radix_kvm_prefetch_workaround(struct mm_struct *mm);
> +void do_h_rpt_invalidate(unsigned long pid, unsigned long lpid,
> + unsigned long type, unsigned long page_size,
> + unsigned long psize, unsigned long start,
> + unsigned long end);
> #else
> static inline void radix_kvm_prefetch_workaround(struct mm_struct *mm) { }
> +static inline void do_h_rpt_invalidate(unsigned long pid,
> + unsigned long lpid,
> + unsigned long type,
> + unsigned long page_size,
> + unsigned long psize,
> + unsigned long start,
> + unsigned long end) { }
> #endif
>
> extern void switch_cop(struct mm_struct *next);
> diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c
> index 13bad6bf4c95..d83f006fc19d 100644
> --- a/arch/powerpc/kvm/book3s_hv.c
> +++ b/arch/powerpc/kvm/book3s_hv.c
> @@ -921,6 +921,69 @@ static int kvmppc_get_yield_count(struct kvm_vcpu *vcpu)
> return yield_count;
> }
>
> +static void do_h_rpt_invalidate_prs(unsigned long pid, unsigned long lpid,
> + unsigned long type, unsigned long pg_sizes,
> + unsigned long start, unsigned long end)
> +{
> + unsigned long psize;
> + struct mmu_psize_def *def;
> +
> + for (psize = 0; psize < MMU_PAGE_COUNT; psize++) {
> + def = &mmu_psize_defs[psize];
> + if (pg_sizes & def->h_rpt_pgsize)
> + do_h_rpt_invalidate(pid, lpid, type,
> + (1UL << def->shift), psize,
> + start, end);
> + }
> +}
> +
> +static void kvmppc_nested_rpt_invalidate(struct kvm_vcpu *vcpu)
> +{
> + do_h_rpt_invalidate_prs(kvmppc_get_gpr(vcpu, 4),
> + vcpu->arch.nested->shadow_lpid,
> + kvmppc_get_gpr(vcpu, 5),
> + kvmppc_get_gpr(vcpu, 6),
> + kvmppc_get_gpr(vcpu, 7),
> + kvmppc_get_gpr(vcpu, 8));
> + kvmppc_set_gpr(vcpu, 3, H_SUCCESS);
> +}
> +
> +static long kvmppc_h_rpt_invalidate(struct kvm_vcpu *vcpu,
> + unsigned long pid, unsigned long target,
> + unsigned long type, unsigned long pg_sizes,
> + unsigned long start, unsigned long end)
> +{
> + if (!kvm_is_radix(vcpu->kvm))
> + return H_UNSUPPORTED;
> +
> + /*
> + * For nested guests, this hcall is handled in
> + * L0. See kvmppc_handle_nested_exit() for details.
> + */
> + if (kvmhv_on_pseries())
> + return H_UNSUPPORTED;
> +
> + if (end < start)
> + return H_P5;
> +
> + if (type & H_RPTI_TYPE_NESTED) {
> + if (!nesting_enabled(vcpu->kvm))
> + return H_FUNCTION;
> +
> + /* Support only cores as target */
> + if (target != H_RPTI_TARGET_CMMU)
> + return H_P2;
> +
> + return kvmhv_h_rpti_nested(vcpu, pid,
> + (type & ~H_RPTI_TYPE_NESTED),
> + pg_sizes, start, end);
> + }
> +
> + do_h_rpt_invalidate_prs(pid, vcpu->kvm->arch.lpid, type, pg_sizes,
> + start, end);
> + return H_SUCCESS;
> +}
> +
> int kvmppc_pseries_do_hcall(struct kvm_vcpu *vcpu)
> {
> unsigned long req = kvmppc_get_gpr(vcpu, 3);
> @@ -1129,6 +1192,14 @@ int kvmppc_pseries_do_hcall(struct kvm_vcpu *vcpu)
> */
> ret = kvmppc_h_svm_init_abort(vcpu->kvm);
> break;
> + case H_RPT_INVALIDATE:
> + ret = kvmppc_h_rpt_invalidate(vcpu, kvmppc_get_gpr(vcpu, 4),
> + kvmppc_get_gpr(vcpu, 5),
> + kvmppc_get_gpr(vcpu, 6),
> + kvmppc_get_gpr(vcpu, 7),
> + kvmppc_get_gpr(vcpu, 8),
> + kvmppc_get_gpr(vcpu, 9));
> + break;
>
> default:
> return RESUME_HOST;
> @@ -1175,6 +1246,7 @@ static int kvmppc_hcall_impl_hv(unsigned long cmd)
> case H_XIRR_X:
> #endif
> case H_PAGE_INIT:
> + case H_RPT_INVALIDATE:
> return 1;
> }
>
> @@ -1590,6 +1662,24 @@ static int kvmppc_handle_nested_exit(struct kvm_vcpu *vcpu)
> if (!xics_on_xive())
> kvmppc_xics_rm_complete(vcpu, 0);
> break;
> + case BOOK3S_INTERRUPT_SYSCALL:
> + {
> + unsigned long req = kvmppc_get_gpr(vcpu, 3);
> +
> + /*
> + * The H_RPT_INVALIDATE hcalls issued by nested
> + * guests for process scoped invalidations when
> + * GTSE=0, are handled here in L0.
> + */
> + if (req == H_RPT_INVALIDATE) {
> + kvmppc_nested_rpt_invalidate(vcpu);
> + r = RESUME_GUEST;
> + break;
> + }
I'm inclined to say this is a bit too early. We're handling the hcall
before kvmhv_run_single_vcpu has fully finished and we'll skip some
code that has been running in all guest exits:
if (trap) {
if (!nested)
r = kvmppc_handle_exit_hv(vcpu, current);
else
r = kvmppc_handle_nested_exit(vcpu); <--- we're here
}
vcpu->arch.ret = r;
(...)
vcpu->arch.ceded = 0;
vc->vcore_state = VCORE_INACTIVE;
trace_kvmppc_run_core(vc, 1);
done:
kvmppc_remove_runnable(vc, vcpu);
trace_kvmppc_run_vcpu_exit(vcpu);
return vcpu->arch.ret;
Especially the kvmppc_remove_runnable function because it sets the
vcpu state:
vcpu->arch.state = KVMPPC_VCPU_BUSY_IN_HOST;
which should be the case if we're handling a hypercall.
I suggest we do similarly to the L1 exit code and defer the hcall
handling until after kvmppc_run_single_vcpu has exited, still inside the
is_kvmppc_resume_guest(r) loop.
So we'd set:
case BOOK3S_INTERRUPT_SYSCALL:
vcpu->run->exit_reason = KVM_EXIT_PAPR_HCALL;
r = RESUME_HOST;
break;
and perhaps introduce a new kvmppc_pseries_do_nested_hcall that's called
after kvmppc_run_single_vcpu.
> +
> + r = RESUME_HOST;
> + break;
> + }
> default:
> r = RESUME_HOST;
> break;
> diff --git a/arch/powerpc/kvm/book3s_hv_nested.c b/arch/powerpc/kvm/book3s_hv_nested.c
> index 0cd0e7aad588..ca43b2d38dce 100644
> --- a/arch/powerpc/kvm/book3s_hv_nested.c
> +++ b/arch/powerpc/kvm/book3s_hv_nested.c
> @@ -1191,6 +1191,83 @@ long kvmhv_do_nested_tlbie(struct kvm_vcpu *vcpu)
> return H_SUCCESS;
> }
>
> +static long do_tlb_invalidate_nested_tlb(struct kvm_vcpu *vcpu,
> + unsigned long lpid,
> + unsigned long page_size,
> + unsigned long ap,
> + unsigned long start,
> + unsigned long end)
> +{
> + unsigned long addr = start;
> + int ret;
> +
> + do {
> + ret = kvmhv_emulate_tlbie_tlb_addr(vcpu, lpid, ap,
> + get_epn(addr));
> + if (ret)
> + return ret;
> + addr += page_size;
> + } while (addr < end);
> +
> + return ret;
> +}
> +
> +static long do_tlb_invalidate_nested_all(struct kvm_vcpu *vcpu,
> + unsigned long lpid)
> +{
> + struct kvm *kvm = vcpu->kvm;
> + struct kvm_nested_guest *gp;
> +
> + gp = kvmhv_get_nested(kvm, lpid, false);
> + if (gp) {
> + kvmhv_emulate_tlbie_lpid(vcpu, gp, RIC_FLUSH_ALL);
> + kvmhv_put_nested(gp);
> + }
> + return H_SUCCESS;
> +}
> +
> +long kvmhv_h_rpti_nested(struct kvm_vcpu *vcpu, unsigned long lpid,
> + unsigned long type, unsigned long pg_sizes,
> + unsigned long start, unsigned long end)
> +{
> + struct kvm_nested_guest *gp;
> + long ret;
> + unsigned long psize, ap;
> +
> + /*
> + * If L2 lpid isn't valid, we need to return H_PARAMETER.
> + *
> + * However, nested KVM issues a L2 lpid flush call when creating
> + * partition table entries for L2. This happens even before the
> + * corresponding shadow lpid is created in HV which happens in
> + * H_ENTER_NESTED call. Since we can't differentiate this case from
> + * the invalid case, we ignore such flush requests and return success.
> + */
> + gp = kvmhv_find_nested(vcpu->kvm, lpid);
> + if (!gp)
> + return H_SUCCESS;
> +
> + if ((type & H_RPTI_TYPE_NESTED_ALL) == H_RPTI_TYPE_NESTED_ALL)
> + return do_tlb_invalidate_nested_all(vcpu, lpid);
> +
> + if ((type & H_RPTI_TYPE_TLB) == H_RPTI_TYPE_TLB) {
> + struct mmu_psize_def *def;
> +
> + for (psize = 0; psize < MMU_PAGE_COUNT; psize++) {
> + def = &mmu_psize_defs[psize];
> + if (!(pg_sizes & def->h_rpt_pgsize))
> + continue;
> +
> + ret = do_tlb_invalidate_nested_tlb(vcpu, lpid,
> + (1UL << def->shift),
> + ap, start, end);
> + if (ret)
> + return H_P4;
> + }
> + }
> + return H_SUCCESS;
> +}
> +
> /* Used to convert a nested guest real address to a L1 guest real address */
> static int kvmhv_translate_addr_nested(struct kvm_vcpu *vcpu,
> struct kvm_nested_guest *gp,
> diff --git a/arch/powerpc/kvm/powerpc.c b/arch/powerpc/kvm/powerpc.c
> index a2a68a958fa0..be33b5321a76 100644
> --- a/arch/powerpc/kvm/powerpc.c
> +++ b/arch/powerpc/kvm/powerpc.c
> @@ -682,6 +682,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
> r = !!(hv_enabled && kvmppc_hv_ops->enable_dawr1 &&
> !kvmppc_hv_ops->enable_dawr1(NULL));
> break;
> + case KVM_CAP_PPC_RPT_INVALIDATE:
> + r = 1;
> + break;
> #endif
> default:
> r = 0;
> diff --git a/arch/powerpc/mm/book3s64/radix_tlb.c b/arch/powerpc/mm/book3s64/radix_tlb.c
> index 409e61210789..440d84fffa8c 100644
> --- a/arch/powerpc/mm/book3s64/radix_tlb.c
> +++ b/arch/powerpc/mm/book3s64/radix_tlb.c
> @@ -20,10 +20,6 @@
>
> #include "internal.h"
>
> -#define RIC_FLUSH_TLB 0
> -#define RIC_FLUSH_PWC 1
> -#define RIC_FLUSH_ALL 2
> -
> /*
> * tlbiel instruction for radix, set invalidation
> * i.e., r=1 and is=01 or is=10 or is=11
> @@ -130,6 +126,21 @@ static __always_inline void __tlbie_pid(unsigned long pid, unsigned long ric)
> trace_tlbie(0, 0, rb, rs, ric, prs, r);
> }
>
> +static __always_inline void __tlbie_pid_lpid(unsigned long pid,
> + unsigned long lpid,
> + unsigned long ric)
> +{
> + unsigned long rb, rs, prs, r;
> +
> + rb = PPC_BIT(53); /* IS = 1 */
> + rs = (pid << PPC_BITLSHIFT(31)) | (lpid & ~(PPC_BITMASK(0, 31)));
> + prs = 1; /* process scoped */
> + r = 1; /* radix format */
> +
> + asm volatile(PPC_TLBIE_5(%0, %4, %3, %2, %1)
> + : : "r"(rb), "i"(r), "i"(prs), "i"(ric), "r"(rs) : "memory");
> + trace_tlbie(0, 0, rb, rs, ric, prs, r);
> +}
> static __always_inline void __tlbie_lpid(unsigned long lpid, unsigned long ric)
> {
> unsigned long rb,rs,prs,r;
> @@ -190,6 +201,23 @@ static __always_inline void __tlbie_va(unsigned long va, unsigned long pid,
> trace_tlbie(0, 0, rb, rs, ric, prs, r);
> }
>
> +static __always_inline void __tlbie_va_lpid(unsigned long va, unsigned long pid,
> + unsigned long lpid,
> + unsigned long ap, unsigned long ric)
> +{
> + unsigned long rb, rs, prs, r;
> +
> + rb = va & ~(PPC_BITMASK(52, 63));
> + rb |= ap << PPC_BITLSHIFT(58);
> + rs = (pid << PPC_BITLSHIFT(31)) | (lpid & ~(PPC_BITMASK(0, 31)));
> + prs = 1; /* process scoped */
> + r = 1; /* radix format */
> +
> + asm volatile(PPC_TLBIE_5(%0, %4, %3, %2, %1)
> + : : "r"(rb), "i"(r), "i"(prs), "i"(ric), "r"(rs) : "memory");
> + trace_tlbie(0, 0, rb, rs, ric, prs, r);
> +}
> +
> static __always_inline void __tlbie_lpid_va(unsigned long va, unsigned long lpid,
> unsigned long ap, unsigned long ric)
> {
> @@ -235,6 +263,22 @@ static inline void fixup_tlbie_va_range(unsigned long va, unsigned long pid,
> }
> }
>
> +static inline void fixup_tlbie_va_range_lpid(unsigned long va,
> + unsigned long pid,
> + unsigned long lpid,
> + unsigned long ap)
> +{
> + if (cpu_has_feature(CPU_FTR_P9_TLBIE_ERAT_BUG)) {
> + asm volatile("ptesync" : : : "memory");
> + __tlbie_pid_lpid(0, lpid, RIC_FLUSH_TLB);
> + }
> +
> + if (cpu_has_feature(CPU_FTR_P9_TLBIE_STQ_BUG)) {
> + asm volatile("ptesync" : : : "memory");
> + __tlbie_va_lpid(va, pid, lpid, ap, RIC_FLUSH_TLB);
> + }
> +}
> +
> static inline void fixup_tlbie_pid(unsigned long pid)
> {
> /*
> @@ -254,6 +298,25 @@ static inline void fixup_tlbie_pid(unsigned long pid)
> }
> }
>
> +static inline void fixup_tlbie_pid_lpid(unsigned long pid, unsigned long lpid)
> +{
> + /*
> + * We can use any address for the invalidation, pick one which is
> + * probably unused as an optimisation.
> + */
> + unsigned long va = ((1UL << 52) - 1);
> +
> + if (cpu_has_feature(CPU_FTR_P9_TLBIE_ERAT_BUG)) {
> + asm volatile("ptesync" : : : "memory");
> + __tlbie_pid_lpid(0, lpid, RIC_FLUSH_TLB);
> + }
> +
> + if (cpu_has_feature(CPU_FTR_P9_TLBIE_STQ_BUG)) {
> + asm volatile("ptesync" : : : "memory");
> + __tlbie_va_lpid(va, pid, lpid, mmu_get_ap(MMU_PAGE_64K),
> + RIC_FLUSH_TLB);
> + }
> +}
>
> static inline void fixup_tlbie_lpid_va(unsigned long va, unsigned long lpid,
> unsigned long ap)
> @@ -344,6 +407,31 @@ static inline void _tlbie_pid(unsigned long pid, unsigned long ric)
> asm volatile("eieio; tlbsync; ptesync": : :"memory");
> }
>
> +static inline void _tlbie_pid_lpid(unsigned long pid, unsigned long lpid,
> + unsigned long ric)
> +{
> + asm volatile("ptesync" : : : "memory");
> +
> + /*
> + * Workaround the fact that the "ric" argument to __tlbie_pid
> + * must be a compile-time contraint to match the "i" constraint
> + * in the asm statement.
> + */
> + switch (ric) {
> + case RIC_FLUSH_TLB:
> + __tlbie_pid_lpid(pid, lpid, RIC_FLUSH_TLB);
> + fixup_tlbie_pid_lpid(pid, lpid);
> + break;
> + case RIC_FLUSH_PWC:
> + __tlbie_pid_lpid(pid, lpid, RIC_FLUSH_PWC);
> + break;
> + case RIC_FLUSH_ALL:
> + default:
> + __tlbie_pid_lpid(pid, lpid, RIC_FLUSH_ALL);
> + fixup_tlbie_pid_lpid(pid, lpid);
> + }
> + asm volatile("eieio; tlbsync; ptesync" : : : "memory");
> +}
> struct tlbiel_pid {
> unsigned long pid;
> unsigned long ric;
> @@ -469,6 +557,20 @@ static inline void __tlbie_va_range(unsigned long start, unsigned long end,
> fixup_tlbie_va_range(addr - page_size, pid, ap);
> }
>
> +static inline void __tlbie_va_range_lpid(unsigned long start, unsigned long end,
> + unsigned long pid, unsigned long lpid,
> + unsigned long page_size,
> + unsigned long psize)
> +{
> + unsigned long addr;
> + unsigned long ap = mmu_get_ap(psize);
> +
> + for (addr = start; addr < end; addr += page_size)
> + __tlbie_va_lpid(addr, pid, lpid, ap, RIC_FLUSH_TLB);
> +
> + fixup_tlbie_va_range_lpid(addr - page_size, pid, lpid, ap);
> +}
> +
> static __always_inline void _tlbie_va(unsigned long va, unsigned long pid,
> unsigned long psize, unsigned long ric)
> {
> @@ -549,6 +651,18 @@ static inline void _tlbie_va_range(unsigned long start, unsigned long end,
> asm volatile("eieio; tlbsync; ptesync": : :"memory");
> }
>
> +static inline void _tlbie_va_range_lpid(unsigned long start, unsigned long end,
> + unsigned long pid, unsigned long lpid,
> + unsigned long page_size,
> + unsigned long psize, bool also_pwc)
> +{
> + asm volatile("ptesync" : : : "memory");
> + if (also_pwc)
> + __tlbie_pid_lpid(pid, lpid, RIC_FLUSH_PWC);
> + __tlbie_va_range_lpid(start, end, pid, lpid, page_size, psize);
> + asm volatile("eieio; tlbsync; ptesync" : : : "memory");
> +}
> +
> static inline void _tlbiel_va_range_multicast(struct mm_struct *mm,
> unsigned long start, unsigned long end,
> unsigned long pid, unsigned long page_size,
> @@ -1381,4 +1495,29 @@ extern void radix_kvm_prefetch_workaround(struct mm_struct *mm)
> }
> }
> EXPORT_SYMBOL_GPL(radix_kvm_prefetch_workaround);
> +
> +/*
> + * Process-scoped invalidations for a given LPID.
> + */
> +void do_h_rpt_invalidate(unsigned long pid, unsigned long lpid,
> + unsigned long type, unsigned long page_size,
> + unsigned long psize, unsigned long start,
> + unsigned long end)
> +{
> + if ((type & H_RPTI_TYPE_ALL) == H_RPTI_TYPE_ALL) {
> + _tlbie_pid_lpid(pid, lpid, RIC_FLUSH_ALL);
> + return;
> + }
> +
> + if (type & H_RPTI_TYPE_PWC)
> + _tlbie_pid_lpid(pid, lpid, RIC_FLUSH_PWC);
> +
> + if (!start && end == -1) /* PID */
> + _tlbie_pid_lpid(pid, lpid, RIC_FLUSH_TLB);
> + else /* EA */
> + _tlbie_va_range_lpid(start, end, pid, lpid, page_size,
> + psize, false);
> +}
> +EXPORT_SYMBOL_GPL(do_h_rpt_invalidate);
> +
> #endif /* CONFIG_KVM_BOOK3S_HV_POSSIBLE */
> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index 8b281f722e5b..f8c84a62e8f3 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -1078,6 +1078,7 @@ struct kvm_ppc_resize_hpt {
> #define KVM_CAP_DIRTY_LOG_RING 192
> #define KVM_CAP_X86_BUS_LOCK_EXIT 193
> #define KVM_CAP_PPC_DAWR1 194
> +#define KVM_CAP_PPC_RPT_INVALIDATE 195
>
> #ifdef KVM_CAP_IRQ_ROUTING
^ permalink raw reply
* Re: [PATCH] arch: powerpc: kernel: Change droping to dropping in the file traps.c
From: Randy Dunlap @ 2021-02-24 16:24 UTC (permalink / raw)
To: Bhaskar Chowdhury, mpe, benh, paulus, christophe.leroy, npiggin,
jniethe5, alistair, mikey, linuxppc-dev, linux-kernel
In-Reply-To: <20210224075547.763063-1-unixbhaskar@gmail.com>
On 2/23/21 11:55 PM, Bhaskar Chowdhury wrote:
>
> s/droping/dropping/
>
> Signed-off-by: Bhaskar Chowdhury <unixbhaskar@gmail.com>
Acked-by: Randy Dunlap <rdunlap@infradead.org>
> ---
> arch/powerpc/kernel/traps.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/powerpc/kernel/traps.c b/arch/powerpc/kernel/traps.c
> index 1583fd1c6010..83a53b67412a 100644
> --- a/arch/powerpc/kernel/traps.c
> +++ b/arch/powerpc/kernel/traps.c
> @@ -405,7 +405,7 @@ void hv_nmi_check_nonrecoverable(struct pt_regs *regs)
> * Now test if the interrupt has hit a range that may be using
> * HSPRG1 without having RI=0 (i.e., an HSRR interrupt). The
> * problem ranges all run un-relocated. Test real and virt modes
> - * at the same time by droping the high bit of the nip (virt mode
> + * at the same time by dropping the high bit of the nip (virt mode
> * entry points still have the +0x4000 offset).
> */
> nip &= ~0xc000000000000000ULL;
> --
> 2.30.1
>
--
~Randy
^ permalink raw reply
* Re: [PATCH v6 01/10] powerpc/uaccess: Add unsafe_copy_from_user
From: Christopher M. Riedl @ 2021-02-24 18:01 UTC (permalink / raw)
To: Christophe Leroy, linuxppc-dev; +Cc: Daniel Axtens
In-Reply-To: <3393c40b-6f5f-3683-d03b-aa19bd25d655@csgroup.eu>
On Tue Feb 23, 2021 at 11:15 AM CST, Christophe Leroy wrote:
>
>
> Le 21/02/2021 à 02:23, Christopher M. Riedl a écrit :
> > Just wrap __copy_tofrom_user() for the usual 'unsafe' pattern which
> > accepts a label to goto on error.
> >
> > Signed-off-by: Christopher M. Riedl <cmr@codefail.de>
> > Reviewed-by: Daniel Axtens <dja@axtens.net>
> > ---
> > arch/powerpc/include/asm/uaccess.h | 3 +++
> > 1 file changed, 3 insertions(+)
> >
> > diff --git a/arch/powerpc/include/asm/uaccess.h b/arch/powerpc/include/asm/uaccess.h
> > index 78e2a3990eab..33b2de642120 100644
> > --- a/arch/powerpc/include/asm/uaccess.h
> > +++ b/arch/powerpc/include/asm/uaccess.h
> > @@ -487,6 +487,9 @@ user_write_access_begin(const void __user *ptr, size_t len)
> > #define unsafe_put_user(x, p, e) \
> > __unsafe_put_user_goto((__typeof__(*(p)))(x), (p), sizeof(*(p)), e)
> >
> > +#define unsafe_copy_from_user(d, s, l, e) \
> > + unsafe_op_wrap(__copy_tofrom_user((__force void __user *)d, s, l), e)
> > +
>
> Could we perform same as unsafe_copy_to_user() instead of calling an
> external function which is
> banned in principle inside uaccess blocks ?
Yup, with your patch to move the barrier_nospec() into the allowance
helpers this makes sense now. I just tried it and performance does not
change significantly w/ either radix or hash translation. I will include
this change in the next spin - thanks!
>
>
> > #define unsafe_copy_to_user(d, s, l, e) \
> > do { \
> > u8 __user *_dst = (u8 __user *)(d); \
> >
^ permalink raw reply
* Re: [PATCH 12/13] KVM: PPC: Book3S HV: Move radix MMU switching together in the P9 path
From: Fabiano Rosas @ 2021-02-24 20:36 UTC (permalink / raw)
To: Nicholas Piggin, kvm-ppc; +Cc: linuxppc-dev, Nicholas Piggin
In-Reply-To: <20210219063542.1425130-13-npiggin@gmail.com>
Nicholas Piggin <npiggin@gmail.com> writes:
> Switching the MMU from radix<->radix mode is tricky particularly as the
> MMU can remain enabled and requires a certain sequence of SPR updates.
> Move these together into their own functions.
>
> This also includes the radix TLB check / flush because it's tied in to
> MMU switching due to tlbiel getting LPID from LPIDR.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> ---
<snip>
> @@ -4117,7 +4138,7 @@ int kvmhv_run_single_vcpu(struct kvm_vcpu *vcpu, u64 time_limit,
> {
> struct kvm_run *run = vcpu->run;
> int trap, r, pcpu;
> - int srcu_idx, lpid;
> + int srcu_idx;
> struct kvmppc_vcore *vc;
> struct kvm *kvm = vcpu->kvm;
> struct kvm_nested_guest *nested = vcpu->arch.nested;
> @@ -4191,13 +4212,6 @@ int kvmhv_run_single_vcpu(struct kvm_vcpu *vcpu, u64 time_limit,
> vc->vcore_state = VCORE_RUNNING;
> trace_kvmppc_run_core(vc, 0);
>
> - if (cpu_has_feature(CPU_FTR_HVMODE)) {
> - lpid = nested ? nested->shadow_lpid : kvm->arch.lpid;
> - mtspr(SPRN_LPID, lpid);
> - isync();
> - kvmppc_check_need_tlb_flush(kvm, pcpu, nested);
> - }
> -
What about the counterpart to this^ down below?
if (cpu_has_feature(CPU_FTR_HVMODE)) {
mtspr(SPRN_LPID, kvm->arch.host_lpid);
isync();
}
> guest_enter_irqoff();
>
> srcu_idx = srcu_read_lock(&kvm->srcu);
^ permalink raw reply
* Re: [PATCH v6 06/10] powerpc/signal64: Replace setup_sigcontext() w/ unsafe_setup_sigcontext()
From: Christopher M. Riedl @ 2021-02-24 22:00 UTC (permalink / raw)
To: Christophe Leroy, linuxppc-dev
In-Reply-To: <2115850c-8687-6b61-11ca-0f6fab94a14f@csgroup.eu>
On Tue Feb 23, 2021 at 11:12 AM CST, Christophe Leroy wrote:
>
>
> Le 21/02/2021 à 02:23, Christopher M. Riedl a écrit :
> > Previously setup_sigcontext() performed a costly KUAP switch on every
> > uaccess operation. These repeated uaccess switches cause a significant
> > drop in signal handling performance.
> >
> > Rewrite setup_sigcontext() to assume that a userspace write access window
> > is open by replacing all uaccess functions with their 'unsafe' versions.
> > Modify the callers to first open, call unsafe_setup_sigcontext() and
> > then close the uaccess window.
>
> Do you plan to also convert setup_tm_sigcontexts() ?
> It would allow to then remove copy_fpr_to_user() and
> copy_ckfpr_to_user() and maybe other functions too.
I don't intend to convert the TM functions as part of this series.
Partially because I've been "threatened" with TM ownership for touching
the code :) and also because TM enhancements are a pretty low priority I
think.
>
> Christophe
>
> >
> > Signed-off-by: Christopher M. Riedl <cmr@codefail.de>
> > ---
> > arch/powerpc/kernel/signal_64.c | 71 ++++++++++++++++++++-------------
> > 1 file changed, 44 insertions(+), 27 deletions(-)
> >
> > diff --git a/arch/powerpc/kernel/signal_64.c b/arch/powerpc/kernel/signal_64.c
> > index bd8d210c9115..3faaa736ed62 100644
> > --- a/arch/powerpc/kernel/signal_64.c
> > +++ b/arch/powerpc/kernel/signal_64.c
> > @@ -101,9 +101,13 @@ static void prepare_setup_sigcontext(struct task_struct *tsk)
> > * Set up the sigcontext for the signal frame.
> > */
> >
> > -static long setup_sigcontext(struct sigcontext __user *sc,
> > - struct task_struct *tsk, int signr, sigset_t *set,
> > - unsigned long handler, int ctx_has_vsx_region)
> > +#define unsafe_setup_sigcontext(sc, tsk, signr, set, handler, \
> > + ctx_has_vsx_region, e) \
> > + unsafe_op_wrap(__unsafe_setup_sigcontext(sc, tsk, signr, set, \
> > + handler, ctx_has_vsx_region), e)
> > +static long notrace __unsafe_setup_sigcontext(struct sigcontext __user *sc,
> > + struct task_struct *tsk, int signr, sigset_t *set,
> > + unsigned long handler, int ctx_has_vsx_region)
> > {
> > /* When CONFIG_ALTIVEC is set, we _always_ setup v_regs even if the
> > * process never used altivec yet (MSR_VEC is zero in pt_regs of
> > @@ -118,20 +122,19 @@ static long setup_sigcontext(struct sigcontext __user *sc,
> > #endif
> > struct pt_regs *regs = tsk->thread.regs;
> > unsigned long msr = regs->msr;
> > - long err = 0;
> > /* Force usr to alway see softe as 1 (interrupts enabled) */
> > unsigned long softe = 0x1;
> >
> > BUG_ON(tsk != current);
> >
> > #ifdef CONFIG_ALTIVEC
> > - err |= __put_user(v_regs, &sc->v_regs);
> > + unsafe_put_user(v_regs, &sc->v_regs, efault_out);
> >
> > /* save altivec registers */
> > if (tsk->thread.used_vr) {
> > /* Copy 33 vec registers (vr0..31 and vscr) to the stack */
> > - err |= __copy_to_user(v_regs, &tsk->thread.vr_state,
> > - 33 * sizeof(vector128));
> > + unsafe_copy_to_user(v_regs, &tsk->thread.vr_state,
> > + 33 * sizeof(vector128), efault_out);
> > /* set MSR_VEC in the MSR value in the frame to indicate that sc->v_reg)
> > * contains valid data.
> > */
> > @@ -140,12 +143,12 @@ static long setup_sigcontext(struct sigcontext __user *sc,
> > /* We always copy to/from vrsave, it's 0 if we don't have or don't
> > * use altivec.
> > */
> > - err |= __put_user(tsk->thread.vrsave, (u32 __user *)&v_regs[33]);
> > + unsafe_put_user(tsk->thread.vrsave, (u32 __user *)&v_regs[33], efault_out);
> > #else /* CONFIG_ALTIVEC */
> > - err |= __put_user(0, &sc->v_regs);
> > + unsafe_put_user(0, &sc->v_regs, efault_out);
> > #endif /* CONFIG_ALTIVEC */
> > /* copy fpr regs and fpscr */
> > - err |= copy_fpr_to_user(&sc->fp_regs, tsk);
> > + unsafe_copy_fpr_to_user(&sc->fp_regs, tsk, efault_out);
> >
> > /*
> > * Clear the MSR VSX bit to indicate there is no valid state attached
> > @@ -160,24 +163,27 @@ static long setup_sigcontext(struct sigcontext __user *sc,
> > */
> > if (tsk->thread.used_vsr && ctx_has_vsx_region) {
> > v_regs += ELF_NVRREG;
> > - err |= copy_vsx_to_user(v_regs, tsk);
> > + unsafe_copy_vsx_to_user(v_regs, tsk, efault_out);
> > /* set MSR_VSX in the MSR value in the frame to
> > * indicate that sc->vs_reg) contains valid data.
> > */
> > msr |= MSR_VSX;
> > }
> > #endif /* CONFIG_VSX */
> > - err |= __put_user(&sc->gp_regs, &sc->regs);
> > + unsafe_put_user(&sc->gp_regs, &sc->regs, efault_out);
> > WARN_ON(!FULL_REGS(regs));
> > - err |= __copy_to_user(&sc->gp_regs, regs, GP_REGS_SIZE);
> > - err |= __put_user(msr, &sc->gp_regs[PT_MSR]);
> > - err |= __put_user(softe, &sc->gp_regs[PT_SOFTE]);
> > - err |= __put_user(signr, &sc->signal);
> > - err |= __put_user(handler, &sc->handler);
> > + unsafe_copy_to_user(&sc->gp_regs, regs, GP_REGS_SIZE, efault_out);
> > + unsafe_put_user(msr, &sc->gp_regs[PT_MSR], efault_out);
> > + unsafe_put_user(softe, &sc->gp_regs[PT_SOFTE], efault_out);
> > + unsafe_put_user(signr, &sc->signal, efault_out);
> > + unsafe_put_user(handler, &sc->handler, efault_out);
> > if (set != NULL)
> > - err |= __put_user(set->sig[0], &sc->oldmask);
> > + unsafe_put_user(set->sig[0], &sc->oldmask, efault_out);
> >
> > - return err;
> > + return 0;
> > +
> > +efault_out:
> > + return -EFAULT;
> > }
> >
> > #ifdef CONFIG_PPC_TRANSACTIONAL_MEM
> > @@ -670,12 +676,15 @@ SYSCALL_DEFINE3(swapcontext, struct ucontext __user *, old_ctx,
> >
> > if (old_ctx != NULL) {
> > prepare_setup_sigcontext(current);
> > - if (!access_ok(old_ctx, ctx_size)
> > - || setup_sigcontext(&old_ctx->uc_mcontext, current, 0, NULL, 0,
> > - ctx_has_vsx_region)
> > - || __copy_to_user(&old_ctx->uc_sigmask,
> > - ¤t->blocked, sizeof(sigset_t)))
> > + if (!user_write_access_begin(old_ctx, ctx_size))
> > return -EFAULT;
> > +
> > + unsafe_setup_sigcontext(&old_ctx->uc_mcontext, current, 0, NULL,
> > + 0, ctx_has_vsx_region, efault_out);
> > + unsafe_copy_to_user(&old_ctx->uc_sigmask, ¤t->blocked,
> > + sizeof(sigset_t), efault_out);
> > +
> > + user_write_access_end();
> > }
> > if (new_ctx == NULL)
> > return 0;
> > @@ -704,6 +713,10 @@ SYSCALL_DEFINE3(swapcontext, struct ucontext __user *, old_ctx,
> > /* This returns like rt_sigreturn */
> > set_thread_flag(TIF_RESTOREALL);
> > return 0;
> > +
> > +efault_out:
> > + user_write_access_end();
> > + return -EFAULT;
> > }
> >
> >
> > @@ -854,9 +867,13 @@ int handle_rt_signal64(struct ksignal *ksig, sigset_t *set,
> > } else {
> > err |= __put_user(0, &frame->uc.uc_link);
> > prepare_setup_sigcontext(tsk);
> > - err |= setup_sigcontext(&frame->uc.uc_mcontext, tsk, ksig->sig,
> > - NULL, (unsigned long)ksig->ka.sa.sa_handler,
> > - 1);
> > + if (!user_write_access_begin(&frame->uc.uc_mcontext,
> > + sizeof(frame->uc.uc_mcontext)))
> > + return -EFAULT;
> > + err |= __unsafe_setup_sigcontext(&frame->uc.uc_mcontext, tsk,
> > + ksig->sig, NULL,
> > + (unsigned long)ksig->ka.sa.sa_handler, 1);
> > + user_write_access_end();
> > }
> > err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
> > if (err)
> >
^ permalink raw reply
* [PATCH v3 0/7] Add audio driver base on rpmsg on i.MX platform
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
On Asymmetric multiprocessor, there is Cortex-A core and Cortex-M core,
Linux is running on A core, RTOS is running on M core.
The audio hardware device can be controlled by Cortex-M device,
So audio playback/capture can be handled by M core.
Rpmsg is the interface for sending and receiving msg to and from M
core, that we can create a virtual sound on Cortex-A core side.
A core will tell the Cortex-M core sound format/rate/channel,
where is the data buffer, what is the period size, when to start,
when to stop and when suspend or resume happen, each of this behavior
there is defined rpmsg command.
Especially we designed the low power audio case, that is to
allocate a large buffer and fill the data, then Cortex-A core can go
to sleep mode, Cortex-M core continue to play the sound, when the
buffer is consumed, Cortex-M core will trigger the Cortex-A core to
wakeup to fill data.
changes in v3:
- add local refcount for clk enablement in hw_params()
- update the document according Rob's comments
changes in v2:
- update codes and comments according to Mark's comments
Shengjiu Wang (7):
ASoC: soc-component: Add snd_soc_pcm_component_ack
ASoC: fsl_rpmsg: Add CPU DAI driver for audio base on rpmsg
ASoC: dt-bindings: fsl_rpmsg: Add binding doc for rpmsg cpu dai driver
ASoC: imx-audio-rpmsg: Add rpmsg_driver for audio channel
ASoC: imx-pcm-rpmsg: Add platform driver for audio base on rpmsg
ASoC: imx-rpmsg: Add machine driver for audio base on rpmsg
ASoC: dt-bindings: imx-rpmsg: Add binding doc for rpmsg machine driver
.../devicetree/bindings/sound/fsl,rpmsg.yaml | 94 ++
.../bindings/sound/imx-audio-rpmsg.yaml | 55 ++
include/sound/soc-component.h | 3 +
sound/soc/fsl/Kconfig | 28 +
sound/soc/fsl/Makefile | 6 +
sound/soc/fsl/fsl_rpmsg.c | 261 +++++
sound/soc/fsl/fsl_rpmsg.h | 40 +
sound/soc/fsl/imx-audio-rpmsg.c | 151 +++
sound/soc/fsl/imx-pcm-rpmsg.c | 919 ++++++++++++++++++
sound/soc/fsl/imx-pcm-rpmsg.h | 512 ++++++++++
sound/soc/fsl/imx-rpmsg.c | 148 +++
sound/soc/soc-component.c | 14 +
sound/soc/soc-pcm.c | 2 +
13 files changed, 2233 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml
create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-rpmsg.yaml
create mode 100644 sound/soc/fsl/fsl_rpmsg.c
create mode 100644 sound/soc/fsl/fsl_rpmsg.h
create mode 100644 sound/soc/fsl/imx-audio-rpmsg.c
create mode 100644 sound/soc/fsl/imx-pcm-rpmsg.c
create mode 100644 sound/soc/fsl/imx-pcm-rpmsg.h
create mode 100644 sound/soc/fsl/imx-rpmsg.c
--
2.27.0
^ permalink raw reply
* [PATCH v3 1/7] ASoC: soc-component: Add snd_soc_pcm_component_ack
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
In-Reply-To: <1614221563-26822-1-git-send-email-shengjiu.wang@nxp.com>
Add snd_soc_pcm_component_ack back, which can be used to get updated
buffer pointer in platform driver.
On Asymmetric multiprocessor, this pointer can be sent to Cortex-M
core for audio processing.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
include/sound/soc-component.h | 3 +++
sound/soc/soc-component.c | 14 ++++++++++++++
sound/soc/soc-pcm.c | 2 ++
3 files changed, 19 insertions(+)
diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h
index 5b47768222b7..2dc8c7e3d1a6 100644
--- a/include/sound/soc-component.h
+++ b/include/sound/soc-component.h
@@ -146,6 +146,8 @@ struct snd_soc_component_driver {
int (*mmap)(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct vm_area_struct *vma);
+ int (*ack)(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream);
const struct snd_compress_ops *compress_ops;
@@ -498,5 +500,6 @@ int snd_soc_pcm_component_pm_runtime_get(struct snd_soc_pcm_runtime *rtd,
void *stream);
void snd_soc_pcm_component_pm_runtime_put(struct snd_soc_pcm_runtime *rtd,
void *stream, int rollback);
+int snd_soc_pcm_component_ack(struct snd_pcm_substream *substream);
#endif /* __SOC_COMPONENT_H */
diff --git a/sound/soc/soc-component.c b/sound/soc/soc-component.c
index 159bf88b9f8c..a9fbb2d26412 100644
--- a/sound/soc/soc-component.c
+++ b/sound/soc/soc-component.c
@@ -1212,3 +1212,17 @@ void snd_soc_pcm_component_pm_runtime_put(struct snd_soc_pcm_runtime *rtd,
soc_component_mark_pop(component, stream, pm);
}
}
+
+int snd_soc_pcm_component_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *component;
+ int i;
+
+ /* FIXME: use 1st pointer */
+ for_each_rtd_components(rtd, i, component)
+ if (component->driver->ack)
+ return component->driver->ack(component, substream);
+
+ return 0;
+}
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index 14d85ca1e435..a1b81d4d678c 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -2833,6 +2833,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
rtd->ops.page = snd_soc_pcm_component_page;
if (drv->mmap)
rtd->ops.mmap = snd_soc_pcm_component_mmap;
+ if (drv->ack)
+ rtd->ops.ack = snd_soc_pcm_component_ack;
}
if (playback)
--
2.27.0
^ permalink raw reply related
* [PATCH v3 2/7] ASoC: fsl_rpmsg: Add CPU DAI driver for audio base on rpmsg
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
In-Reply-To: <1614221563-26822-1-git-send-email-shengjiu.wang@nxp.com>
This is a cpu dai driver for rpmsg audio use case,
which is mainly used for getting the user's configuration
from devicetree and configure the clocks which is used by
Cortex-M core.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
sound/soc/fsl/Kconfig | 7 +
sound/soc/fsl/Makefile | 2 +
sound/soc/fsl/fsl_rpmsg.c | 261 ++++++++++++++++++++++++++++++++++++++
sound/soc/fsl/fsl_rpmsg.h | 40 ++++++
4 files changed, 310 insertions(+)
create mode 100644 sound/soc/fsl/fsl_rpmsg.c
create mode 100644 sound/soc/fsl/fsl_rpmsg.h
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index d7f30036d434..a688c3c2efbc 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -115,6 +115,13 @@ config SND_SOC_FSL_AUD2HTX
config SND_SOC_FSL_UTILS
tristate
+config SND_SOC_FSL_RPMSG
+ tristate "Audio Base on RPMSG support"
+ help
+ Say Y if you want to add rpmsg audio support for the Freescale CPUs.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
config SND_SOC_IMX_PCM_DMA
tristate
select SND_SOC_GENERIC_DMAENGINE_PCM
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 8c5fa8a859c0..b63802f345cc 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -27,6 +27,7 @@ snd-soc-fsl-mqs-objs := fsl_mqs.o
snd-soc-fsl-easrc-objs := fsl_easrc.o
snd-soc-fsl-xcvr-objs := fsl_xcvr.o
snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o
+snd-soc-fsl-rpmsg-objs := fsl_rpmsg.o
obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
@@ -42,6 +43,7 @@ obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
obj-$(CONFIG_SND_SOC_FSL_XCVR) += snd-soc-fsl-xcvr.o
obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o
+obj-$(CONFIG_SND_SOC_FSL_RPMSG) += snd-soc-fsl-rpmsg.o
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
diff --git a/sound/soc/fsl/fsl_rpmsg.c b/sound/soc/fsl/fsl_rpmsg.c
new file mode 100644
index 000000000000..1082a3b3a18d
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2018-2021 NXP
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
+#include <linux/rpmsg.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_rpmsg.h"
+#include "imx-pcm.h"
+
+#define FSL_RPMSG_RATES (SNDRV_PCM_RATE_8000 | \
+ SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_48000)
+#define FSL_RPMSG_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static const unsigned int fsl_rpmsg_rates[] = {
+ 8000, 11025, 16000, 22050, 44100,
+ 32000, 48000, 96000, 88200, 176400, 192000,
+ 352800, 384000, 705600, 768000, 1411200, 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list fsl_rpmsg_rate_constraints = {
+ .count = ARRAY_SIZE(fsl_rpmsg_rates),
+ .list = fsl_rpmsg_rates,
+};
+
+static int fsl_rpmsg_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_rpmsg *rpmsg = snd_soc_dai_get_drvdata(dai);
+ struct clk *p = rpmsg->mclk, *pll = 0, *npll = 0;
+ unsigned int rate = params_rate(params);
+ int ret = 0;
+
+ /* Get current pll parent */
+ while (p && rpmsg->pll8k && rpmsg->pll11k) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, rpmsg->pll8k) ||
+ clk_is_match(pp, rpmsg->pll11k)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ /* Switch to another pll parent if needed. */
+ if (pll) {
+ npll = (do_div(rate, 8000) ? rpmsg->pll11k : rpmsg->pll8k);
+ if (!clk_is_match(pll, npll)) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(dai->dev, "failed to set parent %s: %d\n",
+ __clk_get_name(npll), ret);
+ }
+ }
+
+ if (!(rpmsg->mclk_streams & BIT(substream->stream))) {
+ ret = clk_prepare_enable(rpmsg->mclk);
+ if (ret) {
+ dev_err(dai->dev, "failed to enable mclk: %d\n", ret);
+ return ret;
+ }
+
+ rpmsg->mclk_streams |= BIT(substream->stream);
+ }
+
+ return ret;
+}
+
+static int fsl_rpmsg_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_rpmsg *rpmsg = snd_soc_dai_get_drvdata(dai);
+
+ if (rpmsg->mclk_streams & BIT(substream->stream)) {
+ clk_disable_unprepare(rpmsg->mclk);
+ rpmsg->mclk_streams &= ~BIT(substream->stream);
+ }
+
+ return 0;
+}
+
+static int fsl_rpmsg_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &fsl_rpmsg_rate_constraints);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops fsl_rpmsg_dai_ops = {
+ .startup = fsl_rpmsg_startup,
+ .hw_params = fsl_rpmsg_hw_params,
+ .hw_free = fsl_rpmsg_hw_free,
+};
+
+static struct snd_soc_dai_driver fsl_rpmsg_dai = {
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_FORMATS,
+ },
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_FORMATS,
+ },
+ .symmetric_rate = 1,
+ .symmetric_channels = 1,
+ .symmetric_sample_bits = 1,
+ .ops = &fsl_rpmsg_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_component = {
+ .name = "fsl-rpmsg",
+};
+
+static const struct of_device_id fsl_rpmsg_ids[] = {
+ { .compatible = "fsl,imx7ulp-rpmsg"},
+ { .compatible = "fsl,imx8mm-rpmsg"},
+ { .compatible = "fsl,imx8mn-rpmsg"},
+ { .compatible = "fsl,imx8mp-rpmsg"},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_rpmsg_ids);
+
+static int fsl_rpmsg_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_rpmsg *rpmsg;
+ int ret;
+
+ rpmsg = devm_kzalloc(&pdev->dev, sizeof(struct fsl_rpmsg), GFP_KERNEL);
+ if (!rpmsg)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(np, "fsl,audioindex", &rpmsg->audioindex);
+ if (ret)
+ rpmsg->audioindex = 0;
+
+ if (of_property_read_u32(np, "fsl,buffer-size", &rpmsg->buffer_size))
+ rpmsg->buffer_size = IMX_DEFAULT_DMABUF_SIZE;
+
+ if (of_property_read_bool(pdev->dev.of_node, "fsl,enable-lpa"))
+ rpmsg->enable_lpa = 1;
+
+ ret = of_property_read_u32(np, "fsl,version", &rpmsg->version);
+ if (ret)
+ rpmsg->version = API_VERSION_V2;
+
+ /*Get the optional clocks */
+ rpmsg->ipg = devm_clk_get(&pdev->dev, "ipg");
+ if (IS_ERR(rpmsg->ipg))
+ rpmsg->ipg = NULL;
+
+ rpmsg->mclk = devm_clk_get(&pdev->dev, "mclk");
+ if (IS_ERR(rpmsg->mclk))
+ rpmsg->mclk = NULL;
+
+ rpmsg->dma = devm_clk_get(&pdev->dev, "dma");
+ if (IS_ERR(rpmsg->dma))
+ rpmsg->dma = NULL;
+
+ rpmsg->pll8k = devm_clk_get(&pdev->dev, "pll8k");
+ if (IS_ERR(rpmsg->pll8k))
+ rpmsg->pll8k = NULL;
+
+ rpmsg->pll11k = devm_clk_get(&pdev->dev, "pll11k");
+ if (IS_ERR(rpmsg->pll11k))
+ rpmsg->pll11k = NULL;
+
+ platform_set_drvdata(pdev, rpmsg);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
+ &fsl_rpmsg_dai, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_rpmsg_runtime_resume(struct device *dev)
+{
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(rpmsg->ipg);
+ if (ret) {
+ dev_err(dev, "failed to enable ipg clock: %d\n", ret);
+ goto ipg_err;
+ }
+
+ ret = clk_prepare_enable(rpmsg->dma);
+ if (ret) {
+ dev_err(dev, "Failed to enable dma clock %d\n", ret);
+ goto dma_err;
+ }
+
+ return 0;
+
+dma_err:
+ clk_disable_unprepare(rpmsg->ipg);
+ipg_err:
+ return ret;
+}
+
+static int fsl_rpmsg_runtime_suspend(struct device *dev)
+{
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(rpmsg->dma);
+ clk_disable_unprepare(rpmsg->ipg);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fsl_rpmsg_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_rpmsg_runtime_suspend,
+ fsl_rpmsg_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_rpmsg_driver = {
+ .probe = fsl_rpmsg_probe,
+ .driver = {
+ .name = "fsl_rpmsg",
+ .pm = &fsl_rpmsg_pm_ops,
+ .of_match_table = fsl_rpmsg_ids,
+ },
+};
+module_platform_driver(fsl_rpmsg_driver);
+
+MODULE_DESCRIPTION("Freescale SoC Audio PRMSG CPU Interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:fsl_rpmsg");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_rpmsg.h b/sound/soc/fsl/fsl_rpmsg.h
new file mode 100644
index 000000000000..8d380d172b4b
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2017-2021 NXP
+ */
+
+#ifndef __FSL_RPMSG_H
+#define __FSL_RPMSG_H
+
+#define API_VERSION_V1 1
+#define API_VERSION_V2 2
+
+/*
+ * struct fsl_rpmsg - rpmsg private data
+ *
+ * @ipg: ipg clock for cpu dai (SAI)
+ * @mclk: master clock for cpu dai (SAI)
+ * @dma: clock for dma device
+ * @pll8k: parent clock for multiple of 8kHz frequency
+ * @pll11k: parent clock for multiple of 11kHz frequency
+ * @mclk_streams: Active streams that are using baudclk
+ * @force_lpa: force enable low power audio routine if condition satisfy
+ * @enable_lpa: enable low power audio routine according to dts setting
+ * @buffer_size: pre allocated dma buffer size
+ * @audioindex: audio instance index
+ * @version: rpmsg image version
+ */
+struct fsl_rpmsg {
+ struct clk *ipg;
+ struct clk *mclk;
+ struct clk *dma;
+ struct clk *pll8k;
+ struct clk *pll11k;
+ unsigned int mclk_streams;
+ int force_lpa;
+ int enable_lpa;
+ int buffer_size;
+ int audioindex;
+ int version;
+};
+#endif /* __FSL_RPMSG_H */
--
2.27.0
^ permalink raw reply related
* [PATCH v3 3/7] ASoC: dt-bindings: fsl_rpmsg: Add binding doc for rpmsg cpu dai driver
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
In-Reply-To: <1614221563-26822-1-git-send-email-shengjiu.wang@nxp.com>
fsl_rpmsg cpu dai driver is driver for rpmsg audio, which is mainly used
for getting the user's configuration from device tree and configure the
clocks which is used by Cortex-M core. So in this document define the
needed property.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
.../devicetree/bindings/sound/fsl,rpmsg.yaml | 94 +++++++++++++++++++
1 file changed, 94 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml
diff --git a/Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml b/Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml
new file mode 100644
index 000000000000..ab6d3b8d0d38
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,rpmsg.yaml
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/fsl,rpmsg.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP Audio RPMSG CPU DAI Controller
+
+maintainers:
+ - Shengjiu Wang <shengjiu.wang@nxp.com>
+
+description: |
+ fsl_rpmsg cpu dai driver is virtual driver for rpmsg audio, which doesn't
+ touch hardware. It is mainly used for getting the user's configuration
+ from device tree and configure the clocks which is used by Cortex-M core.
+ So in this document define the needed property.
+
+properties:
+ compatible:
+ enum:
+ - fsl,imx7ulp-rpmsg
+ - fsl,imx8mn-rpmsg
+ - fsl,imx8mm-rpmsg
+ - fsl,imx8mp-rpmsg
+
+ clocks:
+ items:
+ - description: Peripheral clock for register access
+ - description: Master clock
+ - description: DMA clock for DMA register access
+ - description: Parent clock for multiple of 8kHz sample rates
+ - description: Parent clock for multiple of 11kHz sample rates
+ minItems: 5
+
+ clock-names:
+ items:
+ - const: ipg
+ - const: mclk
+ - const: dma
+ - const: pll8k
+ - const: pll11k
+ minItems: 5
+
+ power-domains:
+ maxItems: 1
+
+ fsl,audioindex:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [0, 1]
+ default: 0
+ description: Instance index for sound card in
+ M core side, which share one rpmsg
+ channel.
+
+ fsl,version:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [1, 2]
+ default: 2
+ description: The version of M core image, which is
+ to make driver compatible with different image.
+
+ fsl,buffer-size:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: pre allocate dma buffer size
+
+ fsl,enable-lpa:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description: enable low power audio path.
+
+ fsl,codec-type:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [0, 1, 2]
+ default: 0
+ description: Sometimes the codec is registered by
+ driver not the device tree, this items
+ can be used to distinguish codecs.
+
+required:
+ - compatible
+ - fsl,audioindex
+ - fsl,version
+ - fsl,buffer-size
+
+additionalProperties: false
+
+examples:
+ - |
+ rpmsg_audio: rpmsg_audio {
+ compatible = "fsl,imx8mn-rpmsg";
+ fsl,audioindex = <0> ;
+ fsl,version = <2>;
+ fsl,buffer-size = <0x6000000>;
+ fsl,enable-lpa;
+ };
--
2.27.0
^ permalink raw reply related
* [PATCH v3 4/7] ASoC: imx-audio-rpmsg: Add rpmsg_driver for audio channel
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
In-Reply-To: <1614221563-26822-1-git-send-email-shengjiu.wang@nxp.com>
This driver is used to accept the message from rpmsg audio
channel, and if this driver is probed, it will help to register
the platform driver, the platform driver will use this
audio channel to send and receive message to and from Cortex-M
core.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
sound/soc/fsl/Kconfig | 4 +
sound/soc/fsl/Makefile | 1 +
sound/soc/fsl/imx-audio-rpmsg.c | 151 ++++++++++++++++++++++++++++++++
3 files changed, 156 insertions(+)
create mode 100644 sound/soc/fsl/imx-audio-rpmsg.c
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index a688c3c2efbc..84d9f0f1f75b 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -126,6 +126,10 @@ config SND_SOC_IMX_PCM_DMA
tristate
select SND_SOC_GENERIC_DMAENGINE_PCM
+config SND_SOC_IMX_AUDIO_RPMSG
+ tristate
+ depends on RPMSG
+
config SND_SOC_IMX_AUDMUX
tristate "Digital Audio Mux module support"
help
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index b63802f345cc..f08f3cd07ff5 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o
obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
+obj-$(CONFIG_SND_SOC_IMX_AUDIO_RPMSG) += imx-audio-rpmsg.o
# i.MX Machine Support
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c
new file mode 100644
index 000000000000..145edb1492b4
--- /dev/null
+++ b/sound/soc/fsl/imx-audio-rpmsg.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2017-2020 NXP
+
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include "imx-pcm-rpmsg.h"
+
+/*
+ * struct imx_audio_rpmsg: private data
+ *
+ * @rpmsg_pdev: pointer of platform device
+ */
+struct imx_audio_rpmsg {
+ struct platform_device *rpmsg_pdev;
+};
+
+static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct imx_audio_rpmsg *rpmsg = dev_get_drvdata(&rpdev->dev);
+ struct rpmsg_info *info = platform_get_drvdata(rpmsg->rpmsg_pdev);
+ struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data;
+ struct rpmsg_msg *msg;
+ unsigned long flags;
+
+ dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n",
+ src, r_msg->header.cmd, r_msg->param.resp);
+
+ switch (r_msg->header.type) {
+ case MSG_TYPE_C:
+ /* TYPE C is notification from M core */
+ switch (r_msg->header.cmd) {
+ case TX_PERIOD_DONE:
+ spin_lock_irqsave(&info->lock[TX], flags);
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+
+ /*
+ * Low power mode: get the buffer pointer from
+ * receive msg.
+ */
+ if (r_msg->header.major == 1 &&
+ r_msg->header.minor == 2)
+ msg->r_msg.param.buffer_tail =
+ r_msg->param.buffer_tail;
+ else
+ msg->r_msg.param.buffer_tail++;
+
+ msg->r_msg.param.buffer_tail %= info->num_period[TX];
+ spin_unlock_irqrestore(&info->lock[TX], flags);
+ info->callback[TX](info->callback_param[TX]);
+ break;
+ case RX_PERIOD_DONE:
+ spin_lock_irqsave(&info->lock[RX], flags);
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+
+ if (r_msg->header.major == 1 &&
+ r_msg->header.minor == 2)
+ msg->r_msg.param.buffer_tail =
+ r_msg->param.buffer_tail;
+ else
+ msg->r_msg.param.buffer_tail++;
+
+ msg->r_msg.param.buffer_tail %= info->num_period[1];
+ spin_unlock_irqrestore(&info->lock[RX], flags);
+ info->callback[RX](info->callback_param[RX]);
+ break;
+ default:
+ dev_warn(&rpdev->dev, "unknown msg command\n");
+ break;
+ }
+ break;
+ case MSG_TYPE_B:
+ /* TYPE B is response msg */
+ memcpy(&info->r_msg, r_msg, sizeof(struct rpmsg_r_msg));
+ complete(&info->cmd_complete);
+ break;
+ default:
+ dev_warn(&rpdev->dev, "unknown msg type\n");
+ break;
+ }
+
+ return 0;
+}
+
+static int imx_audio_rpmsg_probe(struct rpmsg_device *rpdev)
+{
+ struct imx_audio_rpmsg *data;
+ int ret = 0;
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
+ rpdev->src, rpdev->dst);
+
+ data = devm_kzalloc(&rpdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ dev_set_drvdata(&rpdev->dev, data);
+
+ /* Register platform driver for rpmsg routine */
+ data->rpmsg_pdev = platform_device_register_data(&rpdev->dev,
+ IMX_PCM_DRV_NAME,
+ PLATFORM_DEVID_NONE,
+ NULL, 0);
+ if (IS_ERR(data->rpmsg_pdev)) {
+ dev_err(&rpdev->dev, "failed to register rpmsg platform.\n");
+ ret = PTR_ERR(data->rpmsg_pdev);
+ }
+
+ return ret;
+}
+
+static void imx_audio_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+ struct imx_audio_rpmsg *data = dev_get_drvdata(&rpdev->dev);
+
+ if (data->rpmsg_pdev)
+ platform_device_unregister(data->rpmsg_pdev);
+
+ dev_info(&rpdev->dev, "audio rpmsg driver is removed\n");
+}
+
+static struct rpmsg_device_id imx_audio_rpmsg_id_table[] = {
+ { .name = "rpmsg-audio-channel" },
+ { },
+};
+
+static struct rpmsg_driver imx_audio_rpmsg_driver = {
+ .drv.name = "imx_audio_rpmsg",
+ .drv.owner = THIS_MODULE,
+ .id_table = imx_audio_rpmsg_id_table,
+ .probe = imx_audio_rpmsg_probe,
+ .callback = imx_audio_rpmsg_cb,
+ .remove = imx_audio_rpmsg_remove,
+};
+
+static int __init imx_audio_rpmsg_init(void)
+{
+ return register_rpmsg_driver(&imx_audio_rpmsg_driver);
+}
+
+static void __exit imx_audio_rpmsg_exit(void)
+{
+ unregister_rpmsg_driver(&imx_audio_rpmsg_driver);
+}
+module_init(imx_audio_rpmsg_init);
+module_exit(imx_audio_rpmsg_exit);
+
+MODULE_DESCRIPTION("Freescale SoC Audio RPMSG interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:imx_audio_rpmsg");
+MODULE_LICENSE("GPL v2");
--
2.27.0
^ permalink raw reply related
* [PATCH v3 6/7] ASoC: imx-rpmsg: Add machine driver for audio base on rpmsg
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
In-Reply-To: <1614221563-26822-1-git-send-email-shengjiu.wang@nxp.com>
The platform device is not registered by device tree or
cpu dai driver, it is registered by the rpmsg channel,
So add a dedicated machine driver to handle this case.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
sound/soc/fsl/Kconfig | 12 ++++
sound/soc/fsl/Makefile | 2 +
sound/soc/fsl/imx-rpmsg.c | 148 ++++++++++++++++++++++++++++++++++++++
3 files changed, 162 insertions(+)
create mode 100644 sound/soc/fsl/imx-rpmsg.c
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 749c44fc0759..3557866d3fa2 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -334,6 +334,18 @@ config SND_SOC_IMX_HDMI
Say Y if you want to add support for SoC audio on an i.MX board with
IMX HDMI.
+config SND_SOC_IMX_RPMSG
+ tristate "SoC Audio support for i.MX boards with rpmsg"
+ depends on RPMSG
+ select SND_SOC_IMX_PCM_RPMSG
+ select SND_SOC_IMX_AUDIO_RPMSG
+ select SND_SOC_FSL_RPMSG
+ help
+ SoC Audio support for i.MX boards with rpmsg.
+ There should be rpmsg devices defined in other core (M core)
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a rpmsg devices.
+
endif # SND_IMX_SOC
endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index ce4f4324c3a2..f146ce464acd 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -70,6 +70,7 @@ snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
snd-soc-imx-spdif-objs := imx-spdif.o
snd-soc-imx-audmix-objs := imx-audmix.o
snd-soc-imx-hdmi-objs := imx-hdmi.o
+snd-soc-imx-rpmsg-objs := imx-rpmsg.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
@@ -77,3 +78,4 @@ obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o
obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
+obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o
diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c
new file mode 100644
index 000000000000..a87dcbce4f36
--- /dev/null
+++ b/sound/soc/fsl/imx-rpmsg.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2017-2020 NXP
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include "imx-pcm-rpmsg.h"
+
+struct imx_rpmsg {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+};
+
+static int imx_rpmsg_probe(struct platform_device *pdev)
+{
+ struct snd_soc_dai_link_component *dlc;
+ struct platform_device *cpu_pdev;
+ struct of_phandle_args args;
+ struct device_node *cpu_np;
+ struct imx_rpmsg *data;
+ int ret;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find rpmsg platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = of_reserved_mem_device_init_by_idx(&pdev->dev, pdev->dev.of_node, 0);
+ if (ret)
+ dev_warn(&pdev->dev, "no reserved DMA memory\n");
+
+ data->dai.cpus = &dlc[0];
+ data->dai.num_cpus = 1;
+ data->dai.platforms = &dlc[1];
+ data->dai.num_platforms = 1;
+ data->dai.codecs = &dlc[2];
+ data->dai.num_codecs = 1;
+
+ data->dai.name = "rpmsg hifi";
+ data->dai.stream_name = "rpmsg hifi";
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* Optional codec node */
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "audio-codec", 0, 0, &args);
+ if (ret) {
+ data->dai.codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai.codecs->name = "snd-soc-dummy";
+ } else {
+ data->dai.codecs->of_node = args.np;
+ ret = snd_soc_get_dai_name(&args, &data->dai.codecs->dai_name);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to get codec_dai_name\n");
+ goto fail;
+ }
+ }
+
+ data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platforms->name = IMX_PCM_DRV_NAME;
+ data->dai.playback_only = true;
+ data->dai.capture_only = true;
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+
+ if (of_property_read_bool(pdev->dev.of_node, "rpmsg-out"))
+ data->dai.capture_only = false;
+
+ if (of_property_read_bool(pdev->dev.of_node, "rpmsg-in"))
+ data->dai.playback_only = false;
+
+ if (data->dai.playback_only && data->dai.capture_only) {
+ dev_err(&pdev->dev, "no enabled rpmsg DAI link\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ return ret;
+}
+
+static const struct of_device_id imx_rpmsg_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-rpmsg", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_rpmsg_dt_ids);
+
+static struct platform_driver imx_rpmsg_driver = {
+ .driver = {
+ .name = "imx-audio-rpmsg",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_rpmsg_dt_ids,
+ },
+ .probe = imx_rpmsg_probe,
+};
+module_platform_driver(imx_rpmsg_driver);
+
+MODULE_DESCRIPTION("Freescale SoC Audio RPMSG Machine Driver");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:imx-rpmsg");
+MODULE_LICENSE("GPL v2");
--
2.27.0
^ permalink raw reply related
* [PATCH v3 5/7] ASoC: imx-pcm-rpmsg: Add platform driver for audio base on rpmsg
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
In-Reply-To: <1614221563-26822-1-git-send-email-shengjiu.wang@nxp.com>
platform driver base on rpmsg is the interface for sending and
receiving rpmsg to and from M core. It will tell the Cortex-M core
sound format/rate/channel, where is the data buffer, where is
the period size, when to start, when to stop and when suspend
or resume happen, each this behavior there is defined rpmsg
command.
Especially we designed the low power audio case, that is to
allocate a large buffer and fill the data, then Cortex-A core can go
to sleep mode, Cortex-M core continue to play the sound, when the
buffer is consumed, Cortex-M core will trigger the Cortex-A core to
wakeup.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
sound/soc/fsl/Kconfig | 5 +
sound/soc/fsl/Makefile | 1 +
sound/soc/fsl/imx-pcm-rpmsg.c | 919 ++++++++++++++++++++++++++++++++++
sound/soc/fsl/imx-pcm-rpmsg.h | 512 +++++++++++++++++++
4 files changed, 1437 insertions(+)
create mode 100644 sound/soc/fsl/imx-pcm-rpmsg.c
create mode 100644 sound/soc/fsl/imx-pcm-rpmsg.h
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 84d9f0f1f75b..749c44fc0759 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -130,6 +130,11 @@ config SND_SOC_IMX_AUDIO_RPMSG
tristate
depends on RPMSG
+config SND_SOC_IMX_PCM_RPMSG
+ tristate
+ depends on SND_SOC_IMX_AUDIO_RPMSG
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+
config SND_SOC_IMX_AUDMUX
tristate "Digital Audio Mux module support"
help
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index f08f3cd07ff5..ce4f4324c3a2 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o
obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
obj-$(CONFIG_SND_SOC_IMX_AUDIO_RPMSG) += imx-audio-rpmsg.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o
# i.MX Machine Support
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c
new file mode 100644
index 000000000000..f05d5d489560
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.c
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2017-2021 NXP
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/rpmsg.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "imx-pcm.h"
+#include "fsl_rpmsg.h"
+#include "imx-pcm-rpmsg.h"
+
+static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE,
+ .period_bytes_min = 512,
+ .period_bytes_max = 65536,
+ .periods_min = 2,
+ .periods_max = 6000,
+ .fifo_size = 0,
+};
+
+static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg,
+ struct rpmsg_info *info)
+{
+ struct rpmsg_device *rpdev = info->rpdev;
+ int ret = 0;
+
+ mutex_lock(&info->msg_lock);
+ if (!rpdev) {
+ dev_err(info->dev, "rpmsg channel not ready\n");
+ mutex_unlock(&info->msg_lock);
+ return -EINVAL;
+ }
+
+ dev_dbg(&rpdev->dev, "send cmd %d\n", msg->s_msg.header.cmd);
+
+ if (!(msg->s_msg.header.type == MSG_TYPE_C))
+ reinit_completion(&info->cmd_complete);
+
+ ret = rpmsg_send(rpdev->ept, (void *)&msg->s_msg,
+ sizeof(struct rpmsg_s_msg));
+ if (ret) {
+ dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+ mutex_unlock(&info->msg_lock);
+ return ret;
+ }
+
+ /* No receive msg for TYPE_C command */
+ if (msg->s_msg.header.type == MSG_TYPE_C) {
+ mutex_unlock(&info->msg_lock);
+ return 0;
+ }
+
+ /* wait response from rpmsg */
+ ret = wait_for_completion_timeout(&info->cmd_complete,
+ msecs_to_jiffies(RPMSG_TIMEOUT));
+ if (!ret) {
+ dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n",
+ msg->s_msg.header.cmd);
+ mutex_unlock(&info->msg_lock);
+ return -ETIMEDOUT;
+ }
+
+ memcpy(&msg->r_msg, &info->r_msg, sizeof(struct rpmsg_r_msg));
+ memcpy(&info->msg[msg->r_msg.header.cmd].r_msg,
+ &msg->r_msg, sizeof(struct rpmsg_r_msg));
+
+ /*
+ * Reset the buffer pointer to be zero, actully we have
+ * set the buffer pointer to be zero in imx_rpmsg_terminate_all
+ * But if there is timer task queued in queue, after it is
+ * executed the buffer pointer will be changed, so need to
+ * reset it again with TERMINATE command.
+ */
+ switch (msg->s_msg.header.cmd) {
+ case TX_TERMINATE:
+ info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
+ break;
+ case RX_TERMINATE:
+ info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
+ break;
+ default:
+ break;
+ }
+
+ dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd,
+ info->r_msg.param.resp);
+
+ mutex_unlock(&info->msg_lock);
+
+ return 0;
+}
+
+static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream,
+ struct rpmsg_msg *msg,
+ struct rpmsg_info *info)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ /*
+ * Queue the work to workqueue.
+ * If the queue is full, drop the message.
+ */
+ spin_lock_irqsave(&info->wq_lock, flags);
+ if (info->work_write_index != info->work_read_index) {
+ int index = info->work_write_index;
+
+ memcpy(&info->work_list[index].msg, msg,
+ sizeof(struct rpmsg_s_msg));
+
+ queue_work(info->rpmsg_wq, &info->work_list[index].work);
+ info->work_write_index++;
+ info->work_write_index %= WORK_MAX_NUM;
+ } else {
+ info->msg_drop_count[substream->stream]++;
+ ret = -EPIPE;
+ }
+ spin_unlock_irqrestore(&info->wq_lock, flags);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct rpmsg_msg *msg;
+ int ret = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_HW_PARAM];
+ msg->s_msg.header.cmd = TX_HW_PARAM;
+ } else {
+ msg = &info->msg[RX_HW_PARAM];
+ msg->s_msg.header.cmd = RX_HW_PARAM;
+ }
+
+ msg->s_msg.param.rate = params_rate(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ msg->s_msg.param.format = RPMSG_S16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ msg->s_msg.param.format = RPMSG_S24_LE;
+ break;
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ msg->s_msg.param.format = SNDRV_PCM_FORMAT_DSD_U16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ msg->s_msg.param.format = SNDRV_PCM_FORMAT_DSD_U32_LE;
+ break;
+ default:
+ msg->s_msg.param.format = RPMSG_S32_LE;
+ break;
+ }
+
+ switch (params_channels(params)) {
+ case 1:
+ msg->s_msg.param.channels = RPMSG_CH_LEFT;
+ break;
+ case 2:
+ msg->s_msg.param.channels = RPMSG_CH_STEREO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ info->send_message(msg, info);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ unsigned int pos = 0;
+ int buffer_tail = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ else
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+
+ buffer_tail = msg->r_msg.param.buffer_tail;
+ pos = buffer_tail * snd_pcm_lib_period_bytes(substream);
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static void imx_rpmsg_timer_callback(struct timer_list *t)
+{
+ struct stream_timer *stream_timer =
+ from_timer(stream_timer, t, timer);
+ struct snd_pcm_substream *substream = stream_timer->substream;
+ struct rpmsg_info *info = stream_timer->info;
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = TX_PERIOD_DONE;
+ } else {
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = RX_PERIOD_DONE;
+ }
+
+ imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int ret = 0;
+ int cmd;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_OPEN];
+ msg->s_msg.header.cmd = TX_OPEN;
+
+ /* reinitialize buffer counter*/
+ cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
+
+ } else {
+ msg = &info->msg[RX_OPEN];
+ msg->s_msg.header.cmd = RX_OPEN;
+
+ /* reinitialize buffer counter*/
+ cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
+ }
+
+ info->send_message(msg, info);
+
+ imx_rpmsg_pcm_hardware.period_bytes_max =
+ imx_rpmsg_pcm_hardware.buffer_bytes_max / 2;
+
+ snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_pcm_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ info->msg_drop_count[substream->stream] = 0;
+
+ /* Create timer*/
+ info->stream_timer[substream->stream].info = info;
+ info->stream_timer[substream->stream].substream = substream;
+ timer_setup(&info->stream_timer[substream->stream].timer,
+ imx_rpmsg_timer_callback, 0);
+ return ret;
+}
+
+static int imx_rpmsg_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int ret = 0;
+
+ /* Flush work in workqueue to make TX_CLOSE is the last message */
+ flush_workqueue(info->rpmsg_wq);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_CLOSE];
+ msg->s_msg.header.cmd = TX_CLOSE;
+ } else {
+ msg = &info->msg[RX_CLOSE];
+ msg->s_msg.header.cmd = RX_CLOSE;
+ }
+
+ info->send_message(msg, info);
+
+ del_timer(&info->stream_timer[substream->stream].timer);
+
+ rtd->dai_link->ignore_suspend = 0;
+
+ if (info->msg_drop_count[substream->stream])
+ dev_warn(rtd->dev, "Msg is dropped!, number is %d\n",
+ info->msg_drop_count[substream->stream]);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+
+ /*
+ * NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts
+ * four conditions to determine the lpa is enabled.
+ */
+ if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) &&
+ rpmsg->version == API_VERSION_V2 &&
+ rpmsg->enable_lpa) {
+ /*
+ * Ignore suspend operation in low power mode
+ * M core will continue playback music on A core suspend.
+ */
+ rtd->dai_link->ignore_suspend = 1;
+ rpmsg->force_lpa = 1;
+ } else {
+ rpmsg->force_lpa = 0;
+ }
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_mmap(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_wc(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static void imx_rpmsg_pcm_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int imx_rpmsg_prepare_and_submit(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_BUFFER];
+ msg->s_msg.header.cmd = TX_BUFFER;
+ } else {
+ msg = &info->msg[RX_BUFFER];
+ msg->s_msg.header.cmd = RX_BUFFER;
+ }
+
+ /* Send buffer address and buffer size */
+ msg->s_msg.param.buffer_addr = substream->runtime->dma_addr;
+ msg->s_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ msg->s_msg.param.period_size = snd_pcm_lib_period_bytes(substream);
+ msg->s_msg.param.buffer_tail = 0;
+
+ info->num_period[substream->stream] = msg->s_msg.param.buffer_size /
+ msg->s_msg.param.period_size;
+
+ info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
+ info->callback_param[substream->stream] = substream;
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_async_issue_pending(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_START];
+ msg->s_msg.header.cmd = TX_START;
+ } else {
+ msg = &info->msg[RX_START];
+ msg->s_msg.header.cmd = RX_START;
+ }
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_restart(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_RESTART];
+ msg->s_msg.header.cmd = TX_RESTART;
+ } else {
+ msg = &info->msg[RX_RESTART];
+ msg->s_msg.header.cmd = RX_RESTART;
+ }
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_pause(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_PAUSE];
+ msg->s_msg.header.cmd = TX_PAUSE;
+ } else {
+ msg = &info->msg[RX_PAUSE];
+ msg->s_msg.header.cmd = RX_PAUSE;
+ }
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_terminate_all(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int cmd;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_TERMINATE];
+ msg->s_msg.header.cmd = TX_TERMINATE;
+ /* Clear buffer count*/
+ cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
+ } else {
+ msg = &info->msg[RX_TERMINATE];
+ msg->s_msg.header.cmd = RX_TERMINATE;
+ /* Clear buffer count*/
+ cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM;
+ info->msg[cmd].s_msg.param.buffer_tail = 0;
+ info->msg[cmd].r_msg.param.buffer_tail = 0;
+ info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
+ }
+
+ del_timer(&info->stream_timer[substream->stream].timer);
+
+ return imx_rpmsg_insert_workqueue(substream, msg, info);
+}
+
+static int imx_rpmsg_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = imx_rpmsg_prepare_and_submit(component, substream);
+ if (ret)
+ return ret;
+ ret = imx_rpmsg_async_issue_pending(component, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (rpmsg->force_lpa)
+ break;
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = imx_rpmsg_restart(component, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!rpmsg->force_lpa) {
+ if (runtime->info & SNDRV_PCM_INFO_PAUSE)
+ ret = imx_rpmsg_pause(component, substream);
+ else
+ ret = imx_rpmsg_terminate_all(component, substream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = imx_rpmsg_pause(component, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = imx_rpmsg_terminate_all(component, substream);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * imx_rpmsg_pcm_ack
+ *
+ * Send the period index to M core through rpmsg, but not send
+ * all the period index to M core, reduce some unnessesary msg
+ * to reduce the pressure of rpmsg bandwidth.
+ */
+static int imx_rpmsg_pcm_ack(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ snd_pcm_uframes_t period_size = runtime->period_size;
+ snd_pcm_sframes_t avail;
+ struct timer_list *timer;
+ struct rpmsg_msg *msg;
+ unsigned long flags;
+ int buffer_tail = 0;
+ int written_num = 0;
+
+ if (!rpmsg->force_lpa)
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = TX_PERIOD_DONE;
+ } else {
+ msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
+ msg->s_msg.header.cmd = RX_PERIOD_DONE;
+ }
+
+ msg->s_msg.header.type = MSG_TYPE_C;
+
+ buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) %
+ snd_pcm_lib_buffer_bytes(substream));
+ buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream);
+
+ /* There is update for period index */
+ if (buffer_tail != msg->s_msg.param.buffer_tail) {
+ written_num = buffer_tail - msg->s_msg.param.buffer_tail;
+ if (written_num < 0)
+ written_num += runtime->periods;
+
+ msg->s_msg.param.buffer_tail = buffer_tail;
+
+ /* The notification message is updated to latest */
+ spin_lock_irqsave(&info->lock[substream->stream], flags);
+ memcpy(&info->notify[substream->stream], msg,
+ sizeof(struct rpmsg_s_msg));
+ info->notify_updated[substream->stream] = true;
+ spin_unlock_irqrestore(&info->lock[substream->stream], flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ avail = snd_pcm_playback_hw_avail(runtime);
+ else
+ avail = snd_pcm_capture_hw_avail(runtime);
+
+ timer = &info->stream_timer[substream->stream].timer;
+ /*
+ * If the data in the buffer is less than one period before
+ * this fill, which means the data may not enough on M
+ * core side, we need to send message immediately to let
+ * M core know the pointer is updated.
+ * if there is more than one period data in the buffer before
+ * this fill, which means the data is enough on M core side,
+ * we can delay one period (using timer) to send the message
+ * for reduce the message number in workqueue, because the
+ * pointer may be updated by ack function later, we can
+ * send latest pointer to M core side.
+ */
+ if ((avail - written_num * period_size) <= period_size) {
+ imx_rpmsg_insert_workqueue(substream, msg, info);
+ } else if (rpmsg->force_lpa && !timer_pending(timer)) {
+ int time_msec;
+
+ time_msec = (int)(runtime->period_size * 1000 / runtime->rate);
+ mod_timer(timer, jiffies + msecs_to_jiffies(time_msec));
+ }
+ }
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+ int stream, int size)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_wc(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void imx_rpmsg_pcm_free_dma_buffers(struct snd_soc_component *component,
+ struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = SNDRV_PCM_STREAM_PLAYBACK;
+ stream < SNDRV_PCM_STREAM_LAST; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_wc(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static int imx_rpmsg_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ rpmsg->buffer_size);
+ if (ret)
+ goto out;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ rpmsg->buffer_size);
+ if (ret)
+ goto out;
+ }
+
+ imx_rpmsg_pcm_hardware.buffer_bytes_max = rpmsg->buffer_size;
+out:
+ /* free preallocated buffers in case of error */
+ if (ret)
+ imx_rpmsg_pcm_free_dma_buffers(component, pcm);
+
+ return ret;
+}
+
+static const struct snd_soc_component_driver imx_rpmsg_soc_component = {
+ .name = IMX_PCM_DRV_NAME,
+ .pcm_construct = imx_rpmsg_pcm_new,
+ .pcm_destruct = imx_rpmsg_pcm_free_dma_buffers,
+ .open = imx_rpmsg_pcm_open,
+ .close = imx_rpmsg_pcm_close,
+ .hw_params = imx_rpmsg_pcm_hw_params,
+ .hw_free = imx_rpmsg_pcm_hw_free,
+ .trigger = imx_rpmsg_pcm_trigger,
+ .pointer = imx_rpmsg_pcm_pointer,
+ .mmap = imx_rpmsg_pcm_mmap,
+ .ack = imx_rpmsg_pcm_ack,
+ .prepare = imx_rpmsg_pcm_prepare,
+};
+
+static void imx_rpmsg_pcm_work(struct work_struct *work)
+{
+ struct work_of_rpmsg *work_of_rpmsg;
+ bool is_notification = false;
+ struct rpmsg_info *info;
+ struct rpmsg_msg msg;
+ unsigned long flags;
+
+ work_of_rpmsg = container_of(work, struct work_of_rpmsg, work);
+ info = work_of_rpmsg->info;
+
+ /*
+ * Every work in the work queue, first we check if there
+ * is update for period is filled, because there may be not
+ * enough data in M core side, need to let M core know
+ * data is updated immediately.
+ */
+ spin_lock_irqsave(&info->lock[TX], flags);
+ if (info->notify_updated[TX]) {
+ memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg));
+ info->notify_updated[TX] = false;
+ spin_unlock_irqrestore(&info->lock[TX], flags);
+ info->send_message(&msg, info);
+ } else {
+ spin_unlock_irqrestore(&info->lock[TX], flags);
+ }
+
+ spin_lock_irqsave(&info->lock[RX], flags);
+ if (info->notify_updated[RX]) {
+ memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg));
+ info->notify_updated[RX] = false;
+ spin_unlock_irqrestore(&info->lock[RX], flags);
+ info->send_message(&msg, info);
+ } else {
+ spin_unlock_irqrestore(&info->lock[RX], flags);
+ }
+
+ /* Skip the notification message for it has been processed above */
+ if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C &&
+ (work_of_rpmsg->msg.s_msg.header.cmd == TX_PERIOD_DONE ||
+ work_of_rpmsg->msg.s_msg.header.cmd == RX_PERIOD_DONE))
+ is_notification = true;
+
+ if (!is_notification)
+ info->send_message(&work_of_rpmsg->msg, info);
+
+ /* update read index */
+ spin_lock_irqsave(&info->wq_lock, flags);
+ info->work_read_index++;
+ info->work_read_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&info->wq_lock, flags);
+}
+
+static int imx_rpmsg_pcm_probe(struct platform_device *pdev)
+{
+ struct snd_soc_component *component;
+ struct rpmsg_info *info;
+ int ret, i;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, info);
+
+ info->rpdev = container_of(pdev->dev.parent, struct rpmsg_device, dev);
+ info->dev = &pdev->dev;
+ /* Setup work queue */
+ info->rpmsg_wq = alloc_ordered_workqueue("rpmsg_audio",
+ WQ_HIGHPRI |
+ WQ_UNBOUND |
+ WQ_FREEZABLE);
+ if (!info->rpmsg_wq) {
+ dev_err(&pdev->dev, "workqueue create failed\n");
+ return -ENOMEM;
+ }
+
+ /* Write index initialize 1, make it differ with the read index */
+ info->work_write_index = 1;
+ info->send_message = imx_rpmsg_pcm_send_message;
+
+ for (i = 0; i < WORK_MAX_NUM; i++) {
+ INIT_WORK(&info->work_list[i].work, imx_rpmsg_pcm_work);
+ info->work_list[i].info = info;
+ }
+
+ /* Initialize msg */
+ for (i = 0; i < MSG_MAX_NUM; i++) {
+ info->msg[i].s_msg.header.cate = IMX_RPMSG_AUDIO;
+ info->msg[i].s_msg.header.major = IMX_RMPSG_MAJOR;
+ info->msg[i].s_msg.header.minor = IMX_RMPSG_MINOR;
+ info->msg[i].s_msg.header.type = MSG_TYPE_A;
+ info->msg[i].s_msg.param.audioindex = 0;
+ }
+
+ init_completion(&info->cmd_complete);
+ mutex_init(&info->msg_lock);
+ spin_lock_init(&info->lock[TX]);
+ spin_lock_init(&info->lock[RX]);
+ spin_lock_init(&info->wq_lock);
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &imx_rpmsg_soc_component,
+ NULL, 0);
+ if (ret)
+ goto fail;
+
+ component = snd_soc_lookup_component(&pdev->dev, IMX_PCM_DRV_NAME);
+ if (!component) {
+ ret = -EINVAL;
+ goto fail;
+ }
+#ifdef CONFIG_DEBUG_FS
+ component->debugfs_prefix = "rpmsg";
+#endif
+
+ return 0;
+
+fail:
+ if (info->rpmsg_wq)
+ destroy_workqueue(info->rpmsg_wq);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_remove(struct platform_device *pdev)
+{
+ struct rpmsg_info *info = platform_get_drvdata(pdev);
+
+ if (info->rpmsg_wq)
+ destroy_workqueue(info->rpmsg_wq);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_rpmsg_pcm_runtime_resume(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+
+ cpu_latency_qos_add_request(&info->pm_qos_req, 0);
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+
+ cpu_latency_qos_remove_request(&info->pm_qos_req);
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_rpmsg_pcm_suspend(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+ struct rpmsg_msg *rpmsg_tx;
+ struct rpmsg_msg *rpmsg_rx;
+
+ rpmsg_tx = &info->msg[TX_SUSPEND];
+ rpmsg_rx = &info->msg[RX_SUSPEND];
+
+ rpmsg_tx->s_msg.header.cmd = TX_SUSPEND;
+ info->send_message(rpmsg_tx, info);
+
+ rpmsg_rx->s_msg.header.cmd = RX_SUSPEND;
+ info->send_message(rpmsg_rx, info);
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_resume(struct device *dev)
+{
+ struct rpmsg_info *info = dev_get_drvdata(dev);
+ struct rpmsg_msg *rpmsg_tx;
+ struct rpmsg_msg *rpmsg_rx;
+
+ rpmsg_tx = &info->msg[TX_RESUME];
+ rpmsg_rx = &info->msg[RX_RESUME];
+
+ rpmsg_tx->s_msg.header.cmd = TX_RESUME;
+ info->send_message(rpmsg_tx, info);
+
+ rpmsg_rx->s_msg.header.cmd = RX_RESUME;
+ info->send_message(rpmsg_rx, info);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = {
+ SET_RUNTIME_PM_OPS(imx_rpmsg_pcm_runtime_suspend,
+ imx_rpmsg_pcm_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(imx_rpmsg_pcm_suspend,
+ imx_rpmsg_pcm_resume)
+};
+
+static struct platform_driver imx_pcm_rpmsg_driver = {
+ .probe = imx_rpmsg_pcm_probe,
+ .remove = imx_rpmsg_pcm_remove,
+ .driver = {
+ .name = IMX_PCM_DRV_NAME,
+ .pm = &imx_rpmsg_pcm_pm_ops,
+ },
+};
+module_platform_driver(imx_pcm_rpmsg_driver);
+
+MODULE_DESCRIPTION("Freescale SoC Audio RPMSG PCM interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_ALIAS("platform:" IMX_PCM_DRV_NAME);
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-pcm-rpmsg.h b/sound/soc/fsl/imx-pcm-rpmsg.h
new file mode 100644
index 000000000000..308d153920a3
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.h
@@ -0,0 +1,512 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2017-2021 NXP
+ *
+ ******************************************************************************
+ * Communication stack of audio with rpmsg
+ ******************************************************************************
+ * Packet structure:
+ * A SRTM message consists of a 10 bytes header followed by 0~N bytes of data
+ *
+ * +---------------+-------------------------------+
+ * | | Content |
+ * +---------------+-------------------------------+
+ * | Byte Offset | 7 6 5 4 3 2 1 0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 0 | Category |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 1 ~ 2 | Version |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 3 | Type |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 4 | Command |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 5 | Reserved0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 6 | Reserved1 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 7 | Reserved2 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 8 | Reserved3 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 9 | Reserved4 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 10 | DATA 0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * : : : : : : : : : : : : :
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | N + 10 - 1 | DATA N-1 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ *
+ * +----------+------------+------------------------------------------------+
+ * | Field | Byte | |
+ * +----------+------------+------------------------------------------------+
+ * | Category | 0 | The destination category. |
+ * +----------+------------+------------------------------------------------+
+ * | Version | 1 ~ 2 | The category version of the sender of the |
+ * | | | packet. |
+ * | | | The first byte represent the major version of |
+ * | | | the packet.The second byte represent the minor |
+ * | | | version of the packet. |
+ * +----------+------------+------------------------------------------------+
+ * | Type | 3 | The message type of current message packet. |
+ * +----------+------------+------------------------------------------------+
+ * | Command | 4 | The command byte sent to remote processor/SoC. |
+ * +----------+------------+------------------------------------------------+
+ * | Reserved | 5 ~ 9 | Reserved field for future extension. |
+ * +----------+------------+------------------------------------------------+
+ * | Data | N | The data payload of the message packet. |
+ * +----------+------------+------------------------------------------------+
+ *
+ * Audio control:
+ * SRTM Audio Control Category Request Command Table:
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x00 | Data[0]: Audio Device Index | Open a TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x01 | Data[0]: Audio Device Index | Start a TX Instance. |
+ * | | | | | Same as above command | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause a TX Instance. |
+ * | | | | | Same as above command | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Stop a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for |
+ * | | | | | Data[1]: format | a TX Instance. |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set TX Buffer. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x08 | Data[0]: Audio Device Index | Suspend a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume a TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0A | Data[0]: Audio Device Index | Open a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Stop a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for |
+ * | | | | | Data[1]: format | a RX Instance. |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set RX Buffer. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x12 | Data[0]: Audio Device Index | Suspend a RX Instance.|
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume a RX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x14 | Data[0]: Audio Device Index | Set register value |
+ * | | | | | Data[1-6]: reserved | to codec |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-14]: value | |
+ * | | | | | Data[15-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value |
+ * | | | | | Data[1-6]: reserved | from codec |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * Note 1: See <List of Sample Format> for available value of
+ * Sample Format;
+ * Note 2: See <List of Audio Channels> for available value of Channels;
+ * Note 3: Sample Rate of Set Parameters for an Audio TX Instance
+ * Command and Set Parameters for an Audio RX Instance Command is
+ * in little-endian format.
+ *
+ * SRTM Audio Control Category Response Command Table:
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x00 | Data[0]: Audio Device Index | Reply for Open |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Stop |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Param |
+ * | | | | | Data[1]: Return code | for a TX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set |
+ * | | | | | Data[1]: Return code | TX Buffer |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause |
+ * | | | | | Data[1]: Return code | a TX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Stop |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Param |
+ * | | | | | Data[1]: Return code | for a RX Instance. |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set |
+ * | | | | | Data[1]: Return code | RX Buffer |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Suspend |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume |
+ * | | | | | Data[1]: Return code | a RX Instance |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec |
+ * | | | | | Data[1]: Return code | register value |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec |
+ * | | | | | Data[1]: Return code | register value |
+ * | | | | | Data[2-6]: reserved | |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-14]: value | |
+ * | | | | | Data[15-22]: reserved | |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ *
+ * SRTM Audio Control Category Notification Command Table:
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x02 | 0x00 | Data[0]: Audio Device Index | Notify one TX period |
+ * | | | | | | is finished |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ * | 0x03 | 0x0100 | 0x02 | 0x01 | Data[0]: Audio Device Index | Notify one RX period |
+ * | | | | | | is finished |
+ * +----------+---------+------+---------+-------------------------------+-----------------------+
+ *
+ * List of Sample Format:
+ * +------------------+-----------------------+
+ * | Sample Format | Description |
+ * +------------------+-----------------------+
+ * | 0x0 | S16_LE |
+ * +------------------+-----------------------+
+ * | 0x1 | S24_LE |
+ * +------------------+-----------------------+
+ *
+ * List of Audio Channels
+ * +------------------+-----------------------+
+ * | Audio Channel | Description |
+ * +------------------+-----------------------+
+ * | 0x0 | Left Channel |
+ * +------------------+-----------------------+
+ * | 0x1 | Right Channel |
+ * +------------------+---------------- ------+
+ * | 0x2 | Left & Right Channel |
+ * +------------------+-----------------------+
+ *
+ */
+
+#ifndef _IMX_PCM_RPMSG_H
+#define _IMX_PCM_RPMSG_H
+
+#include <linux/pm_qos.h>
+#include <linux/interrupt.h>
+#include <sound/dmaengine_pcm.h>
+
+#define RPMSG_TIMEOUT 1000
+
+/* RPMSG Command (TYPE A)*/
+#define TX_OPEN 0x0
+#define TX_START 0x1
+#define TX_PAUSE 0x2
+#define TX_RESTART 0x3
+#define TX_TERMINATE 0x4
+#define TX_CLOSE 0x5
+#define TX_HW_PARAM 0x6
+#define TX_BUFFER 0x7
+#define TX_SUSPEND 0x8
+#define TX_RESUME 0x9
+
+#define RX_OPEN 0xA
+#define RX_START 0xB
+#define RX_PAUSE 0xC
+#define RX_RESTART 0xD
+#define RX_TERMINATE 0xE
+#define RX_CLOSE 0xF
+#define RX_HW_PARAM 0x10
+#define RX_BUFFER 0x11
+#define RX_SUSPEND 0x12
+#define RX_RESUME 0x13
+#define SET_CODEC_VALUE 0x14
+#define GET_CODEC_VALUE 0x15
+#define TX_POINTER 0x16
+#define RX_POINTER 0x17
+/* Total msg numver for type A */
+#define MSG_TYPE_A_NUM 0x18
+
+/* RPMSG Command (TYPE C)*/
+#define TX_PERIOD_DONE 0x0
+#define RX_PERIOD_DONE 0x1
+/* Total msg numver for type C */
+#define MSG_TYPE_C_NUM 0x2
+
+#define MSG_MAX_NUM (MSG_TYPE_A_NUM + MSG_TYPE_C_NUM)
+
+#define MSG_TYPE_A 0x0
+#define MSG_TYPE_B 0x1
+#define MSG_TYPE_C 0x2
+
+#define RESP_NONE 0x0
+#define RESP_NOT_ALLOWED 0x1
+#define RESP_SUCCESS 0x2
+#define RESP_FAILED 0x3
+
+#define RPMSG_S16_LE 0x0
+#define RPMSG_S24_LE 0x1
+#define RPMSG_S32_LE 0x2
+#define RPMSG_DSD_U16_LE 0x3
+#define RPMSG_DSD_U24_LE 0x4
+#define RPMSG_DSD_U32_LE 0x5
+
+#define RPMSG_CH_LEFT 0x0
+#define RPMSG_CH_RIGHT 0x1
+#define RPMSG_CH_STEREO 0x2
+
+#define WORK_MAX_NUM 0x30
+
+/* Category define */
+#define IMX_RMPSG_LIFECYCLE 1
+#define IMX_RPMSG_PMIC 2
+#define IMX_RPMSG_AUDIO 3
+#define IMX_RPMSG_KEY 4
+#define IMX_RPMSG_GPIO 5
+#define IMX_RPMSG_RTC 6
+#define IMX_RPMSG_SENSOR 7
+
+/* rpmsg version */
+#define IMX_RMPSG_MAJOR 1
+#define IMX_RMPSG_MINOR 0
+
+#define TX SNDRV_PCM_STREAM_PLAYBACK
+#define RX SNDRV_PCM_STREAM_CAPTURE
+
+/**
+ * struct rpmsg_head: rpmsg header structure
+ *
+ * @cate: category
+ * @major: major version
+ * @minor: minor version
+ * @type: message type (A/B/C)
+ * @cmd: message command
+ * @reserved: reserved space
+ */
+struct rpmsg_head {
+ u8 cate;
+ u8 major;
+ u8 minor;
+ u8 type;
+ u8 cmd;
+ u8 reserved[5];
+} __packed;
+
+/**
+ * struct param_s: sent rpmsg parameter
+ *
+ * @audioindex: audio instance index
+ * @format: audio format
+ * @channels: audio channel number
+ * @rate: sample rate
+ * @buffer_addr: dma buffer physical address or register for SET_CODEC_VALUE
+ * @buffer_size: dma buffer size or register value for SET_CODEC_VALUE
+ * @period_size: period size
+ * @buffer_tail: current period index
+ */
+struct param_s {
+ unsigned char audioindex;
+ unsigned char format;
+ unsigned char channels;
+ unsigned int rate;
+ unsigned int buffer_addr;
+ unsigned int buffer_size;
+ unsigned int period_size;
+ unsigned int buffer_tail;
+} __packed;
+
+/**
+ * struct param_s: send rpmsg parameter
+ *
+ * @audioindex: audio instance index
+ * @resp: response value
+ * @reserved1: reserved space
+ * @buffer_offset: the consumed offset of buffer
+ * @reg_addr: register addr of codec
+ * @reg_data: register value of codec
+ * @reserved2: reserved space
+ * @buffer_tail: current period index
+ */
+struct param_r {
+ unsigned char audioindex;
+ unsigned char resp;
+ unsigned char reserved1[1];
+ unsigned int buffer_offset;
+ unsigned int reg_addr;
+ unsigned int reg_data;
+ unsigned char reserved2[4];
+ unsigned int buffer_tail;
+} __packed;
+
+/* Struct of sent message */
+struct rpmsg_s_msg {
+ struct rpmsg_head header;
+ struct param_s param;
+};
+
+/* Struct of received message */
+struct rpmsg_r_msg {
+ struct rpmsg_head header;
+ struct param_r param;
+};
+
+/* Struct of rpmsg */
+struct rpmsg_msg {
+ struct rpmsg_s_msg s_msg;
+ struct rpmsg_r_msg r_msg;
+};
+
+/* Struct of rpmsg for workqueue */
+struct work_of_rpmsg {
+ struct rpmsg_info *info;
+ /* Sent msg for each work */
+ struct rpmsg_msg msg;
+ struct work_struct work;
+};
+
+/* Struct of timer */
+struct stream_timer {
+ struct timer_list timer;
+ struct rpmsg_info *info;
+ struct snd_pcm_substream *substream;
+};
+
+typedef void (*dma_callback)(void *arg);
+
+/**
+ * struct rpmsg_info: rpmsg audio information
+ *
+ * @rpdev: pointer of rpmsg_device
+ * @dev: pointer for imx_pcm_rpmsg device
+ * @cmd_complete: command is finished
+ * @pm_qos_req: request of pm qos
+ * @r_msg: received rpmsg
+ * @msg: array of rpmsg
+ * @notify: notification msg (type C) for TX & RX
+ * @notify_updated: notification flag for TX & RX
+ * @rpmsg_wq: rpmsg workqueue
+ * @work_list: array of work list for workqueue
+ * @work_write_index: write index of work list
+ * @work_read_index: read index of work list
+ * @msg_drop_count: counter of dropped msg for TX & RX
+ * @num_period: period number for TX & RX
+ * @callback_param: parameter for period elapse callback for TX & RX
+ * @callback: period elapse callback for TX & RX
+ * @send_message: function pointer for send message
+ * @lock: spin lock for TX & RX
+ * @wq_lock: lock for work queue
+ * @msg_lock: lock for send message
+ * @stream_timer: timer for tigger workqueue
+ */
+struct rpmsg_info {
+ struct rpmsg_device *rpdev;
+ struct device *dev;
+ struct completion cmd_complete;
+ struct pm_qos_request pm_qos_req;
+
+ /* Received msg (global) */
+ struct rpmsg_r_msg r_msg;
+ struct rpmsg_msg msg[MSG_MAX_NUM];
+ /* period done */
+ struct rpmsg_msg notify[2];
+ bool notify_updated[2];
+
+ struct workqueue_struct *rpmsg_wq;
+ struct work_of_rpmsg work_list[WORK_MAX_NUM];
+ int work_write_index;
+ int work_read_index;
+ int msg_drop_count[2];
+ int num_period[2];
+ void *callback_param[2];
+ dma_callback callback[2];
+ int (*send_message)(struct rpmsg_msg *msg, struct rpmsg_info *info);
+ spinlock_t lock[2]; /* spin lock for resource protection */
+ spinlock_t wq_lock; /* spin lock for resource protection */
+ struct mutex msg_lock; /* mutex for resource protection */
+ struct stream_timer stream_timer[2];
+};
+
+#define IMX_PCM_DRV_NAME "imx_pcm_rpmsg"
+
+#endif /* IMX_PCM_RPMSG_H */
--
2.27.0
^ permalink raw reply related
* [PATCH v3 7/7] ASoC: dt-bindings: imx-rpmsg: Add binding doc for rpmsg machine driver
From: Shengjiu Wang @ 2021-02-25 2:52 UTC (permalink / raw)
To: lgirdwood, broonie, perex, tiwai, alsa-devel, linux-kernel, timur,
nicoleotsuka, Xiubo.Lee, festevam, linuxppc-dev, robh+dt,
devicetree
In-Reply-To: <1614221563-26822-1-git-send-email-shengjiu.wang@nxp.com>
Imx-rpmsg is a new added machine driver for supporting audio on Cortex-M
core. The Cortex-M core will control the audio interface, DMA and audio
codec, setup the pipeline, the audio driver on Cortex-A core side is just
to communitcate with M core, it is a virtual sound card and don't touch
the hardware.
Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
---
.../bindings/sound/imx-audio-rpmsg.yaml | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-rpmsg.yaml
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.yaml b/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.yaml
new file mode 100644
index 000000000000..ff9b3fc69dae
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.yaml
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/imx-audio-rpmsg.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP i.MX audio complex with rpmsg
+
+maintainers:
+ - Shengjiu Wang <shengjiu.wang@nxp.com>
+
+description: |
+ imx-rpmsg is a machine driver for supporting audio by rpmsg.
+ The Cortex-M core will control the audio interface, DMA and audio
+ codec, setup the pipeline, the audio driver on Cortex-A core side
+ is just to communitcate with M core, it is a virtual sound card
+ and don't touch the hardware.
+
+properties:
+ compatible:
+ enum:
+ - fsl,imx-audio-rpmsg
+
+ model:
+ $ref: /schemas/types.yaml#/definitions/string
+ description: User specified audio sound card name
+
+ audio-cpu:
+ description: The phandle of an CPU DAI controller
+
+ rpmsg-out:
+ description: |
+ This is a boolean property. If present, the transmitting function
+ will be enabled,
+
+ rpmsg-in:
+ description: |
+ This is a boolean property. If present, the receiving function
+ will be enabled.
+
+required:
+ - compatible
+ - model
+ - audio-cpu
+
+additionalProperties: false
+
+examples:
+ - |
+ sound-rpmsg {
+ compatible = "fsl,imx-audio-rpmsg";
+ model = "ak4497-audio";
+ audio-cpu = <&rpmsg_audio>;
+ rpmsg-out;
+ };
--
2.27.0
^ permalink raw reply related
* [RFC PATCH 0/8] WIP support for the LLVM integrated assembler
From: Daniel Axtens @ 2021-02-25 3:09 UTC (permalink / raw)
To: llvmlinux, linuxppc-dev; +Cc: Daniel Axtens
To support Clang's CFI we need LTO. For LTO, we need to be able to compile
with the LLVM integrated assembler.
Currently, we can't.
This series gets us a bit closer, but I'm still stuck and I'm hoping
someone can point me in the right direction.
Patch 1 is a fix that can be merged at any time.
The rest of this series is pretty rough, but with it, building like this:
make CC=clang-11 LD=ld.lld-11 AR=llvm-ar-11 NM=llvm-nm-11 STRIP=llvm-strip-11 \
OBJCOPY=llvm-objcopy-11 OBJDUMP=llvm-objdump-11 READELF=llvm-readelf-11 \
HOSTCC=clang-11 HOSTCXX=clang++-11 HOSTAR=llvm-ar-11 HOSTLD=ld.lld-11 \
LLVM_IAS=1 vmlinux
on a pseries_le_defconfig without Werror works except for head-64.S,
which still fails as described in the final patch. Help would be
appreciated because it's deep magic all around.
Apart from the very very dodgy change to drop the tlbiel feature
section, none of the de-gas-ing changed the compiled binary for me
under gcc-10.2.0-13ubuntu1.
Daniel Axtens (8):
powerpc/64s/exception: Clean up a missed SRR specifier
powerpc: check for support for -Wa,-m{power4,any}
powerpc/head-64: do less gas-specific stuff with sections
powerpc/ppc_asm: use plain numbers for registers
poweprc/lib/quad: Provide macros for lq/stq
powerpc/mm/book3s64/hash: drop pre 2.06 tlbiel for clang
powerpc/purgatory: drop .machine specifier
powerpc/64/asm: don't reassign labels
arch/powerpc/Makefile | 4 +-
arch/powerpc/include/asm/head-64.h | 20 ++++----
arch/powerpc/include/asm/ppc-opcode.h | 4 ++
arch/powerpc/include/asm/ppc_asm.h | 64 +++++++++++++-------------
arch/powerpc/kernel/exceptions-64s.S | 33 ++++++-------
arch/powerpc/kernel/head_64.S | 16 +++----
arch/powerpc/lib/quad.S | 4 +-
arch/powerpc/mm/book3s64/hash_native.c | 10 ++++
arch/powerpc/purgatory/trampoline_64.S | 2 +-
9 files changed, 86 insertions(+), 71 deletions(-)
--
2.27.0
^ permalink raw reply
* [PATCH 1/8] powerpc/64s/exception: Clean up a missed SRR specifier
From: Daniel Axtens @ 2021-02-25 3:09 UTC (permalink / raw)
To: llvmlinux, linuxppc-dev; +Cc: Nicholas Piggin, Daniel Axtens
In-Reply-To: <20210225031006.1204774-1-dja@axtens.net>
Nick's patch cleaning up the SRR specifiers in exception-64s.S
missed a single instance of EXC_HV_OR_STD. Clean that up.
Caught by clang's integrated assembler.
Fixes: 3f7fbd97d07d ("powerpc/64s/exception: Clean up SRR specifiers")
Acked-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Daniel Axtens <dja@axtens.net>
---
arch/powerpc/kernel/exceptions-64s.S | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/powerpc/kernel/exceptions-64s.S b/arch/powerpc/kernel/exceptions-64s.S
index a4bd3c114a0a..720fb9892745 100644
--- a/arch/powerpc/kernel/exceptions-64s.S
+++ b/arch/powerpc/kernel/exceptions-64s.S
@@ -466,7 +466,7 @@ DEFINE_FIXED_SYMBOL(\name\()_common_real)
ld r10,PACAKMSR(r13) /* get MSR value for kernel */
/* MSR[RI] is clear iff using SRR regs */
- .if IHSRR == EXC_HV_OR_STD
+ .if IHSRR_IF_HVMODE
BEGIN_FTR_SECTION
xori r10,r10,MSR_RI
END_FTR_SECTION_IFCLR(CPU_FTR_HVMODE)
--
2.27.0
^ permalink raw reply related
* [RFC PATCH 2/8] powerpc: check for support for -Wa,-m{power4,any}
From: Daniel Axtens @ 2021-02-25 3:10 UTC (permalink / raw)
To: llvmlinux, linuxppc-dev; +Cc: Daniel Axtens
In-Reply-To: <20210225031006.1204774-1-dja@axtens.net>
LLVM's integrated assembler does not like either -Wa,-mpower4
or -Wa,-many. So just don't pass them if they're not supported.
Signed-off-by: Daniel Axtens <dja@axtens.net>
---
arch/powerpc/Makefile | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/Makefile b/arch/powerpc/Makefile
index 08cf0eade56a..3e2c72d20bb8 100644
--- a/arch/powerpc/Makefile
+++ b/arch/powerpc/Makefile
@@ -252,7 +252,9 @@ cpu-as-$(CONFIG_E500) += -Wa,-me500
# When using '-many -mpower4' gas will first try and find a matching power4
# mnemonic and failing that it will allow any valid mnemonic that GAS knows
# about. GCC will pass -many to GAS when assembling, clang does not.
-cpu-as-$(CONFIG_PPC_BOOK3S_64) += -Wa,-mpower4 -Wa,-many
+# LLVM IAS doesn't understand either flag: https://github.com/ClangBuiltLinux/linux/issues/675
+# but LLVM IAS only supports ISA >= 2.06 for Book3S 64 anyway...
+cpu-as-$(CONFIG_PPC_BOOK3S_64) += $(call as-option,-Wa$(comma)-mpower4) $(call as-option,-Wa$(comma)-many)
cpu-as-$(CONFIG_PPC_E500MC) += $(call as-option,-Wa$(comma)-me500mc)
KBUILD_AFLAGS += $(cpu-as-y)
--
2.27.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox