Fix TLB uniquification for systems with TLB not initialised by firmware

Fix allocation in TLB uniquification
 Fix SiByte cache initialisation
 Check uart parameters from firmware on Loongson64 systems
 Fix clock id mismatch for Ralink SoCs
 Fix GCC version check for __mutli3 workaround
 -----BEGIN PGP SIGNATURE-----
 
 iQJOBAABCAA4FiEEbt46xwy6kEcDOXoUeZbBVTGwZHAFAmnSLdQaHHRzYm9nZW5k
 QGFscGhhLmZyYW5rZW4uZGUACgkQeZbBVTGwZHC6fA/7B13xK55Q70sZsiWK+02m
 XhE2cEOctWpzpwoMGYov4lY4alW4IzL7ZVWqeD96QeEILopHf448w/3+KCbdjmKr
 0+6AY9n1k91i0R2hhEWBor/B+4EXjUgKGrGTzQxyCKkRnzQcy0HBuoZuY7xoW2j/
 432DmEs4ogTFHMpkwrz0jB/dv7286KY5VUOr4y2G3SXMgF9mrPqQsFTypR2IC7IS
 TZ8rr+tjtsM2WYcayW6pbo5jBX+TfJbRxfdQELjX1YStGkThtIg1ZNUD+DK3CTUO
 M9Id1eknSUYcyuxlJruVwEjRZcv49uPEE4JTPjs50dZQxSsWPp1B70FZN/KsP3D3
 miJS2Shx0ptPVta/DiNB0sdLFdMvo2FeAQBzl3ZrAYXVuIT9/KrXY/ZiInLevMFa
 +m7J85XvfW7vcLtHM0ptz3+Zk836nnTaqUgLkTTtCEBLH/YAjkY8/orZOHaFfOHJ
 zXzLZhnhyosJ3Hd4Ie49YekIPjcKf9bMastGzD887syLOGtGqU6FpV0FSgJtER71
 SXYmUfZfS7Aj4nfwQuijAJSs9aZnSXUEvxYOQvAM9Go1zLBIZXEVJNmYd8smfpF0
 OduLVKDliirEmHPDMOLKmX26E6EZxeres4aHVAJ7xOsyoTwxFY/8PzInQFP4wV7X
 /aVMB91RS16sJIphpHcJ6E8=
 =1q4p
 -----END PGP SIGNATURE-----

Merge tag 'mips-fixes_7.0_1' of git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux

Pull MIPS fixes from Thomas Bogendoerfer:

 - Fix TLB uniquification for systems with TLB not initialised by
   firmware

 - Fix allocation in TLB uniquification

 - Fix SiByte cache initialisation

 - Check uart parameters from firmware on Loongson64 systems

 - Fix clock id mismatch for Ralink SoCs

 - Fix GCC version check for __mutli3 workaround

* tag 'mips-fixes_7.0_1' of git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux:
  mips: mm: Allocate tlb_vpn array atomically
  MIPS: mm: Rewrite TLB uniquification for the hidden bit feature
  MIPS: mm: Suppress TLB uniquification on EHINV hardware
  MIPS: Always record SEGBITS in cpu_data.vmbits
  MIPS: Fix the GCC version check for `__multi3' workaround
  MIPS: SiByte: Bring back cache initialisation
  mips: ralink: update CPU clock index
  MIPS: Loongson64: env: Check UARTs passed by LEFI cautiously
This commit is contained in:
Linus Torvalds 2026-04-05 11:29:07 -07:00
commit eb3765aa71
10 changed files with 269 additions and 73 deletions

View File

@ -484,7 +484,6 @@
# endif
# ifndef cpu_vmbits
# define cpu_vmbits cpu_data[0].vmbits
# define __NEED_VMBITS_PROBE
# endif
#endif

View File

@ -80,9 +80,7 @@ struct cpuinfo_mips {
int srsets; /* Shadow register sets */
int package;/* physical package number */
unsigned int globalnumber;
#ifdef CONFIG_64BIT
int vmbits; /* Virtual memory size in bits */
#endif
void *data; /* Additional data */
unsigned int watch_reg_count; /* Number that exist */
unsigned int watch_reg_use_cnt; /* Usable by ptrace */

View File

@ -1871,6 +1871,8 @@ do { \
#define read_c0_entryhi() __read_ulong_c0_register($10, 0)
#define write_c0_entryhi(val) __write_ulong_c0_register($10, 0, val)
#define read_c0_entryhi_64() __read_64bit_c0_register($10, 0)
#define write_c0_entryhi_64(val) __write_64bit_c0_register($10, 0, val)
#define read_c0_guestctl1() __read_32bit_c0_register($10, 4)
#define write_c0_guestctl1(val) __write_32bit_c0_register($10, 4, val)

View File

@ -210,11 +210,14 @@ static inline void set_elf_base_platform(const char *plat)
static inline void cpu_probe_vmbits(struct cpuinfo_mips *c)
{
#ifdef __NEED_VMBITS_PROBE
write_c0_entryhi(0x3fffffffffffe000ULL);
back_to_back_c0_hazard();
c->vmbits = fls64(read_c0_entryhi() & 0x3fffffffffffe000ULL);
#endif
int vmbits = 31;
if (cpu_has_64bits) {
write_c0_entryhi_64(0x3fffffffffffe000ULL);
back_to_back_c0_hazard();
vmbits = fls64(read_c0_entryhi_64() & 0x3fffffffffffe000ULL);
}
c->vmbits = vmbits;
}
static void set_isa(struct cpuinfo_mips *c, unsigned int isa)

View File

@ -137,6 +137,8 @@ void cpu_probe(void)
else
cpu_set_nofpu_opts(c);
c->vmbits = 31;
reserve_exception_space(0, 0x400);
}

View File

@ -4,12 +4,12 @@
#include "libgcc.h"
/*
* GCC 7 & older can suboptimally generate __multi3 calls for mips64r6, so for
* GCC 9 & older can suboptimally generate __multi3 calls for mips64r6, so for
* that specific case only we implement that intrinsic here.
*
* See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82981
*/
#if defined(CONFIG_64BIT) && defined(CONFIG_CPU_MIPSR6) && (__GNUC__ < 8)
#if defined(CONFIG_64BIT) && defined(CONFIG_CPU_MIPSR6) && (__GNUC__ < 10)
/* multiply 64-bit values, low 64-bits returned */
static inline long long notrace dmulu(long long a, long long b)
@ -51,4 +51,4 @@ ti_type notrace __multi3(ti_type a, ti_type b)
}
EXPORT_SYMBOL(__multi3);
#endif /* 64BIT && CPU_MIPSR6 && GCC7 */
#endif /* 64BIT && CPU_MIPSR6 && GCC9 */

View File

@ -17,7 +17,9 @@
#include <linux/dma-map-ops.h>
#include <linux/export.h>
#include <linux/libfdt.h>
#include <linux/minmax.h>
#include <linux/pci_ids.h>
#include <linux/serial_core.h>
#include <linux/string_choices.h>
#include <asm/bootinfo.h>
#include <loongson.h>
@ -106,9 +108,23 @@ static void __init lefi_fixup_fdt(struct system_loongson *system)
is_loongson64g = (read_c0_prid() & PRID_IMP_MASK) == PRID_IMP_LOONGSON_64G;
for (i = 0; i < system->nr_uarts; i++) {
for (i = 0; i < min(system->nr_uarts, MAX_UARTS); i++) {
uartdev = &system->uarts[i];
/*
* Some firmware does not set nr_uarts properly and passes empty
* items. Ignore them silently.
*/
if (uartdev->uart_base == 0)
continue;
/* Our DT only works with UPIO_MEM. */
if (uartdev->iotype != UPIO_MEM) {
pr_warn("Ignore UART 0x%llx with iotype %u passed by firmware\n",
uartdev->uart_base, uartdev->iotype);
continue;
}
ret = lefi_fixup_fdt_serial(fdt_buf, uartdev->uart_base,
uartdev->uartclk);
/*

View File

@ -207,7 +207,8 @@ void cpu_cache_init(void)
{
if (IS_ENABLED(CONFIG_CPU_R3000) && cpu_has_3k_cache)
r3k_cache_init();
if (IS_ENABLED(CONFIG_CPU_R4K_CACHE_TLB) && cpu_has_4k_cache)
if ((IS_ENABLED(CONFIG_CPU_R4K_CACHE_TLB) ||
IS_ENABLED(CONFIG_CPU_SB1)) && cpu_has_4k_cache)
r4k_cache_init();
if (IS_ENABLED(CONFIG_CPU_CAVIUM_OCTEON) && cpu_has_octeon_cache)

View File

@ -13,6 +13,7 @@
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/memblock.h>
#include <linux/minmax.h>
#include <linux/mm.h>
#include <linux/hugetlb.h>
#include <linux/export.h>
@ -24,6 +25,7 @@
#include <asm/hazards.h>
#include <asm/mmu_context.h>
#include <asm/tlb.h>
#include <asm/tlbdebug.h>
#include <asm/tlbex.h>
#include <asm/tlbmisc.h>
#include <asm/setup.h>
@ -511,12 +513,229 @@ static int __init set_ntlb(char *str)
__setup("ntlb=", set_ntlb);
/* Comparison function for EntryHi VPN fields. */
static int r4k_vpn_cmp(const void *a, const void *b)
/* The start bit position of VPN2 and Mask in EntryHi/PageMask registers. */
#define VPN2_SHIFT 13
/* Read full EntryHi even with CONFIG_32BIT. */
static inline unsigned long long read_c0_entryhi_native(void)
{
long v = *(unsigned long *)a - *(unsigned long *)b;
int s = sizeof(long) > sizeof(int) ? sizeof(long) * 8 - 1: 0;
return s ? (v != 0) | v >> s : v;
return cpu_has_64bits ? read_c0_entryhi_64() : read_c0_entryhi();
}
/* Write full EntryHi even with CONFIG_32BIT. */
static inline void write_c0_entryhi_native(unsigned long long v)
{
if (cpu_has_64bits)
write_c0_entryhi_64(v);
else
write_c0_entryhi(v);
}
/* TLB entry state for uniquification. */
struct tlbent {
unsigned long long wired:1;
unsigned long long global:1;
unsigned long long asid:10;
unsigned long long vpn:51;
unsigned long long pagesz:5;
unsigned long long index:14;
};
/*
* Comparison function for TLB entry sorting. Place wired entries first,
* then global entries, then order by the increasing VPN/ASID and the
* decreasing page size. This lets us avoid clashes with wired entries
* easily and get entries for larger pages out of the way first.
*
* We could group bits so as to reduce the number of comparisons, but this
* is seldom executed and not performance-critical, so prefer legibility.
*/
static int r4k_entry_cmp(const void *a, const void *b)
{
struct tlbent ea = *(struct tlbent *)a, eb = *(struct tlbent *)b;
if (ea.wired > eb.wired)
return -1;
else if (ea.wired < eb.wired)
return 1;
else if (ea.global > eb.global)
return -1;
else if (ea.global < eb.global)
return 1;
else if (ea.vpn < eb.vpn)
return -1;
else if (ea.vpn > eb.vpn)
return 1;
else if (ea.asid < eb.asid)
return -1;
else if (ea.asid > eb.asid)
return 1;
else if (ea.pagesz > eb.pagesz)
return -1;
else if (ea.pagesz < eb.pagesz)
return 1;
else
return 0;
}
/*
* Fetch all the TLB entries. Mask individual VPN values retrieved with
* the corresponding page mask and ignoring any 1KiB extension as we'll
* be using 4KiB pages for uniquification.
*/
static void __ref r4k_tlb_uniquify_read(struct tlbent *tlb_vpns, int tlbsize)
{
int start = num_wired_entries();
unsigned long long vpn_mask;
bool global;
int i;
vpn_mask = GENMASK(current_cpu_data.vmbits - 1, VPN2_SHIFT);
vpn_mask |= cpu_has_64bits ? 3ULL << 62 : 1 << 31;
for (i = 0; i < tlbsize; i++) {
unsigned long long entryhi, vpn, mask, asid;
unsigned int pagesz;
write_c0_index(i);
mtc0_tlbr_hazard();
tlb_read();
tlb_read_hazard();
global = !!(read_c0_entrylo0() & ENTRYLO_G);
entryhi = read_c0_entryhi_native();
mask = read_c0_pagemask();
asid = entryhi & cpu_asid_mask(&current_cpu_data);
vpn = (entryhi & vpn_mask & ~mask) >> VPN2_SHIFT;
pagesz = ilog2((mask >> VPN2_SHIFT) + 1);
tlb_vpns[i].global = global;
tlb_vpns[i].asid = global ? 0 : asid;
tlb_vpns[i].vpn = vpn;
tlb_vpns[i].pagesz = pagesz;
tlb_vpns[i].wired = i < start;
tlb_vpns[i].index = i;
}
}
/*
* Write unique values to all but the wired TLB entries each, using
* the 4KiB page size. This size might not be supported with R6, but
* EHINV is mandatory for R6, so we won't ever be called in that case.
*
* A sorted table is supplied with any wired entries at the beginning,
* followed by any global entries, and then finally regular entries.
* We start at the VPN and ASID values of zero and only assign user
* addresses, therefore guaranteeing no clash with addresses produced
* by UNIQUE_ENTRYHI. We avoid any VPN values used by wired or global
* entries, by increasing the VPN value beyond the span of such entry.
*
* When a VPN/ASID clash is found with a regular entry we increment the
* ASID instead until no VPN/ASID clash has been found or the ASID space
* has been exhausted, in which case we increase the VPN value beyond
* the span of the largest clashing entry.
*
* We do not need to be concerned about FTLB or MMID configurations as
* those are required to implement the EHINV feature.
*/
static void __ref r4k_tlb_uniquify_write(struct tlbent *tlb_vpns, int tlbsize)
{
unsigned long long asid, vpn, vpn_size, pagesz;
int widx, gidx, idx, sidx, lidx, i;
vpn_size = 1ULL << (current_cpu_data.vmbits - VPN2_SHIFT);
pagesz = ilog2((PM_4K >> VPN2_SHIFT) + 1);
write_c0_pagemask(PM_4K);
write_c0_entrylo0(0);
write_c0_entrylo1(0);
asid = 0;
vpn = 0;
widx = 0;
gidx = 0;
for (sidx = 0; sidx < tlbsize && tlb_vpns[sidx].wired; sidx++)
;
for (lidx = sidx; lidx < tlbsize && tlb_vpns[lidx].global; lidx++)
;
idx = gidx = sidx + 1;
for (i = sidx; i < tlbsize; i++) {
unsigned long long entryhi, vpn_pagesz = 0;
while (1) {
if (WARN_ON(vpn >= vpn_size)) {
dump_tlb_all();
/* Pray local_flush_tlb_all() will cope. */
return;
}
/* VPN must be below the next wired entry. */
if (widx < sidx && vpn >= tlb_vpns[widx].vpn) {
vpn = max(vpn,
(tlb_vpns[widx].vpn +
(1ULL << tlb_vpns[widx].pagesz)));
asid = 0;
widx++;
continue;
}
/* VPN must be below the next global entry. */
if (gidx < lidx && vpn >= tlb_vpns[gidx].vpn) {
vpn = max(vpn,
(tlb_vpns[gidx].vpn +
(1ULL << tlb_vpns[gidx].pagesz)));
asid = 0;
gidx++;
continue;
}
/* Try to find a free ASID so as to conserve VPNs. */
if (idx < tlbsize && vpn == tlb_vpns[idx].vpn &&
asid == tlb_vpns[idx].asid) {
unsigned long long idx_pagesz;
idx_pagesz = tlb_vpns[idx].pagesz;
vpn_pagesz = max(vpn_pagesz, idx_pagesz);
do
idx++;
while (idx < tlbsize &&
vpn == tlb_vpns[idx].vpn &&
asid == tlb_vpns[idx].asid);
asid++;
if (asid > cpu_asid_mask(&current_cpu_data)) {
vpn += vpn_pagesz;
asid = 0;
vpn_pagesz = 0;
}
continue;
}
/* VPN mustn't be above the next regular entry. */
if (idx < tlbsize && vpn > tlb_vpns[idx].vpn) {
vpn = max(vpn,
(tlb_vpns[idx].vpn +
(1ULL << tlb_vpns[idx].pagesz)));
asid = 0;
idx++;
continue;
}
break;
}
entryhi = (vpn << VPN2_SHIFT) | asid;
write_c0_entryhi_native(entryhi);
write_c0_index(tlb_vpns[i].index);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlb_vpns[i].asid = asid;
tlb_vpns[i].vpn = vpn;
tlb_vpns[i].pagesz = pagesz;
asid++;
if (asid > cpu_asid_mask(&current_cpu_data)) {
vpn += 1ULL << pagesz;
asid = 0;
}
}
}
/*
@ -527,70 +746,25 @@ static void __ref r4k_tlb_uniquify(void)
{
int tlbsize = current_cpu_data.tlbsize;
bool use_slab = slab_is_available();
int start = num_wired_entries();
phys_addr_t tlb_vpn_size;
unsigned long *tlb_vpns;
unsigned long vpn_mask;
int cnt, ent, idx, i;
vpn_mask = GENMASK(cpu_vmbits - 1, 13);
vpn_mask |= IS_ENABLED(CONFIG_64BIT) ? 3ULL << 62 : 1 << 31;
struct tlbent *tlb_vpns;
tlb_vpn_size = tlbsize * sizeof(*tlb_vpns);
tlb_vpns = (use_slab ?
kmalloc(tlb_vpn_size, GFP_KERNEL) :
kmalloc(tlb_vpn_size, GFP_ATOMIC) :
memblock_alloc_raw(tlb_vpn_size, sizeof(*tlb_vpns)));
if (WARN_ON(!tlb_vpns))
return; /* Pray local_flush_tlb_all() is good enough. */
htw_stop();
for (i = start, cnt = 0; i < tlbsize; i++, cnt++) {
unsigned long vpn;
r4k_tlb_uniquify_read(tlb_vpns, tlbsize);
write_c0_index(i);
mtc0_tlbr_hazard();
tlb_read();
tlb_read_hazard();
vpn = read_c0_entryhi();
vpn &= vpn_mask & PAGE_MASK;
tlb_vpns[cnt] = vpn;
sort(tlb_vpns, tlbsize, sizeof(*tlb_vpns), r4k_entry_cmp, NULL);
/* Prevent any large pages from overlapping regular ones. */
write_c0_pagemask(read_c0_pagemask() & PM_DEFAULT_MASK);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
}
sort(tlb_vpns, cnt, sizeof(tlb_vpns[0]), r4k_vpn_cmp, NULL);
r4k_tlb_uniquify_write(tlb_vpns, tlbsize);
write_c0_pagemask(PM_DEFAULT_MASK);
write_c0_entrylo0(0);
write_c0_entrylo1(0);
idx = 0;
ent = tlbsize;
for (i = start; i < tlbsize; i++)
while (1) {
unsigned long entryhi, vpn;
entryhi = UNIQUE_ENTRYHI(ent);
vpn = entryhi & vpn_mask & PAGE_MASK;
if (idx >= cnt || vpn < tlb_vpns[idx]) {
write_c0_entryhi(entryhi);
write_c0_index(i);
mtc0_tlbw_hazard();
tlb_write_indexed();
ent++;
break;
} else if (vpn == tlb_vpns[idx]) {
ent++;
} else {
idx++;
}
}
tlbw_use_hazard();
htw_start();
@ -640,7 +814,8 @@ static void r4k_tlb_configure(void)
temp_tlb_entry = current_cpu_data.tlbsize - 1;
/* From this point on the ARC firmware is dead. */
r4k_tlb_uniquify();
if (!cpu_has_tlbinv)
r4k_tlb_uniquify();
local_flush_tlb_all();
/* Did I tell you that ARC SUCKS? */

View File

@ -21,16 +21,16 @@ static const char *clk_cpu(int *idx)
{
switch (ralink_soc) {
case RT2880_SOC:
*idx = 0;
*idx = 1;
return "ralink,rt2880-sysc";
case RT3883_SOC:
*idx = 0;
*idx = 1;
return "ralink,rt3883-sysc";
case RT305X_SOC_RT3050:
*idx = 0;
*idx = 1;
return "ralink,rt3050-sysc";
case RT305X_SOC_RT3052:
*idx = 0;
*idx = 1;
return "ralink,rt3052-sysc";
case RT305X_SOC_RT3350:
*idx = 1;