I want to write a function in my kernel module to invalidate the physical page mapping of a virtual address. However, I don't know how to flush the tlb entry of the page of the virtual address. I try to use "flush_tlb_range" in my module code. However, it reports "implicit declaration of function ‘flush_tlb_range’".
Here is a snippet in my code
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/tlb.h>
#include <asm/tlbflush.h>
u64 mypool_get_page(u64 gfn, bool write_fault, bool *writable){
u64 kvm_hva;
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma = current->mm->mmap;
unsigned int level;
pte_t *ptep;
kvm_hva = map_lookup(index);
....
//invalid kvm_hva in page table
ptep = lookup_address(kvm_hva, &level);
if (ptep) {
pte_clear(mm, kvm_hva, ptep);
flush_tlb_range(vma,kvm_hva, kvm_hva+PAGE_SIZE);
}
...
}
Here is my Makefile
mypool-objs := pool-main.o maptable.o
obj-m += mypool.o
KERNEL := /lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
modules :
$(MAKE) -C $(KERNEL) M=$(PWD) modules
.PHONEY:clean
clean :
rm -rf *.o *.ko
Can someone teach me how to invalidate tlb in kernel modules?
I also tried to use ‘flush_tlb_mm_range’. However, it also reports implicit declaration of function ‘flush_tlb_mm_range’.
There are multiple ways to achieve the goal depending on what you want to achieve.
If you are on x86 you can flush the TLB entry of the currently running CPU with
asm volatile("invlpg (%0)" ::"r" (addr) : "memory");
However, this does not ensure coherency between the remaining cores of the system. You would need to perform a TLBSHOOTDOWN and manually do the IPI's to sync the TLB. For other architectures, such as ARM64 this is not required.
Change the Linux kernel and export the symbol of the function you want to use. This is a pretty dirty way and should not be done in production, but if it is just for testing this should be sufficient.
EXPORT_SYMBOL(flush_tlb_range)
Use kallsyms_lookup_name
to find the address of the function. Here is an example kernel module. Note that there is a problem with IBT enabled and AFAIK there is currently no solution to this problem stackoverflow-discussion.
#include <asm/io.h>
#include <asm/tlbflush.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/ptrace.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
#define KPROBE_KALLSYMS_LOOKUP 1
typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
kallsyms_lookup_name_t kallsyms_lookup_name_func;
#define kallsyms_lookup_name kallsyms_lookup_name_func
static struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name"
};
#endif
void (*flush_tlb_mm_range_func)(struct mm_struct *, unsigned long, unsigned long, unsigned int, bool);
/*
* Init function of our module
*/
static int __init init_custom(void) {
/*
* Dirty workaround to get kallsyms_lookup_name address, since it is not exported anymore
*/
register_kprobe(&kp);
#ifdef CONFIG_X86_KERNEL_IBT
printk("IBT IS ENABLED\n");
return -ENXIO;
#else
printk("IBT IS DISABLED\n");
kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;
#endif
printk("kallsyms_lookup_name address: %px\n", kallsyms_lookup_name);
unregister_kprobe(&kp);
if(!unlikely(kallsyms_lookup_name)) {
pr_alert("Could not retrieve kallsyms_lookup_name address\n");
return -ENXIO;
}
// get flush_tlb_mm_range address
flush_tlb_mm_range_func = (void *)kallsyms_lookup_name("flush_tlb_mm_range");
printk("flush_tlb_mm_range address: %p\n", flush_tlb_mm_range_func);
if (!flush_tlb_mm_range_func) {
pr_alert("Could not retrieve flush_tlb_mm_range function\n");
return -ENXIO;
}
return 0;
}
/*
* Exit function of our module.
*/
static void __exit exit_custom(void) {
printk(KERN_INFO "Hasta la vista, Kernel!\n");
}
MODULE_DESCRIPTION("WIP");
MODULE_LICENSE("GPL");
module_init(init_custom);
module_exit(exit_custom);