Currently, a Preemptible Kernel is only supported on the Coldfire CPU
family. Extend preempt support to the full Classic M68K CPU family
(68020+ with MMU, and 68000-derivatives without MMU).
Make sure preemption is disabled in loops involving cache and TLB
flushing.
Co-developed-by: Finn Thain <[email protected]>
Signed-off-by: Finn Thain <[email protected]>
Co-developed-by: Michael Schmitz <[email protected]>
Signed-off-by: Michael Schmitz <[email protected]>
Signed-off-by: Geert Uytterhoeven <[email protected]>
---
This patch touches Coldfire cache handling and other code paths taken on
Coldfire. Perhaps these parts should be spun off in a separate patch as
bug fixes? Or are they not needed?
v2:
- Disable preemption in get_pointer_table() (Finn) and
free_pointer_table() (Michael),
- Call preempt_enable_no_resched() instead of
sched_preempt_enable_no_resched(), cfr. commit ba74c1448f127649
("sched/rt: Document scheduler related skip-resched-check sites").
v1 at
https://lore.kernel.org/all/7858a184cda66e0991fd295c711dfed7e4d1248c.1696603287.git.geert@linux-m68k.org/
---
arch/m68k/68000/entry.S | 15 +++++++++++++++
arch/m68k/Kconfig | 1 -
arch/m68k/coldfire/cache.c | 6 ++++++
arch/m68k/include/asm/cacheflush_mm.h | 23 +++++++++++++++++++++++
arch/m68k/include/asm/tlbflush.h | 16 ++++++++++++++++
arch/m68k/kernel/entry.S | 16 ++++++++++++++++
arch/m68k/mm/cache.c | 15 +++++++++++++++
arch/m68k/mm/motorola.c | 27 ++++++++++++++++++++++++---
8 files changed, 115 insertions(+), 4 deletions(-)
diff --git a/arch/m68k/68000/entry.S b/arch/m68k/68000/entry.S
index 72e95663b62ffd54..1f79deb91da4dcbb 100644
--- a/arch/m68k/68000/entry.S
+++ b/arch/m68k/68000/entry.S
@@ -87,6 +87,21 @@ ret_from_exception:
btst #5,%sp@(PT_OFF_SR) /* check if returning to kernel*/
jeq Luser_return /* if so, skip resched, signals*/
+#ifdef CONFIG_PREEMPTION
+ movel %sp,%d1 /* get thread_info pointer */
+ andl #-THREAD_SIZE,%d1 /* at base of kernel stack */
+ movel %d1,%a0
+ movel %a0@(TINFO_FLAGS),%d1 /* get thread_info->flags */
+ btst #TIF_NEED_RESCHED,%d1
+ jeq Lkernel_return
+
+ movel %a0@(TINFO_PREEMPT),%d1
+ jne Lkernel_return
+
+ pea Lkernel_return
+ jmp preempt_schedule_irq /* preempt the kernel */
+#endif /* CONFIG_PREEMPTION */
+
Lkernel_return:
RESTORE_ALL
diff --git a/arch/m68k/Kconfig b/arch/m68k/Kconfig
index 99837d4e8c977485..ef350af5a3b03853 100644
--- a/arch/m68k/Kconfig
+++ b/arch/m68k/Kconfig
@@ -11,7 +11,6 @@ config M68K
select ARCH_HAS_SYNC_DMA_FOR_DEVICE if M68K_NONCOHERENT_DMA
select ARCH_HAVE_NMI_SAFE_CMPXCHG if RMW_INSNS
select ARCH_MIGHT_HAVE_PC_PARPORT if ISA
- select ARCH_NO_PREEMPT if !COLDFIRE
select ARCH_USE_MEMTEST if MMU_MOTOROLA
select ARCH_WANT_IPC_PARSE_VERSION
select BINFMT_FLAT_ARGVP_ENVP_ON_STACK
diff --git a/arch/m68k/coldfire/cache.c b/arch/m68k/coldfire/cache.c
index 98ee89b87439cdc4..592fd1d579bef348 100644
--- a/arch/m68k/coldfire/cache.c
+++ b/arch/m68k/coldfire/cache.c
@@ -10,6 +10,8 @@
/***************************************************************************/
#include <linux/kernel.h>
+#include <linux/preempt.h>
+
#include <asm/coldfire.h>
#include <asm/mcfsim.h>
@@ -25,6 +27,8 @@
void mcf_cache_push(void)
{
+ preempt_disable();
+
__asm__ __volatile__ (
"clrl %%d0\n\t"
"1:\n\t"
@@ -42,6 +46,8 @@ void mcf_cache_push(void)
"i" (DCACHE_SIZE / CACHE_WAYS),
"i" (CACHE_WAYS)
: "d0", "a0" );
+
+ preempt_enable();
}
/***************************************************************************/
diff --git a/arch/m68k/include/asm/cacheflush_mm.h b/arch/m68k/include/asm/cacheflush_mm.h
index 9a71b0148461a455..b3c77d5097aeea9e 100644
--- a/arch/m68k/include/asm/cacheflush_mm.h
+++ b/arch/m68k/include/asm/cacheflush_mm.h
@@ -3,6 +3,8 @@
#define _M68K_CACHEFLUSH_H
#include <linux/mm.h>
+#include <linux/preempt.h>
+
#ifdef CONFIG_COLDFIRE
#include <asm/mcfsim.h>
#endif
@@ -63,6 +65,8 @@ static inline void flush_cf_icache(unsigned long start, unsigned long end)
{
unsigned long set;
+ preempt_disable();
+
for (set = start; set <= end; set += (0x10 - 3)) {
__asm__ __volatile__ (
"cpushl %%ic,(%0)\n\t"
@@ -75,12 +79,16 @@ static inline void flush_cf_icache(unsigned long start, unsigned long end)
: "=a" (set)
: "a" (set));
}
+
+ preempt_enable();
}
static inline void flush_cf_dcache(unsigned long start, unsigned long end)
{
unsigned long set;
+ preempt_disable();
+
for (set = start; set <= end; set += (0x10 - 3)) {
__asm__ __volatile__ (
"cpushl %%dc,(%0)\n\t"
@@ -93,12 +101,16 @@ static inline void flush_cf_dcache(unsigned long start, unsigned long end)
: "=a" (set)
: "a" (set));
}
+
+ preempt_enable();
}
static inline void flush_cf_bcache(unsigned long start, unsigned long end)
{
unsigned long set;
+ preempt_disable();
+
for (set = start; set <= end; set += (0x10 - 3)) {
__asm__ __volatile__ (
"cpushl %%bc,(%0)\n\t"
@@ -111,6 +123,8 @@ static inline void flush_cf_bcache(unsigned long start, unsigned long end)
: "=a" (set)
: "a" (set));
}
+
+ preempt_enable();
}
/*
@@ -228,14 +242,21 @@ static inline void __flush_pages_to_ram(void *vaddr, unsigned int nr)
addr = ((unsigned long) vaddr) & ~(PAGE_SIZE - 1);
start = addr & ICACHE_SET_MASK;
end = (addr + nr * PAGE_SIZE - 1) & ICACHE_SET_MASK;
+
+ preempt_disable();
+
if (start > end) {
flush_cf_bcache(0, end);
end = ICACHE_MAX_ADDR;
}
flush_cf_bcache(start, end);
+
+ preempt_enable();
} else if (CPU_IS_040_OR_060) {
unsigned long paddr = __pa(vaddr);
+ preempt_disable();
+
do {
__asm__ __volatile__("nop\n\t"
".chip 68040\n\t"
@@ -244,6 +265,8 @@ static inline void __flush_pages_to_ram(void *vaddr, unsigned int nr)
: : "a" (paddr));
paddr += PAGE_SIZE;
} while (--nr);
+
+ preempt_enable();
} else {
unsigned long _tmp;
__asm__ __volatile__("movec %%cacr,%0\n\t"
diff --git a/arch/m68k/include/asm/tlbflush.h b/arch/m68k/include/asm/tlbflush.h
index 6d42e2906887931b..f95a8e9c9912a3e8 100644
--- a/arch/m68k/include/asm/tlbflush.h
+++ b/arch/m68k/include/asm/tlbflush.h
@@ -101,6 +101,7 @@ static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end
#else
+#include <linux/preempt.h>
/* Reserved PMEGs. */
extern char sun3_reserved_pmeg[SUN3_PMEGS_NUM];
@@ -115,6 +116,8 @@ static inline void flush_tlb_all(void)
unsigned long addr;
unsigned char ctx, oldctx;
+ preempt_disable();
+
oldctx = sun3_get_context();
for (addr = 0x00000000; addr < TASK_SIZE; addr += SUN3_PMEG_SIZE) {
for (ctx = 0; ctx < 8; ctx++) {
@@ -133,6 +136,8 @@ static inline void flush_tlb_all(void)
pmeg_vaddr[addr] = 0;
}
}
+
+ preempt_enable();
}
/* Clear user TLB entries within the context named in mm */
@@ -142,6 +147,8 @@ static inline void flush_tlb_mm (struct mm_struct *mm)
unsigned char seg;
unsigned long i;
+ preempt_disable();
+
oldctx = sun3_get_context();
sun3_put_context(mm->context);
@@ -157,6 +164,8 @@ static inline void flush_tlb_mm (struct mm_struct *mm)
}
sun3_put_context(oldctx);
+
+ preempt_enable();
}
/* Flush a single TLB page. In this case, we're limited to flushing a
@@ -167,6 +176,8 @@ static inline void flush_tlb_page (struct vm_area_struct *vma,
unsigned char oldctx;
unsigned char i;
+ preempt_disable();
+
oldctx = sun3_get_context();
sun3_put_context(vma->vm_mm->context);
addr &= ~SUN3_PMEG_MASK;
@@ -179,6 +190,7 @@ static inline void flush_tlb_page (struct vm_area_struct *vma,
}
sun3_put_context(oldctx);
+ preempt_enable();
}
/* Flush a range of pages from TLB. */
@@ -190,6 +202,8 @@ static inline void flush_tlb_range (struct vm_area_struct *vma,
start &= ~SUN3_PMEG_MASK;
+ preempt_disable();
+
oldctx = sun3_get_context();
sun3_put_context(mm->context);
@@ -207,6 +221,8 @@ static inline void flush_tlb_range (struct vm_area_struct *vma,
start += SUN3_PMEG_SIZE;
}
sun3_put_context(oldctx);
+
+ preempt_enable();
}
static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end)
diff --git a/arch/m68k/kernel/entry.S b/arch/m68k/kernel/entry.S
index 3bcdd32a6b36613d..9933679ea28b44ab 100644
--- a/arch/m68k/kernel/entry.S
+++ b/arch/m68k/kernel/entry.S
@@ -241,7 +241,23 @@ syscall_exit_work:
ENTRY(ret_from_exception)
.Lret_from_exception:
btst #5,%sp@(PT_OFF_SR) | check if returning to kernel
+#ifdef CONFIG_PREEMPTION
+ jeq 2f
+
+ movel %curptr@(TASK_STACK),%a0
+ tstb %a0@(TINFO_FLAGS+3) | check TIF_NEED_RESCHED
+ jpl 1f
+
+ movel %a0@(TINFO_PREEMPT),%d1
+ jne 1f
+
+ pea 1f
+ jmp preempt_schedule_irq | preempt the kernel
+#else /* !CONFIG_PREEMPTION */
bnes 1f | if so, skip resched, signals
+#endif /* !CONFIG_PREEMPTION */
+
+2:
| only allow interrupts when we are really the last one on the
| kernel stack, otherwise stack overflow can occur during
| heavy interrupt load
diff --git a/arch/m68k/mm/cache.c b/arch/m68k/mm/cache.c
index dde978e66f14fb31..d23958bc2a815ea7 100644
--- a/arch/m68k/mm/cache.c
+++ b/arch/m68k/mm/cache.c
@@ -8,6 +8,8 @@
*/
#include <linux/module.h>
+#include <linux/preempt.h>
+
#include <asm/cacheflush.h>
#include <asm/traps.h>
@@ -62,14 +64,21 @@ void flush_icache_user_range(unsigned long address, unsigned long endaddr)
unsigned long start, end;
start = address & ICACHE_SET_MASK;
end = endaddr & ICACHE_SET_MASK;
+
+ preempt_disable();
+
if (start > end) {
flush_cf_icache(0, end);
end = ICACHE_MAX_ADDR;
}
flush_cf_icache(start, end);
+
+ preempt_enable();
} else if (CPU_IS_040_OR_060) {
address &= PAGE_MASK;
+ preempt_disable();
+
do {
asm volatile ("nop\n\t"
".chip 68040\n\t"
@@ -78,6 +87,8 @@ void flush_icache_user_range(unsigned long address, unsigned long endaddr)
: : "a" (virt_to_phys_slow(address)));
address += PAGE_SIZE;
} while (address < endaddr);
+
+ preempt_enable();
} else {
unsigned long tmp;
asm volatile ("movec %%cacr,%0\n\t"
@@ -103,12 +114,16 @@ void flush_icache_user_page(struct vm_area_struct *vma, struct page *page,
unsigned long start, end;
start = addr & ICACHE_SET_MASK;
end = (addr + len) & ICACHE_SET_MASK;
+
+ preempt_disable();
+
if (start > end) {
flush_cf_icache(0, end);
end = ICACHE_MAX_ADDR;
}
flush_cf_icache(start, end);
+ preempt_enable();
} else if (CPU_IS_040_OR_060) {
asm volatile ("nop\n\t"
".chip 68040\n\t"
diff --git a/arch/m68k/mm/motorola.c b/arch/m68k/mm/motorola.c
index c1761d309fc6128c..81ce42b062e78839 100644
--- a/arch/m68k/mm/motorola.c
+++ b/arch/m68k/mm/motorola.c
@@ -15,6 +15,7 @@
#include <linux/mm.h>
#include <linux/swap.h>
#include <linux/kernel.h>
+#include <linux/preempt.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/init.h>
@@ -139,10 +140,13 @@ void __init init_pointer_table(void *table, int type)
void *get_pointer_table(int type)
{
- ptable_desc *dp = ptable_list[type].next;
- unsigned int mask = list_empty(&ptable_list[type]) ? 0 : PD_MARKBITS(dp);
- unsigned int tmp, off;
+ unsigned int mask, tmp, off;
+ ptable_desc *dp;
+
+ preempt_disable();
+ dp = ptable_list[type].next;
+ mask = list_empty(&ptable_list[type]) ? 0 : PD_MARKBITS(dp);
/*
* For a pointer table for a user process address space, a
* table is taken from a page allocated for the purpose. Each
@@ -153,9 +157,13 @@ void *get_pointer_table(int type)
void *page;
ptable_desc *new;
+ preempt_enable_no_resched();
+
if (!(page = (void *)get_zeroed_page(GFP_KERNEL)))
return NULL;
+ preempt_disable();
+
if (type == TABLE_PTE) {
/*
* m68k doesn't have SPLIT_PTE_PTLOCKS for not having
@@ -170,6 +178,8 @@ void *get_pointer_table(int type)
PD_MARKBITS(new) = ptable_mask(type) - 1;
list_add_tail(new, dp);
+ preempt_enable_no_resched();
+
return (pmd_t *)page;
}
@@ -180,6 +190,9 @@ void *get_pointer_table(int type)
/* move to end of list */
list_move_tail(dp, &ptable_list[type]);
}
+
+ preempt_enable_no_resched();
+
return page_address(PD_PAGE(dp)) + off;
}
@@ -190,6 +203,8 @@ int free_pointer_table(void *table, int type)
unsigned long page = ptable & PAGE_MASK;
unsigned int mask = 1U << ((ptable - page)/ptable_size(type));
+ preempt_disable();
+
dp = PD_PTABLE(page);
if (PD_MARKBITS (dp) & mask)
panic ("table already free!");
@@ -203,6 +218,9 @@ int free_pointer_table(void *table, int type)
if (type == TABLE_PTE)
pagetable_pte_dtor(virt_to_ptdesc((void *)page));
free_page (page);
+
+ preempt_enable_no_resched();
+
return 1;
} else if (ptable_list[type].next != dp) {
/*
@@ -211,6 +229,9 @@ int free_pointer_table(void *table, int type)
*/
list_move(dp, &ptable_list[type]);
}
+
+ preempt_enable_no_resched();
+
return 0;
}
--
2.34.1