The PAPR document specifies the TLB Block Invalidate Characteristics which
tells for each pair of segment base page size, actual page size, the size
of the block the hcall H_BLOCK_REMOVE supports.

These characteristics are loaded at boot time in a new table hblkr_size.
The table is separate from the mmu_psize_def because this is specific to
the pseries platform.

A new init function, pseries_lpar_read_hblkrm_characteristics() is added to
read the characteristics. It is called from pSeries_setup_arch().

Fixes: ba2dd8a26baa ("powerpc/pseries/mm: call H_BLOCK_REMOVE")
Signed-off-by: Laurent Dufour <lduf...@linux.ibm.com>
---
 arch/powerpc/platforms/pseries/lpar.c    | 139 +++++++++++++++++++++++
 arch/powerpc/platforms/pseries/pseries.h |   1 +
 arch/powerpc/platforms/pseries/setup.c   |   1 +
 3 files changed, 141 insertions(+)

diff --git a/arch/powerpc/platforms/pseries/lpar.c 
b/arch/powerpc/platforms/pseries/lpar.c
index 36b846f6e74e..8dad8d9c9e76 100644
--- a/arch/powerpc/platforms/pseries/lpar.c
+++ b/arch/powerpc/platforms/pseries/lpar.c
@@ -56,6 +56,15 @@ EXPORT_SYMBOL(plpar_hcall);
 EXPORT_SYMBOL(plpar_hcall9);
 EXPORT_SYMBOL(plpar_hcall_norets);
 
+/*
+ * H_BLOCK_REMOVE supported block size for this page size in segment who's base
+ * page size is that page size.
+ *
+ * The first index is the segment base page size, the second one is the actual
+ * page size.
+ */
+static int hblkrm_size[MMU_PAGE_COUNT][MMU_PAGE_COUNT] __ro_after_init;
+
 #ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
 static u8 dtl_mask = DTL_LOG_PREEMPT;
 #else
@@ -1311,6 +1320,136 @@ static void do_block_remove(unsigned long number, 
struct ppc64_tlb_batch *batch,
                (void)call_block_remove(pix, param, true);
 }
 
+/*
+ * TLB Block Invalidate Characteristics
+ *
+ * These characteristics define the size of the block the hcall H_BLOCK_REMOVE
+ * is able to process for each couple segment base page size, actual page size.
+ *
+ * The ibm,get-system-parameter properties is returning a buffer with the
+ * following layout:
+ *
+ * [ 2 bytes size of the RTAS buffer (excluding these 2 bytes) ]
+ * -----------------
+ * TLB Block Invalidate Specifiers:
+ * [ 1 byte LOG base 2 of the TLB invalidate block size being specified ]
+ * [ 1 byte Number of page sizes (N) that are supported for the specified
+ *          TLB invalidate block size ]
+ * [ 1 byte Encoded segment base page size and actual page size
+ *          MSB=0 means 4k segment base page size and actual page size
+ *          MSB=1 the penc value in mmu_psize_def ]
+ * ...
+ * -----------------
+ * Next TLB Block Invalidate Specifiers...
+ * -----------------
+ * [ 0 ]
+ */
+static inline void set_hblkrm_bloc_size(int bpsize, int psize,
+                                       unsigned int block_size)
+{
+       if (block_size > hblkrm_size[bpsize][psize])
+               hblkrm_size[bpsize][psize] = block_size;
+}
+
+/*
+ * Decode the Encoded segment base page size and actual page size.
+ * PAPR specifies:
+ *   - bit 7 is the L bit
+ *   - bits 0-5 are the penc value
+ * If the L bit is 0, this means 4K segment base page size and actual page size
+ * otherwise the penc value should be read.
+ */
+#define HBLKRM_L_MASK          0x80
+#define HBLKRM_PENC_MASK       0x3f
+static inline void __init check_lp_set_hblkrm(unsigned int lp,
+                                             unsigned int block_size)
+{
+       unsigned int bpsize, psize;
+
+       /* First, check the L bit, if not set, this means 4K */
+       if ((lp & HBLKRM_L_MASK) == 0) {
+               set_hblkrm_bloc_size(MMU_PAGE_4K, MMU_PAGE_4K, block_size);
+               return;
+       }
+
+       lp &= HBLKRM_PENC_MASK;
+       for (bpsize = 0; bpsize < MMU_PAGE_COUNT; bpsize++) {
+               struct mmu_psize_def *def = &mmu_psize_defs[bpsize];
+
+               for (psize = 0; psize < MMU_PAGE_COUNT; psize++) {
+                       if (def->penc[psize] == lp) {
+                               set_hblkrm_bloc_size(bpsize, psize, block_size);
+                               return;
+                       }
+               }
+       }
+}
+
+#define SPLPAR_TLB_BIC_TOKEN           50
+/*
+ * The size of the TLB Block Invalidate Characteristics is variable. But at the
+ * maximum it will be the number of possible page sizes *2 + 10 bytes.
+ * Currently MMU_PAGE_COUNT is 16, which means 42 bytes. Use a cache line size
+ * (128 bytes) for the buffer to get plenty of space.
+ */
+#define SPLPAR_TLB_BIC_MAXLENGTH       128
+//#warning revoir ce define mpe voudrait que ce soit cach size aligned
+void __init pseries_lpar_read_hblkrm_characteristics(void)
+{
+       unsigned char local_buffer[SPLPAR_TLB_BIC_MAXLENGTH];
+       int call_status, len, idx, bpsize;
+
+       spin_lock(&rtas_data_buf_lock);
+       memset(rtas_data_buf, 0, RTAS_DATA_BUF_SIZE);
+       call_status = rtas_call(rtas_token("ibm,get-system-parameter"), 3, 1,
+                               NULL,
+                               SPLPAR_TLB_BIC_TOKEN,
+                               __pa(rtas_data_buf),
+                               RTAS_DATA_BUF_SIZE);
+       memcpy(local_buffer, rtas_data_buf, SPLPAR_TLB_BIC_MAXLENGTH);
+       local_buffer[SPLPAR_TLB_BIC_MAXLENGTH - 1] = '\0';
+       spin_unlock(&rtas_data_buf_lock);
+
+       if (call_status != 0) {
+               pr_warn("%s %s Error calling get-system-parameter (0x%x)\n",
+                       __FILE__, __func__, call_status);
+               return;
+       }
+
+       /*
+        * The first two (2) bytes of the data in the buffer are the length of
+        * the returned data, not counting these first two (2) bytes.
+        */
+       len = be16_to_cpu(*((u16 *)local_buffer)) + 2;
+       if (len > SPLPAR_TLB_BIC_MAXLENGTH) {
+               pr_warn("%s too large returned buffer %d", __func__, len);
+               return;
+       }
+
+       idx = 2;
+       while (idx < len) {
+               u8 block_shift = local_buffer[idx++];
+               u32 block_size;
+               unsigned int npsize;
+
+               if (!block_shift)
+                       break;
+
+               block_size = 1 << block_shift;
+
+               for (npsize = local_buffer[idx++];
+                    npsize > 0 && idx < len; npsize--)
+                       check_lp_set_hblkrm((unsigned int) local_buffer[idx++],
+                                           block_size);
+       }
+
+       for (bpsize = 0; bpsize < MMU_PAGE_COUNT; bpsize++)
+               for (idx = 0; idx < MMU_PAGE_COUNT; idx++)
+                       if (hblkrm_size[bpsize][idx])
+                               pr_info("H_BLOCK_REMOVE supports base psize:%d 
psize:%d block size:%d",
+                                       bpsize, idx, hblkrm_size[bpsize][idx]);
+}
+
 /*
  * Take a spinlock around flushes to avoid bouncing the hypervisor tlbie
  * lock.
diff --git a/arch/powerpc/platforms/pseries/pseries.h 
b/arch/powerpc/platforms/pseries/pseries.h
index a6624d4bd9d0..13fa370a87e4 100644
--- a/arch/powerpc/platforms/pseries/pseries.h
+++ b/arch/powerpc/platforms/pseries/pseries.h
@@ -112,5 +112,6 @@ static inline unsigned long cmo_get_page_size(void)
 int dlpar_workqueue_init(void);
 
 void pseries_setup_rfi_flush(void);
+void pseries_lpar_read_hblkrm_characteristics(void);
 
 #endif /* _PSERIES_PSERIES_H */
diff --git a/arch/powerpc/platforms/pseries/setup.c 
b/arch/powerpc/platforms/pseries/setup.c
index f8adcd0e4589..0a40201f315f 100644
--- a/arch/powerpc/platforms/pseries/setup.c
+++ b/arch/powerpc/platforms/pseries/setup.c
@@ -744,6 +744,7 @@ static void __init pSeries_setup_arch(void)
 
        pseries_setup_rfi_flush();
        setup_stf_barrier();
+       pseries_lpar_read_hblkrm_characteristics();
 
        /* By default, only probe PCI (can be overridden by rtas_pci) */
        pci_add_flags(PCI_PROBE_ONLY);
-- 
2.23.0

Reply via email to