diff --git a/arch/sparc/Kbuild b/arch/sparc/Kbuild index ae589c82c947839070a2580a6a2bf4806562b33b..082d329d32457042a5519ea71ba36ef3b1b579b6 100644 --- a/arch/sparc/Kbuild +++ b/arch/sparc/Kbuild @@ -1 +1,2 @@ obj-y += kernel/ +obj-y += mm/ diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig index 9a0a4aa017699bacd0400439d2e3d6db8b92fd25..5371255217e7ed8ff1cab5e1a26bf20bf98ba261 100644 --- a/arch/sparc/Kconfig +++ b/arch/sparc/Kconfig @@ -6,8 +6,9 @@ config PAGE_OFFSET bool "Use an offset in physical/virtual page address conversion" default n help - Use a fixed offset when computing the virtual and physical page - addresses. + Use an offset when computing the virtual and physical page + addresses. This can not work unless the kernel is bootstrapped. + If unsure, say N. config EXTRA_SPARC_PHYS_BANKS int "Number of extra physical memory banks" @@ -19,6 +20,7 @@ config EXTRA_SPARC_PHYS_BANKS menu "Memory Management Settings" depends on MM + depends on CHUNK config SPARC_MM_BLOCK_ORDER_MAX int "Initial memory block order upper limit" @@ -50,6 +52,31 @@ config SPARC_INIT_PAGE_MAP_MAX_ENTRIES Configures the storage space for the initial page map. It's wise to say at least EXTRA_SPARC_PHYS_BANKS here. +config SPARC_BOOTMEM_RESERVE_CHUNK_ORDER + int "Memory block order to reserve for boot memory allocator" + depends on MM + depends on CHUNK + default 20 + range 12 SPARC_MM_BLOCK_ORDER_MAX + help + The order (i.e. 2^N bytes) of memory to reserve for the boot memory + allocator in a single request to the higher-tier memory manager. + The boot memory allocator is used by low level functionality, such as + the mangement of MMU translation tables, so choose a large enough + number. The default (20, i.e. 1 MiB) is a good starting point. + Allowed order range is 12 (SPARC page size) to + SPARC_MM_BLOCK_ORDER_MAX. + +config SPARC_BOOTMEM_REQUEST_NEW_ON_DEMAND + bool "Allow the boot memory allocator to request new memory blocks" + depends on MM + depends on CHUNK + default y + help + Allow the boot memory allocator to request new blocks of size + SPARC_BOOTMEM_RESERVE_CHUNK_ORDER from the higher-tier memory manager. + Beware that this will potentially lead to greater memory fragmentation. + If unsure, say Y. endmenu diff --git a/arch/sparc/include/asm/io.h b/arch/sparc/include/asm/io.h new file mode 100644 index 0000000000000000000000000000000000000000..f4afa6edaf5816ff22ad0a5793ca0f209f462865 --- /dev/null +++ b/arch/sparc/include/asm/io.h @@ -0,0 +1,117 @@ +/** + * @file sparc/include/asm/io.h + * + * @copyright GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * @brief a collection of accessor functions and macros to perform unbuffered + * access to memory or hardware registers + */ + +#ifndef _ARCH_SPARC_ASM_IO_H_ +#define _ARCH_SPARC_ASM_IO_H_ + +#include <stdint.h> +#include <uapi/asi.h> + +/* + * convention/calls same as in linux kernel (see arch/sparc/include/asm/io_32.h) + */ + + + +#define __raw_readb __raw_readb +static inline uint8_t __raw_readb(const volatile void *addr) +{ + uint8_t ret; + + __asm__ __volatile__("lduba [%1] %2, %0\n\t" + : "=r" (ret) + : "r" (addr), "i" (ASI_LEON_NOCACHE)); + + return ret; +} + +#define __raw_readw __raw_readw +static inline uint16_t __raw_readw(const volatile void *addr) +{ + uint16_t ret; + + __asm__ __volatile__("lduha [%1] %2, %0\n\t" + : "=r" (ret) + : "r" (addr), "i" (ASI_LEON_NOCACHE)); + + return ret; +} + + +#define __raw_readl __raw_readl +static inline uint32_t __raw_readl(const volatile void *addr) +{ + uint32_t ret; + + __asm__ __volatile__("lda [%1] %2, %0\n\t" + : "=r" (ret) + : "r" (addr), "i" (ASI_LEON_NOCACHE)); + + return ret; +} + +#define __raw_writeb __raw_writeb +static inline void __raw_writeb(uint8_t w, const volatile void *addr) +{ + __asm__ __volatile__("stba %r0, [%1] %2\n\t" + : + : "Jr" (w), "r" (addr), "i" (ASI_LEON_NOCACHE)); +} + +#define __raw_writew __raw_writew +static inline void __raw_writew(uint16_t w, const volatile void *addr) +{ + __asm__ __volatile__("stha %r0, [%1] %2\n\t" + : + : "Jr" (w), "r" (addr), "i" (ASI_LEON_NOCACHE)); +} + + +#define __raw_writel __raw_writel +static inline void __raw_writel(uint32_t l, const volatile void *addr) +{ + __asm__ __volatile__("sta %r0, [%1] %2\n\t" + : + : "Jr" (l), "r" (addr), "i" (ASI_LEON_NOCACHE)); +} + +#ifndef ioread8 +#define ioread8(X) __raw_read8(X) +#endif + +#ifndef iowrite8 +#define iowrite8(X) __raw_write8(X) +#endif + +#ifndef ioread16be +#define ioread16be(X) __raw_readw(X) +#endif + +#ifndef ioread32be +#define ioread32be(X) __raw_readl(X) +#endif + +#ifndef iowrite16be +#define iowrite16be(val,X) __raw_writew(val,X) +#endif + +#ifndef iowrite32be +#define iowrite32be(val,X) __raw_writel(val,X) +#endif + +#endif /* _ARCH_SPARC_ASM_IO_H_ */ diff --git a/arch/sparc/include/asm/leon.h b/arch/sparc/include/asm/leon.h new file mode 100644 index 0000000000000000000000000000000000000000..8737d4137ffed6098fa12c9c2d4ed83e1b40e85f --- /dev/null +++ b/arch/sparc/include/asm/leon.h @@ -0,0 +1,205 @@ +/** + * @file asm/leon.h + * + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + * @date 2015 + * + * @copyright GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * @brief assembly functions for the leon3 target + * + */ + +#ifndef _SPARC_ASM_LEON_H_ +#define _SPARC_ASM_LEON_H_ + + + +#define ASI_LEON3_SYSCTRL 0x02 + +#define ASI_LEON3_SYSCTRL_CCR 0x00 +#define ASI_LEON3_SYSCTRL_ICFG 0x08 +#define ASI_LEON3_SYSCTRL_DCFG 0x0c + + + +__attribute__((unused)) +static inline unsigned long leon3_asr17(void) +{ + unsigned long asr17; + + __asm__ __volatile__ ( + "rd %%asr17, %0 \n\t" + :"=r" (asr17) + ); + + return asr17; +} + +__attribute__((unused)) +static inline unsigned long leon3_cpuid(void) +{ + unsigned long cpuid; + + __asm__ __volatile__ ( + "rd %%asr17, %0 \n\t" + "srl %0, 28, %0 \n\t" + :"=r" (cpuid) + : + :"l1"); + return cpuid; +} + +__attribute__((unused)) +static inline void leon3_powerdown_safe(unsigned long phys_memaddr) +{ + __asm__ __volatile__( + "wr %%g0, %%asr19 \n\t" + "lda [%0] 0x1c, %%g0 \n\t" + : + :"r" (phys_memaddr) + :"memory"); +} + +__attribute__((unused)) +static inline void leon3_flush(void) +{ + __asm__ __volatile__( + "flush \n\t" + "set 0x81000f, %%g1 \n\t" + "sta %%g1, [%0] %1 \n\t" + : + : "r" (ASI_LEON3_SYSCTRL_CCR), + "i" (ASI_LEON3_SYSCTRL) + : "g1"); +} + +__attribute__((unused)) +static inline void leon3_enable_icache(void) +{ + __asm__ __volatile__( + "lda [%0] %1, %%l1 \n\t" + "set 0x3, %%l2 \n\t" + "or %%l2, %%l1, %%l2 \n\t" + "sta %%l2, [%0] %1 \n\t" + : + : "r" (ASI_LEON3_SYSCTRL_CCR), + "i" (ASI_LEON3_SYSCTRL) + : "l1", "l2"); +} + +__attribute__((unused)) +static inline void leon3_enable_dcache(void) +{ + __asm__ __volatile__( + "lda [%0] %1, %%l1 \n\t" + "set 0xc, %%l2 \n\t" + "or %%l2, %%l1, %%l2 \n\t" + "sta %%l2, [%0] %1 \n\t" + : + : "r" (ASI_LEON3_SYSCTRL_CCR), + "i" (ASI_LEON3_SYSCTRL) + : "l1", "l2"); +} + + +__attribute__((unused)) +static inline void leon3_enable_snooping(void) +{ + __asm__ __volatile__( + "lda [%0] %1, %%l1 \n\t" + "set 0x800000, %%l2 \n\t" + "or %%l2, %%l1, %%l2 \n\t" + "sta %%l2, [%0] %1 \n\t" + : + : "r" (ASI_LEON3_SYSCTRL_CCR), + "i" (ASI_LEON3_SYSCTRL) + : "l1", "l2"); +} + +__attribute__((unused)) +static inline void leon3_enable_fault_tolerant(void) +{ + __asm__ __volatile__( + "lda [%0] %1, %%l1 \n\t" + "set 0x80000, %%l2 \n\t" + "or %%l2, %%l1, %%l2 \n\t" + "sta %%l2, [%0] %1 \n\t" + : + : "r" (ASI_LEON3_SYSCTRL_CCR), + "i" (ASI_LEON3_SYSCTRL) + : "l1", "l2"); +} + + + + +__attribute__((unused)) +static inline void leon_set_sp(unsigned long stack_addr) +{ + __asm__ __volatile__( + "mov %0, %%sp\n\t" + : + :"r"(stack_addr) + :"memory"); +} + +__attribute__((unused)) +static inline void leon_set_fp(unsigned long stack_addr) +{ + __asm__ __volatile__( + "mov %0, %%fp\n\t" + : + :"r" (stack_addr) + :"memory"); +} + + +__attribute__((unused)) +static inline unsigned long leon_get_sp(void) +{ + unsigned long sp; + + __asm__ __volatile__ ( + "mov %%sp, %0 \n\t" + :"=r" (sp) + ); + + return sp; +} + + +__attribute__((unused)) +static inline unsigned long leon_get_fp(void) +{ + unsigned long fp; + + __asm__ __volatile__ ( + "mov %%fp, %0 \n\t" + :"=r" (fp) + ); + + return fp; +} + + +/* XXX need to make sure this trap is installed */ +__attribute__((unused)) +static inline void leon_reg_win_flush(void) +{ + __asm__ __volatile__("ta 3"); +} + + + + +#endif /* _SPARC_ASM_LEON_H_ */ diff --git a/arch/sparc/include/cpu_type.h b/arch/sparc/include/cpu_type.h new file mode 100644 index 0000000000000000000000000000000000000000..9e963b7cb94d2bf430309d7bc483c74ba6a05f5a --- /dev/null +++ b/arch/sparc/include/cpu_type.h @@ -0,0 +1,21 @@ +/** + * @brief supported CPU models + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + */ + +#ifndef _ASM_CPU_TYPE_H_ +#define _ASM_CPU_TYPE_H_ + + +/* + * supported CPU models + */ + +enum sparc_cpus { + sparc_leon = 0x06, +}; + +extern enum sparc_cpus sparc_cpu_model; + + +#endif /* _ASM_CPU_TYPE_H_ */ diff --git a/arch/sparc/include/init.h b/arch/sparc/include/init.h index 41383638ce4a7191bf0f2ddc1295b7993213cf49..4f64dd825322bb5c539460b741372429bded5a10 100644 --- a/arch/sparc/include/init.h +++ b/arch/sparc/include/init.h @@ -2,8 +2,16 @@ * @file arch/sparc/include/init.h */ -#ifndef _SPARC_INIT_H_ -#define _SPARC_INIT_H_ +#ifndef _SPARC_INIT_H_ +#define _SPARC_INIT_H_ + +#if defined(CONFIG_KERNEL_STACK_PAGES) +#define KERNEL_STACK_PAGES CONFIG_KERNEL_STACK_PAGES +#else +#define KERNEL_STACK_PAGES 8 +#endif + + void paging_init(void); diff --git a/arch/sparc/include/mm.h b/arch/sparc/include/mm.h index 1fa7d116e2ec48bdcd0e7e92bfea57fd8c510f36..5cac7dbc2ccfedc1610552a3eb144d95bbcaae84 100644 --- a/arch/sparc/include/mm.h +++ b/arch/sparc/include/mm.h @@ -72,6 +72,18 @@ extern struct list_head mm_init_block_order[MM_BLOCK_ORDER_MAX + 1]; +#define BOOTMEM_CHUNKSIZE (1 << CONFIG_SPARC_BOOTMEM_RESERVE_CHUNK_ORDER) + + void bootmem_init(void); +void *bootmem_alloc(size_t size); +void bootmem_free(void *ptr); + +void mm_mmu_paging_init(void); + +void mm_mmu_trap(void); + +int mm_set_mmu_ctx(unsigned long ctx); +unsigned long mm_get_mmu_ctx(void); #endif /*_SPARC_MM_H_ */ diff --git a/arch/sparc/include/page.h b/arch/sparc/include/page.h index 255bff4e2d4de847c98178d18c27975c952f94a6..f52411502cbf59fce35a7f3e88edc2be51a51ec9 100644 --- a/arch/sparc/include/page.h +++ b/arch/sparc/include/page.h @@ -20,14 +20,41 @@ /* align address to the (next) page boundary */ #define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE) +#define PAGE_ALIGN_PTR(addr) PTR_ALIGN(addr, PAGE_SIZE) #define PFN_PHYS(x) ((unsigned long)((x) << PAGE_SHIFT)) -#define PHYS_PFN(x) ((unsigned long)((x) >> PAGE_SHIFT)) +#define PHYS_PFN(x)((unsigned long)((x) >> PAGE_SHIFT)) -#if defined(CONFIG_PAGE_OFFSET) -#define PAGE_OFFSET 0xf0000000 -#endif + +/** + * Reserved memory ranges used in mappings: + * + * - everything above HIGHMEM_START is 1:1 mapped through the MMU and marks + * the per-thread allocatable memory. + * - LOWMEM_RESERVED is the reserved lower address range + * - VMALLOC_{START, END} define the boundaries of mapped virtual pages + * + * TODO: make this configurable via make config + * The values are selected such that: + * + * 1. the lowest 16M are reserved + * 2. highmem starts at 0x40000000, this is typically the lowest (RAM) + * address in use on a LEON platform (usually internal static RAM). + * 3. the above boundaries are selected under the condition that the + * kernel is not bootstrapped, i.e. an initial MMU context is not set + * up by a stage 1 loader, which then copies and starts the kernel + * from some location. + */ + +#define HIGHMEM_START 0x40000000 +#define LOWMEM_RESERVED 0x01000000 + +#define VMALLOC_START (LOWMEM_RESERVED) +#define VMALLOC_END (HIGHMEM_START - 1) + + +#define PAGE_OFFSET HIGHMEM_START extern unsigned long phys_base; extern unsigned long pfn_base; @@ -44,7 +71,7 @@ extern unsigned long pfn_base; #if defined (CONFIG_SPARC_INIT_PAGE_MAP_MAX_ENTRIES) #define INIT_PAGE_MAP_MAX_ENTRIES CONFIG_SPARC_INIT_PAGE_MAP_MAX_ENTRIES #else -#define INIT_PAGE_MAP_MAX_ENTRIES 1 +#define INIT_PAGE_MAP_MAX_ENTRIES 0 #endif extern struct mm_pool mm_init_page_pool; diff --git a/arch/sparc/include/srmmu.h b/arch/sparc/include/srmmu.h new file mode 100644 index 0000000000000000000000000000000000000000..32683574a2ba060ccb99cb13934f0a7667923977 --- /dev/null +++ b/arch/sparc/include/srmmu.h @@ -0,0 +1,245 @@ +/** + * @brief SPARC Reference (SR) Memory Management Unit (MMU) + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + * + * @see SPARCv8 Architecture Manual for more info + */ + +#ifndef _SPARC_SRMMU_H_ +#define _SPARC_SRMMU_H_ + + +#include <stddef.h> + + +/* XXX: not here */ +#define __BIG_ENDIAN_BITFIELD __BIG_ENDIAN_BITFIELD + + +#define SRMMU_CTRL_IMPL_MASK 0xf0000000 +#define SRMMU_CTRL_VER_MASK 0x0f000000 + +#define SRMMU_CTRL_IMPL_SHIFT 28 +#define SRMMU_CTRL_VER_SHIFT 24 + + +#define SRMMU_CONTEXTS 256 +#define SRMMU_SIZE_TBL_LVL_1 256 +#define SRMMU_SIZE_TBL_LVL_2 64 +#define SRMMU_SIZE_TBL_LVL_3 64 +#define SRMMU_ENTRY_SIZE_BYTES 4 + +/* SRMMU page tables must be aligned to their size */ +#define SRMMU_TBL_LVL_1_ALIGN (SRMMU_SIZE_TBL_LVL_1 * SRMMU_ENTRY_SIZE_BYTES) +#define SRMMU_TBL_LVL_2_ALIGN (SRMMU_SIZE_TBL_LVL_2 * SRMMU_ENTRY_SIZE_BYTES) +#define SRMMU_TBL_LVL_3_ALIGN (SRMMU_SIZE_TBL_LVL_3 * SRMMU_ENTRY_SIZE_BYTES) + +#define SRMMU_SMALL_PAGE_SIZE PAGE_SIZE +#define SRMMU_MEDIUM_PAGE_SIZE (SRMMU_SMALL_PAGE_SIZE * SRMMU_SIZE_TBL_LVL_3) +#define SRMMU_LARGE_PAGE_SIZE (SRMMU_MEDIUM_PAGE_SIZE * SRMMU_SIZE_TBL_LVL_2) + + +/* full 32 bit range divided by 256 lvl1 contexts: + * upper 8 bits == 16 MiB pages + * + * a 16 MiB page divided by 64 lvl2 contexts: + * next 6 bits = 256 kiB pages + * + * a 256 kiB page divided by 64 lvl3 contexts: + * next 6 bits = 4 kiB pages + * + * the last 12 bits are the offset within a 4096 kiB page + */ +#define SRMMU_TBL_LVL_1_SHIFT 24 +#define SRMMU_TBL_LVL_1_MASK 0xff +#define SRMMU_TBL_LVL_2_SHIFT 18 +#define SRMMU_TBL_LVL_2_MASK 0x3f +#define SRMMU_TBL_LVL_3_SHIFT 12 +#define SRMMU_TBL_LVL_3_MASK 0x3f + +/* offsets into the different level tables */ +#define SRMMU_LVL1_GET_TBL_OFFSET(addr) ((addr >> SRMMU_TBL_LVL_1_SHIFT) \ + & SRMMU_TBL_LVL_1_MASK) +#define SRMMU_LVL2_GET_TBL_OFFSET(addr) ((addr >> SRMMU_TBL_LVL_2_SHIFT) \ + & SRMMU_TBL_LVL_2_MASK) +#define SRMMU_LVL3_GET_TBL_OFFSET(addr) ((addr >> SRMMU_TBL_LVL_3_SHIFT) \ + & SRMMU_TBL_LVL_3_MASK) + + +#define SRMMU_ENTRY_TYPE_INVALID 0x0 +#define SRMMU_ENTRY_TYPE_PT_DESC 0x1 +#define SRMMU_ENTRY_TYPE_PT_ENTRY 0x2 +#define SRMMU_ENTRY_TYPE_RESERVED 0x3 +#define SRMMU_ENTRY_TYPE_MASK 0x3 + +#define SRMMU_PTE_FLAGS_MASK 0xff + +#define SRMMU_CACHEABLE 0x80 + +#define SRMMMU_ACC_SHIFT 2 + +/* user access permissions, ASI 0x8 or 0xa */ +#define SRMMU_ACC_U_R_1 (0x0 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_U_RW (0x1 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_U_RX (0x2 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_U_RWX (0x3 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_U_X (0x4 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_U_R_2 (0x5 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_U_NO_ACCESS_1 (0x6 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_U_NO_ACCESS_2 (0x7 << SRMMMU_ACC_SHIFT) + +/* supervisor access permissions, ASI 0x9 or 0xb */ +#define SRMMU_ACC_S_R (0x0 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_S_RW_1 (0x1 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_S_RX_1 (0x2 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_S_RWX_1 (0x3 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_S_X (0x4 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_S_RW_2 (0x5 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_S_RX_2 (0x6 << SRMMMU_ACC_SHIFT) +#define SRMMU_ACC_S_RWX_2 (0x7 << SRMMMU_ACC_SHIFT) + + +/** + * The physical address is 36 bits long and composed of a page offset that is + * 12 bits wide and the physical page number, that is 24 bits wide + * The virtual address is composed of the virtual page number, which is 20 + * bits wide and the page offset that is the same as in the physical address. + * The 4 extra bits are used to map 4 GB sections. Any address that is used + * to set up the page table entry/descriptor address is hence shifted right + * by 4 bits for the correct representation of the physical page number + */ +#define SRMMU_PTD(addr) (((addr >> 4) & ~SRMMU_ENTRY_TYPE_MASK) | \ + SRMMU_ENTRY_TYPE_PT_DESC) + +#define SRMMU_PTE(addr, flags) (((addr >> 4) & ~SRMMU_PTE_FLAGS_MASK) | \ + SRMMU_ENTRY_TYPE_PT_ENTRY | flags) + +#define SRMMU_PTD_TO_ADDR(ptd) ((ptd & ~SRMMU_ENTRY_TYPE_MASK) << 4) + +#define SRMMU_PTE_TO_ADDR(pte) ((pte & ~(SRMMU_PTE_FLAGS_MASK)) << 4) + +/** + * a SRMMU page table descriptor/entry + * the page table pointer must be aligned to a boundary equal to the size of + * the next level page table, i.e. + * SRMMU_SIZE_TBL_LVL_1 * SRMMU_ENTRY_SIZE_BYTES for level 1, + * SRMMU_SIZE_TBL_LVL_2 * SRMMU_ENTRY_SIZE_BYTES for level 2, + * SRMMU_SIZE_TBL_LVL_3 * SRMMU_ENTRY_SIZE_BYTES for level 3. + * + * The entry type field may be set to INVALID, PT_DESC, PT_ENTRY or RESERVED + * and determines whether the MMU treats the remaining bits as page table + * descriptor or entry + * + * @note the cacheable flag should be 0 for all mapped i/o locations + */ +__extension__ +struct srmmu_ptde { + union { +#if defined(__BIG_ENDIAN_BITFIELD) + struct { + unsigned long page_table_pointer:30; + unsigned long entry_type : 2; + }; + + struct { + unsigned long physical_page_number :24; + unsigned long cacheable : 1; + unsigned long modified : 1; + unsigned long referenced : 1; + unsigned long access_permissions : 3; + unsigned long entry_type : 2; + }; +#endif + unsigned long ptd; + unsigned long pte; + }; +}; + + +/* page table level the fault occured in */ +#define SRMMU_FLT_L_CTX 0 +#define SRMMU_FLT_L_LVL1 1 +#define SRMMU_FLT_L_LVL2 2 +#define SRMMU_FLT_L_LVL3 3 + + +/* srmmu access type faults (User, Superuser, Read, Write eXecute */ +#define SRMMU_FLT_AT_R__U_DATA 0 +#define SRMMU_FLT_AT_R__S_DATA 1 +#define SRMMU_FLT_AT_RX_U_INST 2 +#define SRMMU_FLT_AT_RX_S_INST 3 +#define SRMMU_FLT_AT_W__U_DATA 4 +#define SRMMU_FLT_AT_W__S_DATA 5 +#define SRMMU_FLT_AT_W__U_INST 6 +#define SRMMU_FLT_AT_W__S_INST 7 + + +/* fault type */ +#define SRMMU_FLT_FT_NONE 0 +#define SRMMU_FLT_FT_INVALID_ADDR 1 +#define SRMMU_FLT_FT_PROTECTION 2 +#define SRMMU_FLT_FT_PRIV_VIOLATION 3 +#define SRMMU_FLT_FT_TRANSLATION 4 +#define SRMMU_FLT_FT_ACCESS_BUS 5 +#define SRMMU_FLT_FT_INTERNAL 6 + + +#define SRMMU_FLT_EBE_SHIFT 10 /* external bus error (impl. dep.) */ +#define SRMMU_FLT_L_SHIFT 8 /* page table level */ +#define SRMMU_FLT_AT_SHIFT 5 /* access type */ +#define SRMMU_FLT_FT_SHIFT 2 /* fault type */ +#define SRMMU_FLT_FAV_SHIFT 1 /* fault address valid */ +#define SRMMU_FLT_OV_SHIFT 0 /* multiple faults occured since read */ + + + +/* srmmu fault status register, see SPARCv8 manual, p256 */ +__extension__ +struct srmmu_fault_status { + union { +#if defined(__BIG_ENDIAN_BITFIELD) + struct { + unsigned long reserved:14; + unsigned long external_bus_error:8; + unsigned long page_table_level:2; + unsigned long access_type:3; + unsigned long fault_type:3; + unsigned long fault_address_vaild:1; + unsigned long overwrite:1; + }; +#endif + unsigned long status; + }; +}; + + + + +int srmmu_select_ctx(unsigned long ctx_num); +void srmmu_enable_mmu(void); + +int srmmu_init(void *(*alloc)(size_t size), void (*free)(void *addr)); + +int srmmu_do_small_mapping_range(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long num_pages, unsigned long perm); + +int srmmu_do_large_mapping_range(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long num_pages, unsigned long perm); + +int srmmu_do_small_mapping(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long perm); + + +int srmmu_do_large_mapping(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long perm); + + +void srmmu_release_pages(unsigned long ctx_num, + unsigned long va, unsigned long va_end, + void (*free_page)(void *addr)); + +#endif /*_SPARC_SRMMU_H_ */ diff --git a/arch/sparc/include/srmmu_access.h b/arch/sparc/include/srmmu_access.h new file mode 100644 index 0000000000000000000000000000000000000000..6dca38375327846380d08ed49d0bf20da2ff62ba --- /dev/null +++ b/arch/sparc/include/srmmu_access.h @@ -0,0 +1,27 @@ +/** + * @brief SPARC Reference (SR) Memory Management Unit (MMU) access functions + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + * + * @see SPARCv8 Architecture Manual for more info + */ + +#ifndef _SPARC_SRMMU_ACCESS_H_ +#define _SPARC_SRMMU_ACCESS_H_ + +unsigned int srmmu_get_mmu_ctrl(void); +struct srmmu_fault_status srmmu_get_mmu_fault_status(void); +unsigned int srmmu_get_mmu_fault_address(void); +unsigned int srmmu_get_mmu_impl(void); +unsigned int srmmu_get_mmu_ver(void); + + +void srmmu_set_ctx_tbl_addr(unsigned long addr); +void srmmu_set_ctx(unsigned int ctx); + +void leon_flush_cache_all(void); +void leon_flush_tlb_all(void); +void srmmu_set_mmureg(unsigned long regval); +unsigned int srmmu_get_mmureg(void); + + +#endif /*_SPARC_SRMMU_ACCESS_H_ */ diff --git a/arch/sparc/include/stack.h b/arch/sparc/include/stack.h new file mode 100644 index 0000000000000000000000000000000000000000..5f9c76e34654d97b4c091d8e921462af34c91991 --- /dev/null +++ b/arch/sparc/include/stack.h @@ -0,0 +1,131 @@ +#ifndef _SPARC_STACK_H_ +#define _SPARC_STACK_H_ + +#include <stdint.h> + +/* reg window offset */ +#define RW_L0 0x00 +#define RW_L1 0x04 +#define RW_L2 0x08 +#define RW_L3 0x0c +#define RW_L4 0x10 +#define RW_L5 0x14 +#define RW_L6 0x18 +#define RW_L7 0x1c +#define RW_I0 0x20 +#define RW_I1 0x24 +#define RW_I2 0x28 +#define RW_I3 0x2c +#define RW_I4 0x30 +#define RW_I5 0x34 +#define RW_I6 0x38 +#define RW_I7 0x3c + +/* stack frame offsets */ +#define SF_L0 0x00 +#define SF_L1 0x04 +#define SF_L2 0x08 +#define SF_L3 0x0c +#define SF_L4 0x10 +#define SF_L5 0x14 +#define SF_L6 0x18 +#define SF_L7 0x1c +#define SF_I0 0x20 +#define SF_I1 0x24 +#define SF_I2 0x28 +#define SF_I3 0x2c +#define SF_I4 0x30 +#define SF_I5 0x34 +#define SF_FP 0x38 +#define SF_PC 0x3c +#define SF_RETP 0x40 +#define SF_XARG0 0x44 +#define SF_XARG1 0x48 +#define SF_XARG2 0x4c +#define SF_XARG3 0x50 +#define SF_XARG4 0x54 +#define SF_XARG5 0x58 +#define SF_XXARG 0x5c + +#define UREG_G0 0 +#define UREG_G1 1 +#define UREG_G2 2 +#define UREG_G3 3 +#define UREG_G4 4 +#define UREG_G5 5 +#define UREG_G6 6 +#define UREG_G7 7 +#define UREG_I0 8 +#define UREG_I1 9 +#define UREG_I2 10 +#define UREG_I3 11 +#define UREG_I4 12 +#define UREG_I5 13 +#define UREG_I6 14 +#define UREG_I7 15 +#define UREG_FP UREG_I6 +#define UREG_RETPC UREG_I7 + +/* These for pt_regs. */ +#define PT_PSR 0x0 +#define PT_PC 0x4 +#define PT_NPC 0x8 +#define PT_Y 0xc +#define PT_G0 0x10 +#define PT_WIM PT_G0 +#define PT_G1 0x14 +#define PT_G2 0x18 +#define PT_G3 0x1c +#define PT_G4 0x20 +#define PT_G5 0x24 +#define PT_G6 0x28 +#define PT_G7 0x2c +#define PT_I0 0x30 +#define PT_I1 0x34 +#define PT_I2 0x38 +#define PT_I3 0x3c +#define PT_I4 0x40 +#define PT_I5 0x44 +#define PT_I6 0x48 +#define PT_FP PT_I6 +#define PT_I7 0x4c + + + + + +struct pt_regs { + uint32_t psr; + uint32_t pc; + uint32_t npc; + uint32_t y; + uint32_t u_regs[16]; /* globals and ins */ +}; + +struct leon_reg_win { + uint32_t locals[8]; + uint32_t ins[8]; +}; + +/* a stack frame */ +struct sparc_stackf { + uint32_t locals[8]; + uint32_t ins[6]; + struct sparc_stackf *fp; /* %i6 == %fp */ + uint32_t callers_pc; /* %i7 == return %pc */ + uint8_t *structptr; + uint32_t xargs[6]; + uint32_t xxargs[1]; + /* everyting allocated on the stack follows here */ +}; + + +#define STACKFRAME_SZ sizeof(struct sparc_stackf) + +#define STACK_ALIGN 8 + + +int stack_migrate(void *sp, void *stack_top_new); + + +#endif /* _SPARC_STACK_H_ */ diff --git a/arch/sparc/include/stacktrace.h b/arch/sparc/include/stacktrace.h new file mode 100644 index 0000000000000000000000000000000000000000..26094690ee0539e6bec637e6236b6a52fcf15dc0 --- /dev/null +++ b/arch/sparc/include/stacktrace.h @@ -0,0 +1,33 @@ +/** + * @file arch/sparc/include/stacktrace.h + */ + +#ifndef _SPARC_STACKTRACE_H_ +#define _SPARC_STACKTRACE_H_ + +#include <stdint.h> +#include <stack.h> + +struct stack_trace { + uint32_t nr_entries; + uint32_t max_entries; + struct sparc_stackf **frames; + struct pt_regs **regs; +}; + + +#if defined(USE_STACK_TRACE_TRAP) +/** + * @brief a trap handler to execute a stack trace (implemented in asm) + */ +void trace_trap(void); + +void die(void); +#endif + +void save_stack_trace(struct stack_trace *trace, uint32_t sp, uint32_t pc); + +/* part of libgloss */ +void __flush_windows(void); + +#endif /* _SPARC_STACKTRACE_H_ */ diff --git a/arch/sparc/include/traps.h b/arch/sparc/include/traps.h new file mode 100644 index 0000000000000000000000000000000000000000..1b80d592b189bd8c645582dbe51f768242d1f196 --- /dev/null +++ b/arch/sparc/include/traps.h @@ -0,0 +1,32 @@ +/** + * @file arch/sparc/include/traps.h + * @ingroup traps + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + * @date February, 2016 + * + * @copyright GPLv2 + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + + +#ifndef _ARCH_SPARC_TRAPS_H_ +#define _ARCH_SPARC_TRAPS_H_ + +#include <stdint.h> + +void trap_handler_install(uint32_t trap, void (*handler)(void)); + +void data_access_exception_trap(void); +void data_access_exception_trap_ignore(void); + +void floating_point_exception_trap(void); + +#endif /* _ARCH_SPARC_TRAPS_H_ */ diff --git a/arch/sparc/include/uapi/asi.h b/arch/sparc/include/uapi/asi.h new file mode 100644 index 0000000000000000000000000000000000000000..298fe9fa52d2d95d168467e55f4e30b9aa5965f8 --- /dev/null +++ b/arch/sparc/include/uapi/asi.h @@ -0,0 +1,31 @@ +/** + * @brief Address Space Identifiers (ASI) for the LEON + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + */ + + +#ifndef _SPARC_ASI_H_ +#define _SPARC_ASI_H_ + +#define ASI_LEON_NOCACHE 0x01 +#define ASI_LEON_DCACHE_MISS ASI_LEON_NOCACHE + +#define ASI_LEON_CACHEREGS 0x02 +#define ASI_LEON_IFLUSH 0x10 +#define ASI_LEON_DFLUSH 0x11 + +#define ASI_LEON2_IFLUSH 0x05 +#define ASI_LEON2_DFLUSH 0x06 + +#define ASI_LEON_MMUFLUSH 0x18 +#define ASI_LEON_MMUREGS 0x19 +#define ASI_LEON_BYPASS 0x1c +#define ASI_LEON_FLUSH_PAGE 0x10 + + + +#define ASI_LEON_CACHEREGS_SNOOPING_BIT (1<<27) /* GR712RC-UM p. 44 */ + + + +#endif /* _SPARC_ASI_H_ */ diff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile index 48692145a056402dc1b4dc5c68fafe0d049dbba1..e00aa128102045c013a85863637c46a876d649a5 100644 --- a/arch/sparc/kernel/Makefile +++ b/arch/sparc/kernel/Makefile @@ -9,5 +9,10 @@ obj-y += bootmem.o obj-y += mm.o obj-y += elf.o obj-y += module.o +obj-y += traps.o +obj-y += stacktrace.o +obj-y += stack.o +obj-y += traps/data_access_exception_trap.o +obj-y += traps/data_access_exception.o #libs-y += lib/ diff --git a/arch/sparc/kernel/bootmem.c b/arch/sparc/kernel/bootmem.c index 688c30bc416785e2fb3615cc2e6540e69de01ceb..b5a9eb07bfde16915ab3b97fa5768fa2c8a124c6 100644 --- a/arch/sparc/kernel/bootmem.c +++ b/arch/sparc/kernel/bootmem.c @@ -1,29 +1,139 @@ /** * @file arch/sparc/kernel/bootmem.c + * + * This sets up a buddy system memory manager that handles the physical RAM + * banks. The kernel image RAM section itself is reserved, and a more + * fine-grained boot memory allocator is set up that can be used to reserve + * memory for low-level kernel objects such as MMU tables. + * + * Note that because we don't use a two-stage bootstrap process at this time, + * we will 1:1 map the relevant physical memory bank addresses through the MMU, + * so the low level objects may be used without a translation step for now, + * but we will migrate to a new kernel stack base at (2^32 - 1) once we + * have set up the MMU. + * + * Because of the above, user space memory will be mapped below the + * boot address of the kernel image, i.e. 0x40000000 for typical LEON hardware, + * since we want to keep our kernel context in the MMU at all times. */ #include <page.h> +#include <stack.h> #include <string.h> #include <kernel/printk.h> #include <kernel/kernel.h> +#include <chunk.h> + + +/* the pool we use for our boot memory allocator */ +static struct chunk_pool phys_mem_pool; + + +/** + * @brief boot memory allocator wrapper + * @param size the number of bytes to allocate + * + * @return a pointer to a buffer or NULL on error + */ + +static void *bootmem_alloc_internal(size_t size) +{ +#if (CONFIG_SPARC_BOOTMEM_REQUEST_NEW_ON_DEMAND) + static int blocked; + + if (blocked) { + pr_crit("BOOTMEM: out of memory\n"); + return NULL; + } + blocked = 1; +#endif + +#if (BOOTMEM_CHUNKSIZE > PAGE_SIZE) + return page_map_reserve_chunk(BOOTMEM_CHUNKSIZE); +#else + return page_alloc(); +#endif +} + + +/** + * @brief boot memory free wrapper + * @param addr the address to free + */ + +static void bootmem_free_internal(void *ptr) +{ + page_free(ptr); +} + +/** + * @brief finds the actual size of an allocated buffer + * @param addr the address to look up + * + * @return the buffer size, or 0 if invalid address or not found + */ + +static size_t bootmem_get_alloc_size(void *ptr) +{ + return page_map_get_chunk_size(ptr); +} + + +/** + * @brief allocate a buffer from the boot memory allocator + * + * @param size the number of bytes to allocate + * + * @return a pointer to a buffer or NULL on error + */ + +void *bootmem_alloc(size_t size) +{ + void *ptr; + + ptr = chunk_alloc(&phys_mem_pool, size); + + if (!ptr) { + pr_emerg("BOOTMEM: allocator out of memory\n"); + BUG(); + } + + return ptr; +} + + +/** + * @brief release a buffer to the boot memory allocator + * + * @param addr the address to release + */ + +void bootmem_free(void *ptr) +{ + chunk_free(&phys_mem_pool, ptr); +} + + +/** + * @brief initialise the boot memory + */ -/* TODO still demo code */ void bootmem_init(void) { int i; + int ret; + unsigned long base_pfn; unsigned long start_pfn; unsigned long start_img_pfn; unsigned long end_pfn = 0UL; unsigned long mem_size; - void *pages[2048]; - - int t=0; + unsigned long node = 0; struct page_map_node **pg_node; @@ -36,12 +146,12 @@ void bootmem_init(void) * start symbol in image, which hopefully coincides with the start * of the RAM we are running from. */ - start_img_pfn = (unsigned long) PAGE_ALIGN((unsigned long) &start); + start_img_pfn = PAGE_ALIGN((unsigned long) &start); /* start allocatable memory with page aligned address of last symbol in * image, everything before will be reserved */ - start_pfn = (unsigned long) PAGE_ALIGN((unsigned long) &end); + start_pfn = PAGE_ALIGN((unsigned long) &end); /* locate the memory bank we're in */ @@ -50,6 +160,9 @@ void bootmem_init(void) for (i = 0; sp_banks[i].num_bytes != 0; i++) { + /* do you feel luck, punk? */ + BUG_ON(i > SPARC_PHYS_BANKS); + if (start_pfn < sp_banks[i].base_addr) continue; @@ -85,8 +198,8 @@ void bootmem_init(void) end_pfn = (unsigned long) __pa(end_pfn); //end_pfn = PHYS_PFN(end_pfn); - pr_info("BOOTMEM: start page frame number: 0x%lx\n" - "BOOTMEM: end page frame number: 0x%lx\n", + pr_info("BOOTMEM: start page frame at 0x%lx\n" + "BOOTMEM: end page frame at 0x%lx\n", start_pfn, end_pfn); @@ -95,7 +208,8 @@ void bootmem_init(void) * on top of that */ - pg_node = MEM_PAGE_NODE(0); + pg_node = MEM_PAGE_NODE(node); + node++; (*pg_node) = &mm_init_page_node; @@ -119,92 +233,59 @@ void bootmem_init(void) BUG_ON(!page_map_reserve_chunk(start_pfn - base_pfn)); - mm_dump_stats((*pg_node)->pool); - - page_alloc(); - page_alloc(); - page_alloc(); - page_alloc(); - page_alloc(); - - mm_dump_stats((*pg_node)->pool); + /* our image has been reserved, now set up the boot memory allocator */ + chunk_pool_init(&phys_mem_pool, STACK_ALIGN, &bootmem_alloc_internal, + &bootmem_free_internal, &bootmem_get_alloc_size); + /* now add the remaining memory banks */ + for (i = 0; sp_banks[i].num_bytes != 0; i++) { - pg_node = MEM_PAGE_NODE(1); - - BUG_ON(!pg_node); - - (*pg_node) = page_map_reserve_chunk( - sizeof(struct page_map_node)); - - (*pg_node)->pool = page_map_reserve_chunk( - sizeof(struct mm_pool)); - - bzero((*pg_node)->pool, sizeof(struct mm_pool)); - - (*pg_node)->pool->block_order = page_map_reserve_chunk( - sizeof(struct list_head) * - MM_BLOCK_ORDER_MAX); - (*pg_node)->pool->alloc_order = page_map_reserve_chunk( - MM_INIT_NUM_BLOCKS); - (*pg_node)->pool->blk_free = page_map_reserve_chunk( - MM_INIT_LEN_BITMAP * - sizeof(unsigned long)); - - - base_pfn = (unsigned long) page_map_reserve_chunk(1024*4*4); - page_map_add(base_pfn, base_pfn + 1024*4*4, PAGE_SIZE); - - - - pg_node = MEM_PAGE_NODE(2); - BUG_ON(!pg_node); - - (*pg_node) = page_map_reserve_chunk( - sizeof(struct page_map_node)); - BUG_ON(!(*pg_node)); + BUG_ON(i > SPARC_PHYS_BANKS); - (*pg_node)->pool = page_map_reserve_chunk( - sizeof(struct mm_pool)); + /* this one has already been added */ + if (base_pfn == sp_banks[i].base_addr) + continue; - bzero((*pg_node)->pool, sizeof(struct mm_pool)); - (*pg_node)->pool->block_order = page_map_reserve_chunk( - sizeof(struct list_head) * - MM_BLOCK_ORDER_MAX); - (*pg_node)->pool->alloc_order = page_map_reserve_chunk( - MM_INIT_NUM_BLOCKS); - (*pg_node)->pool->blk_free = page_map_reserve_chunk( - MM_INIT_LEN_BITMAP * - sizeof(unsigned long)); + + pg_node = MEM_PAGE_NODE(node); + node++; + if(!pg_node) { + pr_err("BOOTMEM: initial page map holds less nodes " + "than number of configured memory banks.\n"); + break; + } - base_pfn = (unsigned long) page_map_reserve_chunk(1024*4*4); - page_map_add(base_pfn, base_pfn + 1024*4*4, PAGE_SIZE); + /* let's assume we always have enough memory, because if we + * don't, there is a serious configuration problem anyways + */ + (*pg_node) = (struct page_map_node *) + bootmem_alloc(sizeof(struct page_map_node)); - while (t < 1740) - pages[t++] = page_alloc(); + (*pg_node)->pool = (struct mm_pool *) + bootmem_alloc(sizeof(struct mm_pool)); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); - pages[t++] = page_alloc(); + bzero((*pg_node)->pool, sizeof(struct mm_pool)); - /* NULL */ - pages[t++] = page_alloc(); + (*pg_node)->pool->block_order = (struct list_head *) + bootmem_alloc(sizeof(struct list_head) + * MM_BLOCK_ORDER_MAX); + (*pg_node)->pool->alloc_order = (unsigned char *) + bootmem_alloc(MM_INIT_NUM_BLOCKS); - page_free(pages[--t]); - page_free(pages[--t]); - page_free(pages[--t]); - page_free(pages[--t]); - page_free(pages[--t]); - page_free(pages[--t]); + (*pg_node)->pool->blk_free = (unsigned long *) + bootmem_alloc(MM_INIT_LEN_BITMAP + * sizeof(unsigned long)); + ret = page_map_add(sp_banks[i].base_addr, + sp_banks[i].base_addr + sp_banks[i].num_bytes, + PAGE_SIZE); + if (ret) { + pr_emerg("BOOTMEM: cannot add page map node\n"); + BUG(); + } + } } diff --git a/arch/sparc/kernel/init.c b/arch/sparc/kernel/init.c index faf296604e7e0175c678b14368947c7df85a5699..c5d6727260397b4bb50e873e6029d53867b29d62 100644 --- a/arch/sparc/kernel/init.c +++ b/arch/sparc/kernel/init.c @@ -3,8 +3,10 @@ */ #include <mm.h> +#include <srmmu.h> void paging_init(void) { bootmem_init(); + mm_mmu_paging_init(); } diff --git a/arch/sparc/kernel/mm.c b/arch/sparc/kernel/mm.c index 42d5a9f96a3e8d3fdb52cab0d4b490c3c4e15a8b..4adfabd5827424652bfb395b01f86c234a2b6547 100644 --- a/arch/sparc/kernel/mm.c +++ b/arch/sparc/kernel/mm.c @@ -4,7 +4,15 @@ #include <mm.h> +#include <kernel/kernel.h> +#include <kernel/printk.h> +#include <page.h> +#include <srmmu.h> +#include <srmmu_access.h> +#include <traps.h> +#include <cpu_type.h> +#include <errno.h> /* things we need statically allocated in the image (i.e. in .bss) * at boot @@ -14,3 +22,342 @@ unsigned long phys_base; unsigned long pfn_base; struct sparc_physical_banks sp_banks[SPARC_PHYS_BANKS + 1]; + + + + + + +/** + * for now, this is our primitive per-process (we really have only one...) + * system break tracker + * + * sbrk: the current program break + * addr_low the legal lower boundary + * addr_hi the legal upper boundary + * + * note: we don't have user/kernel memory segmentation yet + */ + +static struct mm_sbrk { + unsigned long sbrk; + + unsigned long addr_lo; + unsigned long addr_hi; +} mm_proc_mem [SRMMU_CONTEXTS]; + +static unsigned long _mmu_ctx; + + + +/* XXX: dummy, move out of here */ +enum sparc_cpus sparc_cpu_model; + + + +/** + * @brief if the SRMMU is not supported + */ + +static void mm_srmmu_is_bad(void) +{ + pr_emerg("MM: No supported SRMMU type found.\n"); + BUG(); +} + + +/** + * @brief configure MMU on a LEON + * + * TODO stuff... + */ + +static void mm_init_leon(void) +{ + trap_handler_install(0x9, data_access_exception_trap); + srmmu_init(&bootmem_alloc, &bootmem_free); + + /* 1:1 map full range of highmem */ + srmmu_do_large_mapping_range(0, HIGHMEM_START, HIGHMEM_START, + (0xFFFFFFFF - HIGHMEM_START) / + SRMMU_LARGE_PAGE_SIZE + 1, + (SRMMU_CACHEABLE | SRMMU_ACC_S_RWX_2)); + + mm_set_mmu_ctx(0); + + srmmu_enable_mmu(); +} + + +/** + * @brief probe for supported mmu type + */ + +static void get_srmmu_type(void) +{ + /* we only support the GRLIB SRMMU, its implementation and version + * fields are set to 0, so we only check for a matching CPU model + * else we assume a bad SRMMU + */ + + if (srmmu_get_mmu_ver()) /* GR712RC == 0 */ + if (srmmu_get_mmu_ver() != 1) /* apparently MPPB */ + goto bad; + + if (srmmu_get_mmu_impl()) /* GR712RC == 0 */ + if (srmmu_get_mmu_impl() != 8) /* apparently MPPB */ + goto bad; + + if (sparc_cpu_model == sparc_leon) { + mm_init_leon(); + return; + } + +bad: + mm_srmmu_is_bad(); +} + + +/** + * @brief load MMU support + * @note this is called from setup_arch() for LEON + */ + +static void mm_load_mmu(void) +{ + get_srmmu_type(); + + /* XXX: we also might want to: + * - load TLB operations if needed (for SMP) + * - IOMMU setup if needed + * - initialise SMP + */ +} + + +/** + * @brief set the active MMU context + * + * @param ctx the MMU context to set + * + * @returns 0 on success, otherwise error + */ + +int mm_set_mmu_ctx(unsigned long ctx) +{ + + if (srmmu_select_ctx(ctx)) + return -EINVAL; + + /* if necessary, initialise the program break */ + if (!mm_proc_mem[ctx].sbrk) { + mm_proc_mem[ctx].sbrk = VMALLOC_START; + mm_proc_mem[ctx].addr_lo = VMALLOC_START; + mm_proc_mem[ctx].addr_hi = VMALLOC_END; + } + + _mmu_ctx = ctx; + + return 0; +} + + +/** + * @brief get the active MMU context + * + * @returns the active MMU context number + */ + +unsigned long mm_get_mmu_ctx(void) +{ + return _mmu_ctx; +} + +void mm_release_mmu_mapping(unsigned long va_start, unsigned long va_stop) +{ + unsigned long ctx; + + + ctx = mm_get_mmu_ctx(); + + srmmu_release_pages(ctx, va_start, va_stop, page_free); +} + +void *kernel_sbrk(intptr_t increment) +{ + long brk; + long oldbrk; + + unsigned long ctx; + + + ctx = mm_get_mmu_ctx(); + + if (!increment) + return (void *) mm_proc_mem[ctx].sbrk; + + oldbrk = mm_proc_mem[ctx].sbrk; + + brk = oldbrk + increment; + + if (brk < mm_proc_mem[ctx].addr_lo) + return (void *) -1; + + if (brk > mm_proc_mem[ctx].addr_hi) + return (void *) -1; + + /* try to release pages if we decremented below a page boundary */ + if (increment < 0) { + if (PAGE_ALIGN(brk) < PAGE_ALIGN(oldbrk - PAGE_SIZE)) { + printk("SBRK: release %lx (%lx)\n", brk, PAGE_ALIGN(brk)); + mm_release_mmu_mapping(PAGE_ALIGN(brk), oldbrk); + } + } + + mm_proc_mem[ctx].sbrk = brk; + + pr_debug("SBRK: moved %08lx -> %08lx\n", oldbrk, brk); + + return (void *) oldbrk; +} + + + + +void mm_mmu_trap(void) +{ + unsigned long ctx; + + struct srmmu_fault_status status; + unsigned int addr; + unsigned int alloc; + static int last; + + status = srmmu_get_mmu_fault_status(); + + pr_debug("MM: MMU Status:\n" + "MM: ===========\n"); + + pr_debug("MM:\tAccess type: "); + switch(status.access_type) { + case SRMMU_FLT_AT_R__U_DATA: + pr_debug("Read from User data\n"); break; + case SRMMU_FLT_AT_R__S_DATA: + pr_debug("Read from SuperUser data\n"); break; + case SRMMU_FLT_AT_RX_U_INST: + pr_debug("Read/Exec from User instruction\n"); break; + case SRMMU_FLT_AT_RX_S_INST: + pr_debug("Read/Exec from SuperUser instruction\n"); break; + case SRMMU_FLT_AT_W__U_DATA: + pr_debug("Write to User data\n"); break; + case SRMMU_FLT_AT_W__S_DATA: + pr_debug("Write to SuperUser instruction\n"); break; + case SRMMU_FLT_AT_W__U_INST: + pr_debug("Write to User instruction\n"); break; + case SRMMU_FLT_AT_W__S_INST: + pr_debug("Write to SuperUser instruction\n"); break; + default: + pr_debug("Unknown\n"); break; + } + + pr_debug("MM:\tFault type: "); + switch(status.fault_type) { + case SRMMU_FLT_FT_NONE: + pr_debug("None\n"); break; + case SRMMU_FLT_FT_INVALID_ADDR: + if (status.fault_address_vaild) { + + addr = srmmu_get_mmu_fault_address(); + + if (!addr) { + pr_crit("NULL pointer violation " + "in call from %p\n", + __builtin_return_address(1)); + BUG(); + } + + + pr_debug("Invalid Address/Not mapped: 0x%08x\n", + srmmu_get_mmu_fault_address() >> 24); + + + ctx = mm_get_mmu_ctx(); + + if (addr < mm_proc_mem[ctx].addr_lo) { + + pr_crit("Access violation: RESERVED (0x%08lx) " + "in call from %p\n", + addr, + __caller(1)); + BUG(); + } + + if (addr > HIGHMEM_START) { + pr_debug("Access violation: HIGHMEM\n"); + BUG(); + } + + + if (addr < mm_proc_mem[ctx].sbrk) { + alloc = (unsigned long) page_alloc(); + if (!alloc) { + pr_crit("MM:\t Out of physical memory %lx\n", last); + BUG(); + } + + last = alloc; + + pr_debug("MM: Allocating page %lx -> %lx\n",addr, (unsigned + int) alloc); + + srmmu_do_small_mapping(ctx, addr, + alloc, + (SRMMU_CACHEABLE | SRMMU_ACC_S_RW_2)); + } else { + pr_crit("Access violation: system break " + "in call from %p\n", + __builtin_return_address(1)); + BUG(); + } + } + break; + case SRMMU_FLT_FT_PROTECTION: + pr_debug("Protection Error\n"); break; + case SRMMU_FLT_FT_PRIV_VIOLATION: + pr_debug("Priviledge Violation\n"); break; + case SRMMU_FLT_FT_TRANSLATION: + pr_debug("Translation Error\n"); break; + case SRMMU_FLT_FT_ACCESS_BUS: + pr_debug("Bus Access Error\n"); break; + case SRMMU_FLT_FT_INTERNAL: + pr_debug("Internal Error\n"); break; + default: + pr_debug("Unknown\n"); break; + } + + pr_debug("MM:\tPage table level: %d\n", status.page_table_level); + + if (status.fault_address_vaild) + pr_debug("MM:\tVirtual address 0x%x", srmmu_get_mmu_fault_address()); + + if (status.overwrite) + pr_debug("\tMultiple errors since last read of status recorded"); + + + pr_debug("\nMM:\n"); + +} + + + +/** + * + * @brief initialise paging on the SRMMU + */ + +void mm_mmu_paging_init(void) +{ + /* XXX: must be configured earlier by reading appropriate register */ + sparc_cpu_model = sparc_leon; + mm_load_mmu(); +} diff --git a/arch/sparc/kernel/setup.c b/arch/sparc/kernel/setup.c index 3787f6039865e7d582e58c9faecd170f107fd053..f25f695fb311eea52d2df883cf4583718e79d899 100644 --- a/arch/sparc/kernel/setup.c +++ b/arch/sparc/kernel/setup.c @@ -8,6 +8,47 @@ #include <mm.h> #include <compiler.h> +#include <page.h> +#include <stack.h> +#include <kernel/kmem.h> + +void *_kernel_stack_top; +void *_kernel_stack_bottom; + + +/** + * @brief reserve a stack area for the kernel + * + * @warn Since we allocate the kernel stack using kmalloc instead of placing it + * in a custom area, there is a real change of violating the bottom + * boundary, so make sure you allocate enough pages to fit your needs. + * Note that since kmalloc() works via kernel_sbrk() and moving the system + * break does not actually reserve pages until they are accessed, you + * have to initialise the stack area, i.e. actually reserve the pages, + * unless you have a custom interrupt/trap stack. Otherwise the kernel + * cannot perform stack access to an unmapped page, because that would + * require a mapped page... + * + * XXX this needs to be addressed at some point, but probably only after we + * have a kernel bootstrap implemented and we may define custom reserved + * areas more freely. + */ + +#warning "Using fixed-size kernel stack" +static void reserve_kernel_stack(void) +{ + const size_t k_stack_sz = KERNEL_STACK_PAGES * PAGE_SIZE; + + + /* the bottom of the stack */ + _kernel_stack_bottom = kcalloc(k_stack_sz + STACK_ALIGN, sizeof(char)); + BUG_ON(!_kernel_stack_bottom); + + /* the (aligned) top of the stack */ + _kernel_stack_top = (void *) (char *) _kernel_stack_bottom + k_stack_sz; + _kernel_stack_top = ALIGN_PTR(_kernel_stack_top, STACK_ALIGN); +} + /** * @brief configure available memory banks @@ -19,14 +60,16 @@ static void mem_init(void) { - memset(&sp_banks, 0x0, ARRAY_SIZE(sp_banks)); - sp_banks[0].base_addr = 0x40000000; sp_banks[0].num_bytes = 0x00800000; -#if 0 + +#if (SPARC_PHYS_BANKS > 0) sp_banks[1].base_addr = 0x60000000; sp_banks[1].num_bytes = 0x04000000; +#else +#warning "Configuration error: SPARC_PHYS_BANKS size insufficient." #endif + } @@ -37,5 +80,12 @@ static void mem_init(void) void setup_arch(void) { mem_init(); + paging_init(); + + BUG_ON(!kmem_init()); + + reserve_kernel_stack(); + + BUG_ON(stack_migrate(NULL, _kernel_stack_top)); } diff --git a/arch/sparc/kernel/stack.c b/arch/sparc/kernel/stack.c new file mode 100644 index 0000000000000000000000000000000000000000..e4da1b52dace07e2c0a5f7c66edfb4720596180e --- /dev/null +++ b/arch/sparc/kernel/stack.c @@ -0,0 +1,93 @@ +/** + * @file arch/sparc/kernel/stack.c + */ + + +#include <stacktrace.h> +#include <asm/leon.h> + +#include <string.h> /* memcpy() */ +#include <stdio.h> +#include <errno.h> + + +/** + * @brief migrate a stack + * + * @param sp the (bottom) stack pointer of the old stack + * @param stack_top the top of the new stack area + * + * @note the new stack area is assumed to at least hold the old stack + * @note remember that SPARC stacks grow from top to bottom + */ + +int stack_migrate(void *sp, void *stack_top_new) +{ + int i; + + void *sp_new; + + unsigned long stack_sz; + unsigned long stack_top; + unsigned long stack_bot; + + unsigned long stackf_sz; + + + struct stack_trace x; + struct sparc_stackf *frames[30]; + struct pt_regs *regs[30]; + + struct sparc_stackf *stack; + + + + if (!stack_top_new) + return -EINVAL; + + /* migrate the current stack */ + if (!sp) + sp = (void *) leon_get_sp(); + + /* 30 should be more than enough */ + x.max_entries = 30; + x.nr_entries = 0; + x.frames = frames; + x.regs = regs; + + + /* this also flushes SPARC register windows */ + save_stack_trace(&x, (uint32_t) sp, 0); + + + stack_top = (unsigned long) frames[x.nr_entries - 1]; + stack_bot = (unsigned long) frames[0]; + + stack_sz = stack_top - stack_bot; + + /* new bottom of stack */ + sp_new = (void *) ((char *) stack_top_new - stack_sz); + + stack = (struct sparc_stackf *) sp_new; + + /* copy the old stack */ + memcpy(sp_new, (void *) stack_bot, stack_sz); + + /* adjust frame addresses for all but top frame */ + for(i = 0; i < (x.nr_entries - 1); i++) { + + stackf_sz = (unsigned long) frames[i]->fp + - (unsigned long) frames[i]; + stack->fp = (void *) ((char *) stack + stackf_sz); + + stack = stack->fp; + } + + /* go back to new bottom of stack */ + stack = (struct sparc_stackf *) sp_new; + + /* update frame pointer so we jump to the migrated stack on return */ + leon_set_fp((unsigned long) stack->fp); + + return 0; +} diff --git a/arch/sparc/kernel/stacktrace.c b/arch/sparc/kernel/stacktrace.c new file mode 100644 index 0000000000000000000000000000000000000000..889ffaf600934f3685295aae03ba720936931eae --- /dev/null +++ b/arch/sparc/kernel/stacktrace.c @@ -0,0 +1,106 @@ +/** + * @file arch/sparc/kernel/stacktrace.c + */ + +#include <stdlib.h> + +#include <compiler.h> +#include <asm/leon.h> +#include <stacktrace.h> +#include <kernel/printk.h> + + + +/** + * @brief validates the stack pointer address + */ +static int stack_valid(uint32_t sp) +{ + if (sp & (STACK_ALIGN - 1) || !sp) + return 0; + + return 1; +} + + +/** + * @brief performs a stack trace + * + * @param trace a struct stack_trace + * @param sp a stack/frame pointer + * @param pc a program counter + * + * @note When being called from a trap, the pc in %o7 is NOT the return program + * counter of the trapped function, so a stack/frame pointer by itself + * is not enough to provide a proper trace, hence the pc argument + */ + +void save_stack_trace(struct stack_trace *trace, uint32_t sp, uint32_t pc) +{ + struct sparc_stackf *sf; + struct pt_regs *regs; + + + if (!stack_valid(sp)) + return; + + /* flush register windows to memory*/ + leon_reg_win_flush(); + + do { + if (!stack_valid(sp)) + break; + + + sf = (struct sparc_stackf *) sp; + regs = (struct pt_regs *) (sf + 1); + + trace->frames[trace->nr_entries] = sf; + trace->regs[trace->nr_entries] = regs; + + trace->nr_entries++; + + pc = sf->callers_pc; + sp = (uint32_t) sf->fp; + + } while (trace->nr_entries < trace->max_entries); +} + + + +#if defined(USE_STACK_TRACE_TRAP) + +/** + * @brief a generic shutdown function + * TODO: replace with whatever we need in the end + */ + +void die(void) +{ + BUG(); +} + + +/** + * @brief executes a stack trace + * + * @param fp a frame pointer + * @param pc a program counter + * + * @note this is called by the trap handler + */ + +void trace(uint32_t fp, uint32_t pc) +{ + uint32_t entries[30]; + + struct stack_trace x; + + + x.max_entries = 30; + x.nr_entries = 0; + x.entries = entries; + + save_stack_trace(&x, fp, pc); +} +#endif diff --git a/arch/sparc/kernel/traps.c b/arch/sparc/kernel/traps.c new file mode 100644 index 0000000000000000000000000000000000000000..534f3594806720bda4fe6d25543a50410362bd8b --- /dev/null +++ b/arch/sparc/kernel/traps.c @@ -0,0 +1,119 @@ +/** + * @file arch/sparc/kernel/traps.c + * @ingroup traps + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + * @author Linus Torvalds et al. + * @date September, 2015 + * + * @copyright GPLv2 + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * @defgroup traps Trap Table access + * @brief Implements functionality to access or modify SPARC v8 MVT trap + * table entries + * + * + * + * ## Overview + * + * This module implements functionality to access or modify SPARC v8 MVT trap + * table entries. + * + * ## Mode of Operation + * + * None + * + * ## Error Handling + * + * None + * + * ## Notes + * + * - functionality will be added as needed + * + * + */ + +#include <traps.h> +#include <asm/io.h> +#include <compiler.h> + + +/** + * @brief installs a trap handler that executes a long jump + * + * @param trap a trap entry number + * @param handler a function to call when the trap is triggerd + * + * @note I'm not sure why this must be nested in another function call. If it + * isn't, the trap entry seemingly does not update properly. It might have + * to do with the rotation of the register window or some caching + * mechanism I'm not aware of at this time. The writes to the table are + * uncached, so the data cache is likely not the issue, and flusing it + * won't do anything either. When not nested in a wrapper, it however + * appears to work as expected if a function that in turn calls a + * function is executed after the trap entry is written - but it does not + * if the same function is called after this one has returned. O.o + * + * Installs a custom handler into a specified trap table entry. Note that it is + * not possible to install just any function, since traps are disabled when the + * call is executed and any further trap (such as window over/underflow) will + * force the processor to jump to trap 0 (i.e. reset). See lib/asm/trace_trap.S + * for a working example. + * + * + */ + +static void trap_longjump_install(uint32_t trap, void (*handler)(void)) +{ + uint32_t *trap_base; + uint32_t tbr; + uint32_t h; + + + h = (unsigned int) handler; + + /* extract the trap table address from %tbr (skips lower bits 0-11) */ + __asm__ __volatile__("rd %%tbr, %0" : "=r" (tbr)); + + /* calculate offset to trap, each entry is 4 machine words long */ + trap_base = (uint32_t *)((tbr & ~0xfff) + (trap << 4)); + + /* set up the trap entry: + * 0x29000000 sethi %hi(handler), %l4 + * 0x81c52000 jmpl %l4 + %lo(handler), %g0 + * 0x01000000 rd %psr, %l0 + * 0x01000000 nop + */ + iowrite32be(((h >> 10) & 0x3fffff) | 0x29000000, trap_base + 0); + iowrite32be((h & 0x3ff) | 0x81c52000, trap_base + 1); + iowrite32be(0xa1480000, trap_base + 2); + iowrite32be(0x01000000, trap_base + 3); + + barrier(); +} + +/** + * @brief installs a custom trap handler + * + * @param trap a trap entry number + * @param handler a function to call when the trap is triggerd + * + * Installs a custom handler into a specified trap table entry. Note that it is + * not possible to install just any function, since traps are disabled when the + * call is executed and any further trap (such as window over/underflow) will + * force the processor to jump to trap 0 (i.e. reset). See lib/asm/trace_trap.S + * for a working example. + */ + +void trap_handler_install(uint32_t trap, void (*handler)(void)) +{ + trap_longjump_install(trap, handler); +} diff --git a/arch/sparc/kernel/traps/data_access_exception.c b/arch/sparc/kernel/traps/data_access_exception.c new file mode 100644 index 0000000000000000000000000000000000000000..5bfa4acef2bb7eb04bcc0cf20fd4bb2b156e95d0 --- /dev/null +++ b/arch/sparc/kernel/traps/data_access_exception.c @@ -0,0 +1,13 @@ +/** + * @file arch/sparc/kernel/data_access_exception.c + * + */ + +#include <kernel/printk.h> +#include <mm.h> + + +void data_access_exception(void) +{ + mm_mmu_trap(); +} diff --git a/arch/sparc/kernel/traps/data_access_exception_trap.S b/arch/sparc/kernel/traps/data_access_exception_trap.S new file mode 100644 index 0000000000000000000000000000000000000000..65445e42e30b4eb01fe1742943e09682a450cdcb --- /dev/null +++ b/arch/sparc/kernel/traps/data_access_exception_trap.S @@ -0,0 +1,78 @@ +/** + * @file arch/sparc/kernel/traps/data_access_exception_trap.S + * @brief this is a function that is called by a custom trap handler to handle + * an MMU or EDAC trap + * + * @todo this is BCC-specific + */ + + + +#define SAVE_ALL_HEAD \ + sethi %hi(leonbare_trapsetup), %l4; \ + jmpl %l4 + %lo(leonbare_trapsetup), %l6; +#define SAVE_ALL \ + SAVE_ALL_HEAD \ + nop; + + +#define FW_REGS_SZ 0x90 /* 36*4 */ +#define SF_REGS_SZ 0x60 /* 24*4 */ + +/* All traps low-level code here must end with this macro. */ +#define RESTORE_ALL b leonbare_trapreturn; clr %l6; + + + + .text + .align 4 + .globl data_access_exception_trap + + + +#define PSR_ET 0x00000020 /* enable traps field */ +#define PSR_PIL_MASK 0x00000F00 /* processor interrupt level */ + +data_access_exception_trap: + + /* Since we do not overwrite values either in RAM or in FLASH, we cannot + * continue from the original instruction or the same trap will occur, + * hence we have to point %pc to %npc and increment %npc to the + * following instruction + */ + + /* ...but we can for the MMU + mov %l2, %l1 + add %l2, 4, %l2 + */ + + rd %wim, %l3 /* trap setup needs the %wim in %l3 */ + + SAVE_ALL /* create a trap window */ + + /* re-enable traps but not interrupts (set level to max) */ + or %l0, PSR_PIL_MASK, %o0 + wr %o0, PSR_ET, %psr + nop + nop + nop + + call data_access_exception + add %sp, FW_REGS_SZ + 8 + SF_REGS_SZ , %o1 + + + RESTORE_ALL /* also returns */ + + + + + + .globl data_access_exception_trap_ignore + +data_access_exception_trap_ignore: + + /* see above */ + mov %l2, %l1 + add %l2, 4, %l2 + jmp %l1 + rett %l2 diff --git a/arch/sparc/kernel/traps/floating_point_exception_trap.S b/arch/sparc/kernel/traps/floating_point_exception_trap.S new file mode 100644 index 0000000000000000000000000000000000000000..8405294e97f4b2dbcf459ee7f99505bd970a2bb2 --- /dev/null +++ b/arch/sparc/kernel/traps/floating_point_exception_trap.S @@ -0,0 +1,72 @@ +/** + * @file arch/sparc/kernel/traps/floating_point_exception_trap.S + * + * @brief this is a function that is called by a custom trap handler to handle + * a floating point exception + * + * @todo this is BCC-specific + */ + + + +#define SAVE_ALL_HEAD \ + sethi %hi(leonbare_trapsetup), %l4; \ + jmpl %l4 + %lo(leonbare_trapsetup), %l6; +#define SAVE_ALL \ + SAVE_ALL_HEAD \ + nop; + + +#define FW_REGS_SZ 0x90 /* 36*4 */ +#define SF_REGS_SZ 0x60 /* 24*4 */ + +/* All traps low-level code here must end with this macro. */ +#define RESTORE_ALL b leonbare_trapreturn; clr %l6; + + + + .text + .align 4 + .globl floating_point_exception_trap + + + +#define PSR_ET 0x00000020 /* enable traps field */ +#define PSR_PIL_MASK 0x00000F00 /* processor interrupt level */ + +floating_point_exception_trap: + + + mov %l2, %l1 + add %l2, 4, %l2 + + rd %wim, %l3 /* trap setup needs the %wim in %l3 */ + + SAVE_ALL /* create a trap window */ + + /* re-enable traps but not interrupts (set level to max) */ + or %l0, PSR_PIL_MASK, %o0 + wr %o0, PSR_ET, %psr + nop + nop + nop + + call fpe_trap + add %sp, FW_REGS_SZ + 8 + SF_REGS_SZ , %o1 + + + RESTORE_ALL /* also returns */ + + + + + + .globl floating_point_exception_trap_ignore + +floating_point_exception_trap_ignore: + + /* see above */ + mov %l2, %l1 + add %l2, 4, %l2 + jmp %l1 + rett %l2 diff --git a/arch/sparc/kernel/traps/trace_trap.S b/arch/sparc/kernel/traps/trace_trap.S new file mode 100644 index 0000000000000000000000000000000000000000..2d60f4c00a147b413291fa7510856d5cc293ebfd --- /dev/null +++ b/arch/sparc/kernel/traps/trace_trap.S @@ -0,0 +1,34 @@ +/** + * @file arch/sparc/kernel/traps/trace_trap.S + * @ingroup stack_trace + + * @brief this is a function that is called by a custom trap handler to perform + * a backtrace + */ + + .text + .align 4 + .globl trace_trap + +#define PSR_ET 0x00000020 /* enable traps field */ + +trace_trap: + /* re-enable traps */ + rd %psr, %l0 + wr %l0, PSR_ET,%psr + nop + nop + nop + + /* %sp of trapped function */ + mov %fp, %o0 + + /* %pc of trapped function is stored in %l1 */ + mov %l1, %o1 + call trace + nop + call die + nop +/* if we wanted to return from this: */ +# jmp %l1 +# rett %l2 diff --git a/arch/sparc/mm/compile.sh b/arch/sparc/mm/compile.sh new file mode 100644 index 0000000000000000000000000000000000000000..d37afae34e2b6834f20f558a35a5e2d6a33eaa93 --- /dev/null +++ b/arch/sparc/mm/compile.sh @@ -0,0 +1 @@ +sparc-elf-gcc *.c -I../include -I../../../include -I../../../include/kernel diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c new file mode 100644 index 0000000000000000000000000000000000000000..d13ad4d4995918b80dd5fc6b620a69197079f4c4 --- /dev/null +++ b/arch/sparc/mm/srmmu.c @@ -0,0 +1,1193 @@ +/** + * + * @file arch/sparc/mm/srmmu.c + * + * + * + * ## Overview + * + * This implements SRMMU functionality + * + * ## Mode of Operation + * + * This is how the srmmu context table works: + * + * "level 0" is the context pointer that is used by the MMU to determine which + * table to use, if a certain context is selected via srmmu_set_ctx() + * Since level 0 corresponds to a 4 GiB page mapping, it is also possible to + * use it as a page table entry, i.e. to transparently map the whole 32bit + * address through the MMU, you can use the context table pointer (ctp) as + * page table entry (pte): + * + * srmmu_set_ctx_tbl_addr((unsigned long) _mmu_ctp); + * _mmu_ctp[0] = SRMMU_PTE(0x0, (SRMMU_CACHEABLE | SRMMU_ACC_S_RWX_2)); + * srmmu_set_ctx(0); + * + * so the virtual address mapping starts at physical address address 0x0. + * Note: accessing memory regions not actually mapped in hardware will + * subsequently raise data access exceptions! + * + * + * If you are only interested in protecting larger chunks of memory, i.e. + * against accidential R/W/X operations, you can point a level 0 ctp to a + * level 1 table by setting it up as a page table descriptor (ptd): + * + * _mmu_ctp[0] = SRMMU_PTD((unsigned long) &ctx->tbl.lvl1[0]); + * + * Let's say you want to map a 16 MiB chunk starting at 0x40000000 + * transparently through the MMU, you would then set lvl 1 table entry + * 0x40 (64), since lvl1 tables addresses are formed by the two highest-order + * bytes: + * ctx->tbl.lvl1[0x40] = + * SRMMU_PTE(0x40000000, (SRMMU_CACHEABLE | SRMMU_ACC_S_RWX_2)); + * + * an then select the MMU context. + * + * From the above example, you can already guess how the MMU context tables + * work. If you access address 0x40000000, the MMU will take the two highest + * order bytes and use them as an offset into the level 1 table. If it finds + * that the entry is marked as pte, it takes the corresponding two highest-order + * address bytes in the entry and replaces the bytes in your address with those + * bytes. + * + * Say you try to access 0x40123456, the MMU will strip 0x40 and look into the + * table, where (in our case) finds the referenced physical address bytes to be + * 0x40 as well, and will hence take your 6 lower order bytes 0x123456 and put + * 0x40 in front again, resulting in the translated address 0x40123456. + * + * Let's say you created a second mapping to the same physical address: + * + * ctx->tbl.lvl1[0xaa] = + * SRMMU_PTE(0x40000000, (SRMMU_CACHEABLE | SRMMU_ACC_S_RWX_2)); + * + * and try to access 0xaa123456. The MMU will then replace 0xaa with 0x40, and + * you'll get the same physical address again! + * + * Note: You could actually use this to map flat physical memory into a + * virtual "circular" topology by repeating the mapping for a range of virtual + * address space and use an automatically underflowing buffer index + * (i.e. a 8 or 16 bit integer type). + * + * Now, if you wanted to map a 16 MiB chunk into smaller sections, rather than + * setting up the entry as a pte, you would configure it as a ptd that + * references a lvl 2 table, e.g. + * + * ctx->tbl.lvl1[0x40] = SRMMU_PTD((unsigned long) &ctx->tbl->lvl2[0]); + * + * and then set up the lvl 2 table entries, which reference 64 chunks of 256 KiB + * each, which means that the next 6 _bits_ in the address are used by the MMU + * for the offset into the table, i.e. (addr & 0x00FC0000 ) >> 18 + * + * If you configure a mapping for 0xaa04xxxx, you would set: + * ctx->tbl.lvl2[0xaa][0x01] = + * SRMMU_PTE(0x40000000, (SRMMU_CACHEABLE | SRMMU_ACC_S_RWX_2)); + * + * and then access 0xaa04cdef, the MMU will strip the upper 14 bits from that + * address and replace them with the upper 14 bits of 0x40000000 and hence + * reference a physical address of 0x4000cdef + * + * The same applies to level 3 contexts, which are 4 kiB chunks, so here the + * MMU replaces the upper 20 _bits_ of your address. + * + * + * @note Unused context table entries must be set accordingly, otherwise + * the MMU might try to establish a mapping, so initialise all context table + * entries to SRMMU_ENTRY_TYPE_INVALID. + * + * + * ## Error Handling + * + * TODO + * + * ## Notes + * + * We only allow large (16 MiB) and small (4 kiB) page mappings. The intermediate + * (256 kiB) level is always explcitly mapped via small pages. + * + * The level 1 context table is always allocated, level 2 and 3 tables are not + * and are allocated/released as needed. The allocation is done via a low-level + * allocator, that is specified via the srmmu_init_ctx() call and may be + * different for each context in the context list. The alloc() call is expected + * to either return a valid buffer of at least the size requested, or NULL on + * error. + * + * If a mapping is released, all allocations are released by walking the tree, + * since we don't track them separately. If it turns out that this is done + * often, it might for performance reasons be prefereable to maintain SLABS of + * page tables, i.e. one per mapping size, where we take and return them as + * needed. We could also track them of course... + * + * + * @todo this needs some cleanup/streamlining/simplification + */ + + +#include <kernel/kernel.h> +#include <kernel/printk.h> +#include <list.h> + +#include <page.h> +#include <mm.h> +#include <srmmu.h> +#include <srmmu_access.h> +#include <errno.h> + + + +static unsigned long *_mmu_ctp; /* table of mmu context table pointers */ +static unsigned long _mmu_num_ctp; /* number of contexts in the ctp */ +static struct mmu_ctx *_mmu_ctx; /* the currently configured context */ + + + +/** + * The magic entry is placed one word prior to the start of the aligned + * address. If the allocation address coincides with the aligned address, + * a magic marker is placed in the word following the end of the table. + * since the offset is at most (SRMMU_TBL_LVL_1_ALIGN - 1), this is the + * mask that is used for the _negative_ offset to the allocated address. + * + * @note we can remove this once we have chunk_alloc_aligned() (which will + * subsequently require a function bootmem_alloc_aligned()) + * + * @note the above needs another solution to detect empty subtables + */ + +/* XXX: it works for now, but this really needs some improvement, at least + * a checkbit or so... + */ +#define MAGIC_MARKER (0xdeadda7a & ~(SRMMU_TBL_LVL_1_ALIGN - 1)) +#define MAGIC(offset) (((offset) & (SRMMU_TBL_LVL_1_ALIGN - 1)) | MAGIC_MARKER) +#define OFFSET_FROM_MAGIC(x) ((x) & (SRMMU_TBL_LVL_1_ALIGN - 1)) +#define IS_MAGIC(x) (((x) & ~(SRMMU_TBL_LVL_1_ALIGN - 1)) == MAGIC_MARKER) + +#define MAGIC_REF_CNT(x) (x) +#define REF_FROM_MAGIC(x) (x) + +#define MAGIC_WORDS 2 + +/** + * table level lvl1 is always SRMMU_SIZE_TBL_LVL_1 entries + * depending on the number of forward references (ptd's) into the + * higher level tables, the following applies: + * + * lvl2: at most SRMMU_SIZE_TBL_LVL_1 * SRMMU_SIZE_TBL_LVL_2 + * lvl3: at most n_lvl2 * SRMMU_SIZE_TBL_LVL_3 + * + */ + +struct mmu_ctx_tbl { + + struct srmmu_ptde *lvl1; + + struct srmmu_ptde *lvl2[SRMMU_SIZE_TBL_LVL_1][SRMMU_SIZE_TBL_LVL_2]; + struct srmmu_ptde *lvl3[SRMMU_SIZE_TBL_LVL_1][SRMMU_SIZE_TBL_LVL_2]; + + + unsigned long n_lvl2; + unsigned long n_lvl3; +}; + +struct mmu_ctx { + struct mmu_ctx_tbl tbl; + + unsigned int ctx_num; + + void *(*alloc)(size_t size); + void (*free) (void *addr); + + + /* lower and upper boundary of unusable memory space */ + unsigned long lo_res; + unsigned long hi_res; + + struct list_head node; +}; + + +static struct list_head ctx_free; +static struct list_head ctx_used; + + + + + +static inline void del_mmu_ctx_from_list(struct mmu_ctx *ctx) +{ + list_del(&ctx->node); +} + +static inline void add_mmu_ctx_to_list(struct mmu_ctx *ctx, + struct list_head *list) +{ + list_add_tail(&ctx->node, list); +} + +static inline void mmu_ctx_add_free(struct mmu_ctx *ctx) +{ + list_add_tail(&ctx->node, &ctx_free); +} + +static inline void mmu_ctx_add_used(struct mmu_ctx *ctx) +{ + list_add_tail(&ctx->node, &ctx_used); +} + +static inline void mmu_ctx_move_free(struct mmu_ctx *ctx) +{ + list_move_tail(&ctx->node, &ctx_free); +} + +static inline void mmu_ctx_move_used(struct mmu_ctx *ctx) +{ + list_move_tail(&ctx->node, &ctx_used); +} + +static inline void mmu_init_ctx_lists(void) +{ + INIT_LIST_HEAD(&ctx_free); + INIT_LIST_HEAD(&ctx_used); +} + +static inline struct mmu_ctx *mmu_find_ctx(unsigned int ctx_num) +{ + struct mmu_ctx *p_elem; + + + if (ctx_num > _mmu_num_ctp) + return NULL; + + list_for_each_entry(p_elem, &ctx_used, node) { + if (p_elem->ctx_num == ctx_num); + return p_elem; + } + + list_for_each_entry(p_elem, &ctx_free, node) { + if (p_elem->ctx_num == ctx_num); + return p_elem; + } + + + return NULL; +} + + +/** + * @brief set the current working context + */ + +static inline int mmu_set_current_ctx(unsigned int ctx_num) +{ + struct mmu_ctx *ctx; + + ctx = mmu_find_ctx(ctx_num); + if (!ctx) + return -EINVAL; + + _mmu_ctx = ctx; + + return 0; +} + + +/** + * @brief get the current working context + */ + +static inline struct mmu_ctx *mmu_get_current_ctx(void) +{ + return _mmu_ctx; +} + + +/** + * @brief add a new working context + */ + +static inline void mmu_add_ctx(unsigned long ptd) +{ + _mmu_ctp[_mmu_num_ctp] = ptd; + _mmu_num_ctp++; +} + +/** + * @brief get the current number of registered contexts + * + * @returns the current number of registered contexts + */ + +static inline unsigned int mmu_get_num_ctx(void) +{ + return _mmu_num_ctp; +} + +/** + * @brief set the context table pointer + * @param addr a pointer to the table + */ + +static inline void mmu_set_ctp(unsigned long *addr) +{ + _mmu_ctp = addr; + srmmu_set_ctx_tbl_addr((unsigned long) _mmu_ctp); +} + + +/** + * @brief 1:1 map the full 32 bit space + * + * @param ctx_num the context number to configure + */ + +static inline void mmu_set_map_full(unsigned int ctx_num) +{ + if (ctx_num >= SRMMU_CONTEXTS) + return; + + _mmu_ctp[ctx_num] = SRMMU_PTE(0x0, + (SRMMU_CACHEABLE | SRMMU_ACC_S_RWX_2)); +} + + +/** + * @brief initialise all lvl1 table entries as invalid + */ + +static void mmu_set_lvl1_tbl_invalid(struct srmmu_ptde *ptde) +{ + int i; + + for (i = 0; i < SRMMU_SIZE_TBL_LVL_1; i++) + ptde[i].pte = SRMMU_ENTRY_TYPE_INVALID; +} + + +/** + * @brief initialise all lvl2 table entries as invalid + */ + +static void mmu_set_lvl2_tbl_invalid(struct srmmu_ptde *ptde) +{ + int i; + + for (i = 0; i < SRMMU_SIZE_TBL_LVL_2; i++) + ptde[i].pte = SRMMU_ENTRY_TYPE_INVALID; +} + + +/** + * @brief initialise all lvl3 table entries as invalid + */ + +static void mmu_set_lvl3_tbl_invalid(struct srmmu_ptde *ptde) +{ + int i; + + for (i = 0; i < SRMMU_SIZE_TBL_LVL_3; i++) + ptde[i].pte = SRMMU_ENTRY_TYPE_INVALID; +} + + +/** + * @brief increment page reference counter + * + * @returns current reference count + */ + +static unsigned long mmu_inc_ref_cnt(struct srmmu_ptde *tbl, + unsigned long tbl_size) +{ + unsigned long cnt; + + unsigned long *ptd; + + + if (IS_MAGIC(tbl[-1].ptd)) + ptd = &tbl[-2].ptd; + else if (IS_MAGIC(tbl[tbl_size].ptd)) + ptd = &tbl[tbl_size + 1].ptd; + else + BUG(); + + + cnt = REF_FROM_MAGIC((*ptd)) + 1; + (*ptd) = MAGIC_REF_CNT(cnt); + + return cnt; +} + + +/** + * @brief decrement page reference counter + * + * @returns current reference count + */ + +static unsigned long mmu_dec_ref_cnt(struct srmmu_ptde *tbl, + unsigned long tbl_size) +{ + unsigned long cnt; + + unsigned long *ptd; + + + if (IS_MAGIC(tbl[-1].ptd)) + ptd = &tbl[-2].ptd; + else if (IS_MAGIC(tbl[tbl_size].ptd)) + ptd = &tbl[tbl_size + 1].ptd; + else + BUG(); + + + cnt = REF_FROM_MAGIC((*ptd)); + if (!cnt) + BUG(); + + cnt = cnt - 1; + + (*ptd) = MAGIC_REF_CNT(cnt); + + return cnt; +} + + +/** + * @brief allocate and align a SRMMU page table + */ + +static struct srmmu_ptde *mmu_alloc_tbl(struct mmu_ctx *ctx, + unsigned long tbl_size) +{ + int offset; + + struct srmmu_ptde *ptde; + struct srmmu_ptde *ptde_align; + + + ptde = (struct srmmu_ptde *) ctx->alloc(2 * tbl_size); + if (!ptde) + return NULL; + + ptde_align = ALIGN_PTR(ptde, tbl_size); + + /* store positive offset as a ptd */ + offset = (int) ptde_align - (int) ptde; + if (offset > MAGIC_WORDS) { + ptde_align[-1].ptd = MAGIC(offset); + ptde_align[-2].ptd = MAGIC_REF_CNT(0); + } else { + ptde_align[tbl_size].ptd = MAGIC(0); + ptde_align[tbl_size + 1].ptd = MAGIC_REF_CNT(0); + } + + return ptde_align; +} + + +/** + * @brief free a table + */ + +static void mmu_free_tbl(struct mmu_ctx *ctx, struct srmmu_ptde *tbl, + unsigned long tbl_size) +{ + unsigned long ptd; + + void *addr; + + + if (IS_MAGIC(tbl[-1].ptd)) + ptd = tbl[-1].ptd; + else if (IS_MAGIC(tbl[tbl_size].ptd)) + ptd = tbl[tbl_size].ptd; + else + BUG(); + + addr = (void *) ((int) tbl - OFFSET_FROM_MAGIC(ptd)); + + ctx->free(addr); +} + +/** + * @brief allocate a level 1 table + */ + +static struct srmmu_ptde *mmu_alloc_lvl1_tbl(struct mmu_ctx *ctx) +{ + struct srmmu_ptde *ptde; + + + ptde = mmu_alloc_tbl(ctx, SRMMU_TBL_LVL_1_ALIGN); + if (!ptde) + return NULL; + + mmu_set_lvl1_tbl_invalid(ptde); + + return ptde; +} + + +/** + * @brief look up a level 2 table by virtual address + */ + +static struct srmmu_ptde *mmu_find_tbl_lvl2(struct mmu_ctx *ctx, + unsigned long va) +{ + unsigned long page; + + struct srmmu_ptde *lvl1; + + + lvl1 = ctx->tbl.lvl1; + + page = SRMMU_LVL1_GET_TBL_OFFSET(va); + + if (lvl1[page].pte & SRMMU_ENTRY_TYPE_PT_ENTRY) + return NULL; + + if (lvl1[page].pte == SRMMU_ENTRY_TYPE_INVALID) + return NULL; + + return (struct srmmu_ptde *) SRMMU_PTD_TO_ADDR(lvl1[page].ptd); +} + + +/** + * @brief look up a level 3 table by virtual address + */ + +static struct srmmu_ptde *mmu_find_tbl_lvl3(struct mmu_ctx *ctx, + unsigned long va) +{ + unsigned long page; + + struct srmmu_ptde *lvl2; + + + lvl2 = mmu_find_tbl_lvl2(ctx, va); + if (!lvl2) + goto no_table; + + page = SRMMU_LVL2_GET_TBL_OFFSET(va); + + if (lvl2[page].pte & SRMMU_ENTRY_TYPE_PT_ENTRY) + goto no_table; + + if (lvl2[page].pte == SRMMU_ENTRY_TYPE_INVALID) + goto no_table; + + + return (struct srmmu_ptde *) SRMMU_PTD_TO_ADDR(lvl2[page].ptd); + +no_table: + return NULL; +} + + +/** + * @brief if necessary, allocate a new level 2 table + * + * @returns 0 on success, otherwise error + */ + +static int mmu_need_lvl2_tbl(struct mmu_ctx *ctx, unsigned long va) +{ + unsigned long pg_lvl1; + + struct srmmu_ptde *ptde; + + + pg_lvl1 = SRMMU_LVL1_GET_TBL_OFFSET(va); + + /* lvl 1 page in use, no can do */ + if (ctx->tbl.lvl1[pg_lvl1].pte & SRMMU_ENTRY_TYPE_PT_ENTRY) + return -EINVAL; + + if (ctx->tbl.lvl1[pg_lvl1].pte != SRMMU_ENTRY_TYPE_INVALID) + return 0; + + + ptde = mmu_alloc_tbl(ctx, SRMMU_TBL_LVL_2_ALIGN); + if (!ptde) + return -ENOMEM; + + mmu_set_lvl2_tbl_invalid(ptde); + + /* point entry to the new lvl2 table */ + ctx->tbl.lvl1[pg_lvl1].ptd = SRMMU_PTD((unsigned long) ptde); + + /* we don't count lvl 2 references in lvl1 tables */ + + pr_debug("SRMMU: allocated new lvl2 table\n"); + + return 0; +} + + +/** + * @brief if necessary, allocate a new level 2 table + * + * @returns 0 on success, otherwise error + */ + +static int mmu_need_lvl3_tbl(struct mmu_ctx *ctx, unsigned long va) +{ + int ret; + + unsigned long pg_lvl2; + + struct srmmu_ptde *lvl2; + struct srmmu_ptde *lvl3; + + + /* see if we need a lvl3 table */ + lvl3 = mmu_find_tbl_lvl3(ctx, va); + if (lvl3) + return 0; + + /* we might need a lvl2 table first */ + lvl2 = mmu_find_tbl_lvl2(ctx, va); + if (!lvl2) { + if((ret = mmu_need_lvl2_tbl(ctx, va))) + return ret; + + lvl2 = mmu_find_tbl_lvl2(ctx, va); + } + + /* allocate a lvl3 table */ + lvl3 = mmu_alloc_tbl(ctx, SRMMU_TBL_LVL_3_ALIGN); + if (!lvl3) + return -ENOMEM; + + mmu_set_lvl3_tbl_invalid(lvl3); + + /* point lvl2 entry to the new lvl3 table */ + pg_lvl2 = SRMMU_LVL2_GET_TBL_OFFSET(va); + lvl2[pg_lvl2].ptd = SRMMU_PTD((unsigned long) lvl3); + + mmu_inc_ref_cnt(lvl2, SRMMU_TBL_LVL_2_ALIGN); + + pr_debug("SRMMU: allocated new lvl3 table\n"); + + return 0; +} + + +/** + * @brief create a new SRMMU context + * + * @param alloc a pointer to a function we can use to allocate MMU context + * tables + * @param free a pointer to a function that returns MMU context tables to + * the allocator + * + * + * @return >= 0: number of created context, otherwise error + * + * @note allows up to SRMMU_CONTEXTS + */ + +int srmmu_new_ctx(void *(*alloc)(size_t size), void (*free)(void *addr)) +{ + struct mmu_ctx *ctx; + + + if (mmu_get_num_ctx() > SRMMU_CONTEXTS) + return -ENOMEM; + + ctx = (struct mmu_ctx *) alloc(sizeof(struct mmu_ctx)); + if (!ctx) + return -ENOMEM; + + + ctx->alloc = alloc; + ctx->free = free; + + ctx->tbl.lvl1 = mmu_alloc_lvl1_tbl(ctx); + if (!ctx->tbl.lvl1) { + free(ctx); + return -ENOMEM; + } + + ctx->ctx_num = mmu_get_num_ctx(); + + mmu_add_ctx(SRMMU_PTD((unsigned long) &ctx->tbl.lvl1[0])); + + mmu_ctx_add_free(ctx); + + return ctx->ctx_num; +} + + +/** + * @brief release lvl3 pages + * + * @returns 1 if lvl 3 directory still exists, 0 if it was released + */ + +static int srmmu_release_lvl3_pages(struct mmu_ctx *ctx, + unsigned long va, unsigned long va_end, + void (*free_page)(void *addr)) +{ + unsigned long page; + + struct srmmu_ptde *lvl3; + + + + lvl3 = mmu_find_tbl_lvl3(ctx, va); + + if ((va + SRMMU_SIZE_TBL_LVL_3 * SRMMU_SMALL_PAGE_SIZE ) < va_end) + va_end = va + SRMMU_SIZE_TBL_LVL_3 * SRMMU_SMALL_PAGE_SIZE; + + for ( ; va < va_end; va += SRMMU_SMALL_PAGE_SIZE) { + + page = SRMMU_LVL3_GET_TBL_OFFSET(va); + + /* it is quite possible that this is not mapped */ + if (lvl3[page].pte == SRMMU_ENTRY_TYPE_INVALID) { + pr_debug("SRMMU: tried to release address 0x%08x, but " + "lvl3 page was marked invalid, ignoring.\n", + va); + continue; + } + + if (lvl3[page].pte & SRMMU_ENTRY_TYPE_PT_ENTRY) { + + free_page((void *) SRMMU_PTE_TO_ADDR(lvl3[page].pte)); + + pr_debug("SRMMU: freed physical page %lx\n", + SRMMU_PTE_TO_ADDR(lvl3[page].pte)); + + lvl3[page].pte = SRMMU_ENTRY_TYPE_INVALID; + + if (!mmu_dec_ref_cnt(lvl3, SRMMU_TBL_LVL_3_ALIGN)) { + mmu_free_tbl(ctx, lvl3, SRMMU_TBL_LVL_3_ALIGN); + pr_debug("SRMMU: released lvl3 table\n"); + + return 0; + } + } + + + } + + return 1; +} + + +/** + * @brief recursively release lvl2 pages + * + * @returns 1 if lvl 2 directory still exists, 0 if it was released + */ + +static int srmmu_release_lvl2_pages(struct mmu_ctx *ctx, + unsigned long va, unsigned long va_end, + void (*free_page)(void *addr)) +{ + unsigned long page; + + struct srmmu_ptde *lvl2; + + + lvl2 = mmu_find_tbl_lvl2(ctx, va); + + if ((va + SRMMU_SIZE_TBL_LVL_2 * SRMMU_MEDIUM_PAGE_SIZE ) < va_end) + va_end = va + SRMMU_SIZE_TBL_LVL_2 * SRMMU_MEDIUM_PAGE_SIZE; + + for ( ; va < va_end; va += SRMMU_MEDIUM_PAGE_SIZE) { + + page = SRMMU_LVL2_GET_TBL_OFFSET(va); + + /* medium mapping not used, its all done via small pages */ + + /* it is quite possible that this is not mapped */ + if (lvl2[page].pte == SRMMU_ENTRY_TYPE_INVALID) { + pr_debug("SRMMU: tried to release address 0x%08x, but " + "lvl2 page was marked invalid, ignoring.\n", + va); + continue; + } + + if (!srmmu_release_lvl3_pages(ctx, va, va_end, free_page)) { + lvl2[page].pte = SRMMU_ENTRY_TYPE_INVALID; + + pr_debug("SRMMU: lvl3 table unreferenced\n"); + + + /* no more subtables, free ourselves */ + if (!mmu_dec_ref_cnt(lvl2, SRMMU_TBL_LVL_2_ALIGN)) { + mmu_free_tbl(ctx, lvl2, SRMMU_TBL_LVL_2_ALIGN); + pr_debug("SRMMU: released lvl2 table\n"); + + return 0; + } + } + } + + return 1; +} + + +/** + * @brief recursively release lvl1 pages + */ + +static void srmmu_release_lvl1_pages(struct mmu_ctx *ctx, + unsigned long va, unsigned long va_end, + void (*free_page)(void *addr)) +{ + + unsigned long page; + + struct srmmu_ptde *lvl1; + + + lvl1 = ctx->tbl.lvl1; + + for ( ; va < va_end; va += SRMMU_LARGE_PAGE_SIZE) { + + page = SRMMU_LVL1_GET_TBL_OFFSET(va); + + /* large mapping */ + if (lvl1[page].pte & SRMMU_ENTRY_TYPE_PT_ENTRY) { + free_page((void *) SRMMU_PTE_TO_ADDR(lvl1[page].pte)); + lvl1[page].pte = SRMMU_ENTRY_TYPE_INVALID; + continue; + } + + if (lvl1[page].pte == SRMMU_ENTRY_TYPE_INVALID) { + pr_debug("SRMMU: tried to release address 0x%08x, but " + "lvl1 page was marked invalid, skipping.\n", + va); + continue; + } + + if (!srmmu_release_lvl2_pages(ctx, va, va_end, free_page)) { + lvl1[page].pte = SRMMU_ENTRY_TYPE_INVALID; + pr_debug("SRMMU: lvl2 table unreferenced\n"); + } + } +} + + +/** + * @brief recursively release pages by address + * + * @param ctx_num the context number + * @param va the start virtual address + * @param va_end the end virtual address + * @parma free_page a function pages are returned to + * + * @note the addresses are assumed aligned to the page size + */ + +void srmmu_release_pages(unsigned long ctx_num, + unsigned long va, unsigned long va_end, + void (*free_page)(void *addr)) +{ + struct mmu_ctx *ctx; + + if (va_end <= va) + return; + + ctx = mmu_find_ctx(ctx_num); + + srmmu_release_lvl1_pages(ctx, va, va_end, free_page); +} + + +/** + * @brief map a 16 MiB page from a virtual address to a physical address + * + * @param ctx_num the context number to do the mapping in + * @param va the virtual address + * @param pa the physical address + * @parma perm the permissions to configure for the page + * + * @note the addresses are assumed to be aligned to the requested page size + * + * @return 0 on success + */ + +int srmmu_do_large_mapping(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long perm) +{ + size_t page; + + struct mmu_ctx *ctx; + + + if (mmu_get_num_ctx() < ctx_num) + return -EINVAL; + + + ctx = mmu_find_ctx(ctx_num); + + if (!ctx) + return -ENOMEM; + + page = SRMMU_LVL1_GET_TBL_OFFSET(va); + ctx->tbl.lvl1[page].pte = SRMMU_PTE(pa, perm); + + leon_flush_cache_all(); + leon_flush_tlb_all(); + + pr_debug("SRMMU: mapped 16 MiB page from 0x%08x to 0x%08x " + "of context %d, permissions 0x%08x\n", + va, pa, ctx->ctx_num, perm); + + return 0; +} + + +/** + * @brief map a virtual address range to a physical address range in 16MiB pages + * + * @param ctx_num the context number to do the mapping in + * @param va the virtual address + * @param pa the physical address + * @param pages the number of SRMMU_LARGE_PAGE_SIZE pages + * @parma perm the permissions to configure for the pages + * + * @return 0 on success + */ + +int srmmu_do_large_mapping_range(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long num_pages, unsigned long perm) +{ + int ret; + + unsigned long i; + + + if (mmu_get_num_ctx() < ctx_num) + return -EINVAL; + + + for (i = 0; i < num_pages; i++) { + ret = srmmu_do_large_mapping(ctx_num, + va + i * SRMMU_LARGE_PAGE_SIZE, + pa + i * SRMMU_LARGE_PAGE_SIZE, + perm); + if (ret) { + pr_crit("SRMMU: failed to map %d pages from " + "[0x%08lx, 0x%08lx] to [0x%08lx, 0x%08lx]\n", + (num_pages - i), + va + i * SRMMU_LARGE_PAGE_SIZE, + va + num_pages * SRMMU_LARGE_PAGE_SIZE, + pa + i * SRMMU_LARGE_PAGE_SIZE, + pa + num_pages * SRMMU_LARGE_PAGE_SIZE); + return ret; + } + } + + return 0; +} + + + +/** + * @brief map a 4 kiB page from a virtual address to a physical address + * + * @param ctx_num the context number to do the mapping in + * @param va the virtual address + * @param pa the physical address + * @parma perm the permissions to configure for the page + * + * @note the addresses are assumed to be aligned to the requested page size + * + * @return 0 on success + */ + +int srmmu_do_small_mapping(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long perm) +{ + int ret; + + unsigned long pg; + struct mmu_ctx *ctx; + struct srmmu_ptde *lvl3; + + + + if (mmu_get_num_ctx() < ctx_num) + return -EINVAL; + + ctx = mmu_find_ctx(ctx_num); + + if (!ctx) + return -ENOMEM; + + + ret = mmu_need_lvl3_tbl(ctx, va); + if (ret) + return ret; + + lvl3 = mmu_find_tbl_lvl3(ctx, va); + + pg = SRMMU_LVL3_GET_TBL_OFFSET(va); + + lvl3[pg].pte = SRMMU_PTE(pa, perm); + + mmu_inc_ref_cnt(lvl3, SRMMU_TBL_LVL_3_ALIGN); + + leon_flush_cache_all(); + leon_flush_tlb_all(); + + pr_debug("SRMMU: mapped 4 kiB page from 0x%08x to 0x%08x " + "of context %d, permissions 0x%08x\n", + va, pa, ctx->ctx_num, perm); + + return 0; +} + +/** + * @brief map a virtual address range to a physical address range in 4kiB pages + * + * @param ctx_num the context number to do the mapping in + * @param va the virtual address + * @param pa the physical address + * @param pages the number of SRMMU_SMALL_PAGE_SIZE pages + * @parma perm the permissions to configure for the pages + * + * @return 0 on success + */ + +int srmmu_do_small_mapping_range(unsigned long ctx_num, + unsigned long va, unsigned long pa, + unsigned long num_pages, unsigned long perm) +{ + int ret; + + unsigned long i; + + + if (mmu_get_num_ctx() < ctx_num) + return -EINVAL; + + + for (i = 0; i < num_pages; i++) { + ret = srmmu_do_small_mapping(ctx_num, + va + i * SRMMU_SMALL_PAGE_SIZE, + pa + i * SRMMU_SMALL_PAGE_SIZE, + perm); + if (ret) { + pr_crit("SRMMU: failed to map %d pages from " + "[0x%08lx, 0x%08lx] to [0x%08lx, 0x%08lx]\n", + (num_pages - i), + va + i * SRMMU_SMALL_PAGE_SIZE, + va + num_pages * PAGE_SIZE, + pa + i * SRMMU_SMALL_PAGE_SIZE, + pa + num_pages * PAGE_SIZE); + return ret; + } + } + + return 0; +} + + +/** + * @brief select a MMU context + * + * @param ctx_num the context number to select + * + * @return 0 on success, otherwise error + */ + +int srmmu_select_ctx(unsigned long ctx_num) +{ + if (mmu_get_num_ctx() < ctx_num) + return -EINVAL; + + if(mmu_set_current_ctx(ctx_num)) + return -EINVAL; + + srmmu_set_ctx(ctx_num); + + leon_flush_cache_all(); + leon_flush_tlb_all(); + + return 0; +} + + +/** + * @brief enable MMU operation + */ + +void srmmu_enable_mmu(void) +{ + srmmu_set_mmureg(0x00000001); + leon_flush_cache_all(); +} + + +/** + * @brief basic initialisation of the MMU + * + * @param alloc a pointer to a function we can use to allocate MMU context + * tables + * @param free a pointer to a function that returns MMU context tables to + * the allocator + * + * @return 0 on success, otherwise error + * + * @note requires at least one mapping and a call to srmmu_enable_mmu() + * to function + */ + +int srmmu_init(void *(*alloc)(size_t size), void (*free)(void *addr)) +{ + int ret; + + unsigned int i; + + unsigned long *ctp; + + struct mmu_ctx *ctx; + + const size_t ctp_size = SRMMU_CONTEXTS * sizeof(unsigned long); + + + /* don't call this twice by accident, we don't support multiple + * context table pointer tables + */ + BMP(); + + mmu_init_ctx_lists(); + + /* allocate twice the size of the table, so we can align it to the + * a boundary equal its own size + */ + ctp = (unsigned long *) alloc(2 * ctp_size); + if (!ctp) + return -ENOMEM; + + ctp = ALIGN_PTR(ctp, ctp_size); + + mmu_set_ctp(ctp); + + /* all contexts are invalid by default */ + for (i = 0; i < SRMMU_CONTEXTS; i++) + ctp[i] = SRMMU_ENTRY_TYPE_INVALID; + + + ret = srmmu_new_ctx(alloc, free); + if(ret) + return ret; + + BUG_ON(mmu_set_current_ctx(0)); + + ctx = mmu_get_current_ctx(); + + srmmu_select_ctx(0); + + return 0; +} diff --git a/arch/sparc/mm/srmmu_access.c b/arch/sparc/mm/srmmu_access.c new file mode 100644 index 0000000000000000000000000000000000000000..519f8d40aebd63b429153b8c7c02e4325bbf3e9d --- /dev/null +++ b/arch/sparc/mm/srmmu_access.c @@ -0,0 +1,189 @@ + +/** + * @brief SRMMU register access functions + * @author Armin Luntzer (armin.luntzer@univie.ac.at) + * + * @note only LEON ASI is supported + */ + + + +#include <uapi/asi.h> +#include <srmmu.h> + +#include <kernel/kernel.h> + + +#define MMU_CTRL_REG 0x00000000 /* control register */ +#define MMU_CTXP_REG 0x00000100 /* context pointer */ +#define MMU_CTX_REG 0x00000200 /* context */ +#define MMU_FLTS_REG 0x00000300 /* fault status */ +#define MMU_FLTA_REG 0x00000400 /* fault address */ + + + +/** + * @brief access to the SRMMU control register + * + * @return SR MMU control register contents + */ + +unsigned int srmmu_get_mmu_ctrl(void) +{ + unsigned int mmu_ctrl; + + __asm__ __volatile__( + "lda [%%g0] " __stringify(ASI_LEON_MMUREGS) ", %0\n\t" + : "=r" (mmu_ctrl) + : + :"memory"); + + return mmu_ctrl; +} + + +/** + * @brief access to the SRMMU fault status register + * + * @return SRMMU fault status register contents + */ + +struct srmmu_fault_status srmmu_get_mmu_fault_status(void) +{ + struct srmmu_fault_status mmu_fault_status; + + __asm__ __volatile__( + "lda [%1] " __stringify(ASI_LEON_MMUREGS) ", %0\n\t" + :"=r" (mmu_fault_status) + :"r" (MMU_FLTS_REG) + :"memory"); + + return mmu_fault_status; +} + +/** + * @brief access to the SRMMU fault address register + * + * @return SRMMU fault address register contents + */ + +unsigned int srmmu_get_mmu_fault_address(void) +{ + unsigned int mmu_fault_addr; + + __asm__ __volatile__( + "lda [%1] " __stringify(ASI_LEON_MMUREGS) ", %0\n\t" + :"=r" (mmu_fault_addr) + :"r" (MMU_FLTA_REG) + :"memory"); + + + return mmu_fault_addr; +} + + +/** + * @brief get the SRMMU implementation + * + * @return the implementation identifier + */ + +unsigned int srmmu_get_mmu_impl(void) +{ + return (srmmu_get_mmu_ctrl() & SRMMU_CTRL_IMPL_MASK) >> + SRMMU_CTRL_IMPL_SHIFT; +} + + +/** + * @brief get the SRMMU implementation + * + * @return the implementation version + */ + +unsigned int srmmu_get_mmu_ver(void) +{ + return (srmmu_get_mmu_ctrl() & SRMMU_CTRL_VER_MASK) >> + SRMMU_CTRL_VER_SHIFT; +} + + +/** + * @brief set the context table address in the MMU + * + * @param addr the address of the context table + * + * TODO: clean up magic + */ + +void srmmu_set_ctx_tbl_addr(unsigned long addr) +{ + addr = ((addr >> 4) & 0xfffffff0); + + __asm__ __volatile__("sta %0, [%1] %2\n\t" + "flush \n\t" : : + "r" (addr), "r" (MMU_CTXP_REG), + "i" (ASI_LEON_MMUREGS) : + "memory"); +} + + +/** + * @brief select the MMU contest + * + * @param ctx the context to select + * + * TODO: clean up magic + */ + +void srmmu_set_ctx(unsigned int ctx) +{ + __asm__ __volatile__("sta %0, [%1] %2\n\t" : : + "r" (ctx), "r" (MMU_CTX_REG), + "i" (ASI_LEON_MMUREGS) : "memory"); +} + + + +/** + * TODO + */ + +void srmmu_set_mmureg(unsigned long regval) +{ + __asm__ __volatile__( + "sta %0, [%%g0] " __stringify(ASI_LEON_MMUREGS) "\n\t" + : + : "r" (regval) + :"memory"); + +} + + +/** + * @brief flush all leon caches + + * TODO: clean up magic, should be part of leon asm + */ + +void leon_flush_cache_all(void) +{ +#if 0 /* bug on MPPB leon 2? */ + __asm__ __volatile__(" flush "); /*iflush */ + __asm__ __volatile__("sta %%g0, [%%g0] %0\n\t" : : + "i"(ASI_LEON_DFLUSH) : "memory"); +#endif +} + + +/** + * @brief flush the the transation lookaside buffer + * + * TODO: clean up magic, should be part of leon asm + */ +void leon_flush_tlb_all(void) +{ + leon_flush_cache_all(); + __asm__ __volatile__("sta %%g0, [%0] %1\n\t" : : "r"(0x400), + "i"(ASI_LEON_MMUFLUSH) : "memory"); +} diff --git a/include/asm-generic/io.h b/include/asm-generic/io.h new file mode 100644 index 0000000000000000000000000000000000000000..9e9cf3972678d8b1ecbe6e332826cbf74b8380d6 --- /dev/null +++ b/include/asm-generic/io.h @@ -0,0 +1,105 @@ +/** + * @file include/asm-generic/io.h + * + * @copyright GPLv2 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * +* @brief a collection of accessor functions and macros to perform unbuffered + * access to memory or hardware registers (generic variant) + * + * @note same conventions as linux kernel (see include/asm-generic/io.h) + * + * @todo since we really need only big endian functions for now, we don't do + * byte swaps (also we don't really need the functions in this file at + * this time) + */ + +#ifndef _ASM_GENERIC_IO_H_ +#define _ASM_GENERIC_IO_H_ + +#include <stdint.h> + + +#ifndef __raw_readb +#define __raw_readb __raw_readb +static inline uint8_t __raw_readb(const volatile void *addr) +{ + return *(const volatile uint8_t *)addr; +} +#endif + +#ifndef __raw_readw +#define __raw_readw __raw_readw +static inline uint16_t __raw_readw(const volatile void *addr) +{ + return *(const volatile uint16_t *)addr; +} +#endif + +#ifndef __raw_readl +#define __raw_readl __raw_readl +static inline uint32_t __raw_readl(const volatile void *addr) +{ + return *(const volatile uint32_t *)addr; +} +#endif + +#ifndef __raw_writeb +#define __raw_writeb __raw_writeb +static inline void __raw_writeb(uint8_t w, volatile void *addr) +{ + *(volatile uint8_t *)addr = w; +} +#endif + +#ifndef __raw_writew +#define __raw_writew __raw_writew +static inline void __raw_writew(uint16_t w, volatile void *addr) +{ + *(volatile uint16_t *)addr = w; +} +#endif + +#ifndef __raw_writel +#define __raw_writel __raw_writel +static inline void __raw_writel(uint32_t l, volatile void *addr) +{ + *(volatile uint32_t *)addr = l; +} +#endif + + +#ifndef ioread8 +#define ioread8(X) __raw_read8(X) +#endif + +#ifndef iowrite8 +#define iowrite8(X) __raw_write8(X) +#endif + +#ifndef ioread16be +#define ioread16be(X) __raw_readw(X) +#endif + +#ifndef ioread32be +#define ioread32be(X) __raw_readl(X) +#endif + +#ifndef iowrite16be +#define iowrite16be(val,X) __raw_writew(val,X) +#endif + +#ifndef iowrite32be +#define iowrite32be(val,X) __raw_writel(val,X) +#endif + + +#endif diff --git a/include/chunk.h b/include/chunk.h new file mode 100644 index 0000000000000000000000000000000000000000..91474269300083b74e9ca38d6af9aa45c8309740 --- /dev/null +++ b/include/chunk.h @@ -0,0 +1,31 @@ +/** + * @file include/chunk.h + */ + + +#ifndef _CHUNK_H_ +#define _CHUNK_H_ + + +struct chunk_pool { + struct list_head full; + struct list_head empty; + + unsigned long align; + + void *(*alloc)(size_t size); + void (*free)(void *addr); + size_t (*real_alloc_size)(void *addr); +}; + +void *chunk_alloc(struct chunk_pool *pool, size_t size); + +void chunk_free(struct chunk_pool *pool, void *addr); + +void chunk_pool_init(struct chunk_pool *pool, + unsigned long align, + void *(*alloc)(size_t size), + void (*free)(void *addr), + size_t (*real_alloc_size)(void *addr)); + +#endif /* _CHUNK_H_ */ diff --git a/include/kernel/ar.h b/include/kernel/ar.h index e6bf8b00fa1a0c51b68db14f51dd71d31feadcef..dde2a9d930e22ad04d2026777fb158e6133290bd 100644 --- a/include/kernel/ar.h +++ b/include/kernel/ar.h @@ -45,6 +45,7 @@ struct archive { void ar_list_files(struct archive *a); +void ar_list_symbols(struct archive *a); void *ar_find_file(struct archive *a, const char *name); void *ar_find_symbol(struct archive *a, const char *name); void ar_free(struct archive *a); diff --git a/include/kernel/kernel.h b/include/kernel/kernel.h index 2b5892a7e41f8eea478351be4901a21dba42eb8f..101ffa4aee94ec25517fc501c8d9db0efa3b1036 100644 --- a/include/kernel/kernel.h +++ b/include/kernel/kernel.h @@ -3,9 +3,9 @@ #include <compiler.h> - #define ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) #define ALIGN(x, a) ALIGN_MASK(x, (typeof(x))(a) - 1) +#define ALIGN_PTR(x, a) (typeof(x)) ALIGN((unsigned long) x, a) /* this is a bit crude, but must do for now */ @@ -20,4 +20,35 @@ #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0) +#define offset_of(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +/* linux/kernel.h */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offset_of(type,member) );}) + +/* Indirect stringification. Doing two levels allows the parameter to be a + * macro itself. For example, compile with -DFOO=bar, __stringify(FOO) + * converts to "bar". + */ +__extension__ +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + + + +/* + * Basic Moron Protector (BMP)™ + */ +#define BMP() do { \ + static enum {DISABLED, ENABLED} bmp; \ + \ + if (bmp) \ + BUG(); \ + \ + bmp = ENABLED; \ +} while (0); + + + #endif /* _KERNEL_H_ */ diff --git a/include/kernel/kmem.h b/include/kernel/kmem.h new file mode 100644 index 0000000000000000000000000000000000000000..4332b4035b6d50a3c3a6aa37ed3323279450d760 --- /dev/null +++ b/include/kernel/kmem.h @@ -0,0 +1,18 @@ +/** + * @file include/kernel/kmem.h + */ + +#ifndef _KERNEL_KMEM_H_ +#define _KERNEL_KMEM_H_ + +#include <stddef.h> + +void *kmalloc(size_t size); +void *kcalloc(size_t nmemb, size_t size); +void *krealloc(void *ptr, size_t size); + +void kfree(void *ptr); + +void *kmem_init(void); + +#endif /* _KERNEL_KMEM_H_ */ diff --git a/include/kernel/mm.h b/include/kernel/mm.h index 54284daf118f4f2e25769724fef355a61e68bd9f..d96c9898fc4849549b6d8f503f2ceb34d29722be 100644 --- a/include/kernel/mm.h +++ b/include/kernel/mm.h @@ -40,6 +40,8 @@ unsigned long mm_allocated_blocks(struct mm_pool *mp); bool mm_addr_in_pool(struct mm_pool *mp, void *addr); +unsigned long mm_block_size(struct mm_pool *mp, const void *addr); + int mm_init(struct mm_pool *mp, void *base, size_t pool_size, size_t granularity); diff --git a/include/kernel/module.h b/include/kernel/module.h index c2d7ff629d4cbc8a93b7b0766aea87f00ecb4407..535b82d38806909ff8ea054368e71c234378a336 100644 --- a/include/kernel/module.h +++ b/include/kernel/module.h @@ -47,4 +47,6 @@ int apply_relocate_add(struct elf_module *m, Elf_Rela *rel, Elf_Addr sym); struct module_section *find_mod_sec(const struct elf_module *m, const char *name); +int module_load(struct elf_module *m, void *p); + #endif /* _KERNEL_MODULE_H_ */ diff --git a/include/kernel/page.h b/include/kernel/page.h index 0c1a3a94628934799b04e3fec53029effbd78835..85801cb049eba3773684858b15c2d91a667a0abc 100644 --- a/include/kernel/page.h +++ b/include/kernel/page.h @@ -22,4 +22,7 @@ struct page_map_node { #define PAGE_MAP_MOVE_NODE_AVAIL_THRESH 1 #endif +unsigned long page_map_get_chunk_size(void *addr); + + #endif /* _KERNEL_PAGE_H_ */ diff --git a/include/kernel/sbrk.h b/include/kernel/sbrk.h new file mode 100644 index 0000000000000000000000000000000000000000..b4a97bc21513a9dde79cd32f17cd70e9d2816a3b --- /dev/null +++ b/include/kernel/sbrk.h @@ -0,0 +1,12 @@ +/** + * @file include/kernel/sbrk.h + */ + +#ifndef _KERNEL_SBRK_H_ +#define _KERNEL_SBRK_H_ + +#include <stdint.h> + +void *kernel_sbrk(intptr_t increment); + +#endif /* _KERNEL_SBRK_H_ */ diff --git a/include/kernel/sysctl.h b/include/kernel/sysctl.h index 3f11d1abf7cf7931fbf7e10913b4bb7e51bf0539..d24ea0792e046a295b92cff096bf7052d99b6042 100644 --- a/include/kernel/sysctl.h +++ b/include/kernel/sysctl.h @@ -7,25 +7,11 @@ #include <sys/types.h> #include <list.h> +#include <kernel/kernel.h> #ifdef offsetof #undef offsetof #endif -/* linux/stddef.h */ -#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) - -/* linux/kernel.h */ -#define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) - -/* Indirect stringification. Doing two levels allows the parameter to be a - * macro itself. For example, compile with -DFOO=bar, __stringify(FOO) - * converts to "bar". - */ -__extension__ -#define __stringify_1(x...) #x -#define __stringify(x...) __stringify_1(x) /* sysfs.h, modified */ #define __ATTR(_name, _show, _store) { \ diff --git a/init/main.c b/init/main.c index 6004c42a810378a4d7d26df48dc490a35e1cc91b..eb133e0023c362185ae0ddd96bbde1efdf002bd0 100644 --- a/init/main.c +++ b/init/main.c @@ -10,10 +10,17 @@ #include <kernel/ksym.h> /* lookup_symbol */ +#include <kernel/kernel.h> #include <kernel/printk.h> +#include <kernel/kmem.h> +#include <kernel/sbrk.h> + + +void module_image_load_embedded(void); +void *module_lookup_embedded(char *mod_name); +void *module_lookup_symbol_embedded(char *sym_name); -int module_load(struct elf_module *m, void *p); static void kernel_init(void) { @@ -25,18 +32,29 @@ static void kernel_init(void) int main(void) { + kernel_init(); - struct elf_module m; +#if 0 + { + struct elf_module m; + void *addr; - kernel_init(); + printk("%s at %p\n", "printk", lookup_symbol("printk")); + + module_image_load_embedded(); - printk("%s at %p\n", "printk", lookup_symbol("printk")); - printk("%s at %p\n", "printf", lookup_symbol("printf")); + /* load -binary kernel/test.ko 0xA0000000 */ + /* module_load(&m, (char *) 0xA0000000); */ - module_load(&m, (char *) 0xA0000000); - /* load -binary kernel/test.ko 0xA0000000 */ + addr = kmalloc(4096); + //addr = module_lookup_embedded("testmodule.ko"); + addr = module_lookup_symbol_embedded("somefunction"); + if (addr) + module_load(&m, addr); + } +#endif return 0; } diff --git a/init/modules-image.c b/init/modules-image.c index 51de991cf7de44f8e86fee9767c50662d66eb97b..ae18f7f00d6304148e5d4b16797047a225dc6320 100644 --- a/init/modules-image.c +++ b/init/modules-image.c @@ -2,7 +2,32 @@ * linker references to embedded modules.image */ -extern unsigned char _binary_modules_image_start; -extern unsigned char _binary_modules_image_end; -extern unsigned char _binary_modules_image_size; +#include <kernel/printk.h> +#include <kernel/ar.h> +extern unsigned char _binary_modules_image_start __attribute__((weak)); +extern unsigned char _binary_modules_image_end __attribute__((weak)); +extern unsigned char _binary_modules_image_size __attribute__((weak)); + +struct archive mod_ar; + + +void module_image_load_embedded(void) +{ + ar_load(&_binary_modules_image_start, + (unsigned int)&_binary_modules_image_size, &mod_ar); + + ar_list_files(&mod_ar); + ar_list_symbols(&mod_ar); +} + + +void *module_lookup_symbol_embedded(char *sym_name) +{ + return ar_find_symbol(&mod_ar, sym_name); +} + +void *module_lookup_embedded(char *mod_name) +{ + return ar_find_file(&mod_ar, mod_name); +} diff --git a/kernel/Makefile b/kernel/Makefile index 2bf505fe0d8d401b1bb7d5b688369ff5256e4685..9d67633a7d80f513be3027459ac1e62821b0455c 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -1,5 +1,7 @@ +obj-y += kmem.o obj-y += ksym.o obj-$(CONFIG_KERNEL_PRINTK) += printk.o obj-y += bitmap.o obj-y += module.o obj-m += testmodule.o +obj-m += testchain.o diff --git a/kernel/kmem.c b/kernel/kmem.c new file mode 100644 index 0000000000000000000000000000000000000000..8f0489cbecda2e1f1574a991f3e316c72256263a --- /dev/null +++ b/kernel/kmem.c @@ -0,0 +1,374 @@ +/** + * @file kernel/kmem.c + * + * A simple high-level allocator that uses sbrk() to retrieve memory. + * Is similar to lib/chunk.c, but a lot simpler. I need to respect sbrk() + * so I can't just use _chunk_ because it may release any parent chunk, + * while we need to do that from _kmem_last to first as the become free. + * I don't have time do do this properly, so this must do for now.. + */ + +#include <list.h> +#include <kernel/kmem.h> +#include <kernel/sbrk.h> +#include <kernel/kernel.h> +#include <kernel/printk.h> + + +#define WORD_ALIGN(x) ALIGN((x), (sizeof(unsigned long) - 1)) + + +struct kmem { + void *data; + size_t size; + int free; + struct kmem *prev, *next; + struct list_head node; +} __attribute__((aligned)); + +/* the initial and the most recent allocation */ +static struct kmem *_kmem_init; +static struct kmem *_kmem_last; + + +/** + * @brief see if we can find a suitable chunk in our pool + */ + +static struct kmem *kmem_find_free_chunk(size_t size, struct kmem **prev) +{ + struct kmem *p_tmp; + struct kmem *p_elem; + + (*prev) = _kmem_last; + + if (list_empty(&_kmem_init->node)) + return NULL; + + list_for_each_entry_safe(p_elem, p_tmp, &_kmem_init->node, node) { + if (!p_elem->free) + BUG(); + + if (p_elem->size >= size) { + (*prev) = p_elem->prev; + return p_elem; + } + } + + + return NULL; +} + + +/** + * @brief split a chunk in two for a given size + */ + +static void kmem_split(struct kmem *k, size_t size) +{ + struct kmem *split; + + + split = (struct kmem *)((size_t) k + size); + + + split->free = 1; + split->data = split + 1; + + split->prev = k; + split->next = k->next; + + split->size = k->size - size; + + k->size = size - sizeof(struct kmem); + + if (k->next) + k->next->prev = split; + + k->next = split; + + list_add_tail(&split->node, &_kmem_init->node); +} + + + +/** + * @brief returns the initial kmem chunk + * + * @note call this once kernel_sbrk() works + */ + +void *kmem_init(void) +{ + if (likely(_kmem_init)) + return _kmem_init; + + _kmem_init = kernel_sbrk(WORD_ALIGN(sizeof(struct kmem))); + + if (_kmem_init == (void *) -1) { + pr_crit("KMEM: error, cannot _kmem_initialise\n"); + return NULL; + } + + _kmem_init->data = NULL; + _kmem_init->size = 0; + _kmem_init->free = 0; + _kmem_init->prev = NULL; + _kmem_init->next = NULL; + + /* we track our free chunks in the node of the initial allocation */ + INIT_LIST_HEAD(&_kmem_init->node); + + _kmem_last = _kmem_init; + + return _kmem_init; +} + + + +/** + * @brief merge a chunk with its neighbour + */ + +static void kmem_merge(struct kmem *k) +{ + k->size = k->size + k->next->size + sizeof(struct kmem); + + k->next = k->next->next; + + if (k->next) + k->next->prev = k; +} + + +/** + * @brief allocates size bytes and returns a pointer to the allocated memory, + * suitably aligned for any built-in type + * + * @param size the number of bytes to allocate + * + * @returns a pointer or NULL on error or size == 0 + */ + +void *kmalloc(size_t size) +{ + size_t len; + + struct kmem *k_new; + struct kmem *k_prev = NULL; + + + if (!size) + return NULL; + + len = WORD_ALIGN(size + sizeof(struct kmem)); + + /* try to locate a free chunk first */ + k_new = kmem_find_free_chunk(size, &k_prev); + + if (k_new) { + /* take only what we need */ + if ((len + sizeof(struct kmem)) < k_new->size) + kmem_split(k_new, len); + + k_new->free = 0; + + return k_new->data; + } + + + /* need fresh memory */ + k_new = kernel_sbrk(len); + + if (k_new == (void *) -1) + return NULL; + + k_new->free = 0; + + k_new->next = NULL; + + /* link */ + k_new->prev = k_prev; + k_prev->next = k_new; + + k_new->size = len - sizeof(struct kmem); + + /* data section follows just after */ + k_new->data = k_new + 1; + + _kmem_last = k_new; + + return k_new->data; +} + + +/** + * @brief allocates memory for an array of nmemb elements of size bytes each and + * returns a pointer to the allocated memory. The memory is set to zero. + * + * @param nmemb the number of elements + * @param size the number of bytes per element + * + * @returns a pointer or NULL on error or nmemb/size == 0 + */ + +void *kcalloc(size_t nmemb, size_t size) +{ + size_t i; + size_t len; + + char *dst; + void *ptr; + + + len = nmemb * size; + + ptr = kmalloc(len); + + if (ptr) { + dst = ptr; + for (i = 0; i < len; i++) + dst[i] = 0; + } + + return ptr; +} + + +/** + * @brief changes the size of the memory block pointed to by ptr to size bytes. + * The contents will be unchanged in the range from the start of the + * region up to the minimum of the old and new sizes. If the new size is + * larger than the old size,the added memory will not be initialized. + * + * @param ptr the old memory block, if NULL, this function is equal to kmalloc() + * @param size the number of bytes for the new block, if 0, this is equal to + * kfree() + * + * @returns a pointer or NULL on error or size == 0 + */ + +void *krealloc(void *ptr, size_t size) +{ + size_t i; + + size_t len; + + char *dst; + char *src; + + void *ptr_new; + struct kmem *k; + + + + if (!ptr) + return kmalloc(size); + + if (ptr < kmem_init()) { + pr_warning("KMEM: invalid krealloc() of addr %p below lower " + "bound of trackable memory in call from %p\n", + ptr, __caller(0)); + return NULL; + } + + if (ptr > kernel_sbrk(0)) { + pr_warning("KMEM: invalid krealloc() of addr %p beyond system " + "break in call from %p\n", + ptr, __caller(0)); + return NULL; + } + + + k = ((struct kmem *) ptr) - 1; + + if (k->data != ptr) { + pr_warning("KMEM: invalid krealloc() of addr %p in call " + "from %p\n", + ptr, __caller(0)); + return NULL; + } + + + ptr_new = kmalloc(size); + + if (!ptr_new) + return NULL; + + + if (k->size > size) + len = size; + else + len = k->size; + + src = ptr; + dst = ptr_new; + + for (i = 0; i < len; i++) + dst[i] = src[i]; + + kfree(ptr); + + return ptr_new; +} + + +/** + * @brief function frees the memory space pointed to by ptr, which must have + * been returned by a previous call to kmalloc(), kcalloc(), + * or krealloc() + * + * @param ptr the memory to free + */ + +void kfree(void *ptr) +{ + struct kmem *k; + + + if (!ptr) + return; + + if (ptr < kmem_init()) { + pr_warning("KMEM: invalid kfree() of addr %p below lower bound " + "of trackable memory in call from %p\n", + ptr, __caller(0)); + return; + } + + if (ptr > kernel_sbrk(0)) { + pr_warning("KMEM: invalid kfree() of addr %p beyond system " + "break in call from %p\n", + ptr, __caller(0)); + return; + } + + + k = ((struct kmem *) ptr) - 1; + + if (k->data != ptr) { + pr_warning("KMEM: invalid kfree() of addr %p in call from %p\n", + ptr, __caller(0)); + return; + } + + k->free = 1; + + if (k->next && k->next->free) + kmem_merge(k); + + if (k->prev->free) { + k = k->prev; + kmem_merge(k); + } + + if (!k->next) { + k->prev->next = NULL; + _kmem_last = k->prev; + + /* release back */ + kernel_sbrk(-(k->size + sizeof(struct kmem))); + } else { + list_add_tail(&k->node, &_kmem_init->node); + } +} diff --git a/kernel/module.c b/kernel/module.c index 46eeebfb11b83ebb9dab103ea604caf359faa118..6915cf59ab7739720c048b825a4a97f6c47e9c92 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -186,7 +186,7 @@ static char *get_strtab_str(const struct elf_module *m, unsigned int idx) return NULL; } - + /** * @brief get the name of a symbol in .symtab with a given index * @@ -203,10 +203,10 @@ static char *get_symbol_str(const struct elf_module *m, unsigned int idx) //symbols = (Elf_Sym *) (m->pa + symtab->sh_offset); symbols = (Elf_Sym *) ((unsigned long) m->ehdr + symtab->sh_offset); - + if (idx < symtab->sh_size / symtab->sh_entsize) return m->str + symbols[idx].st_name; - + return NULL; } @@ -215,11 +215,11 @@ static char *get_symbol_str(const struct elf_module *m, unsigned int idx) /** * @brief find module section by name * - * @return module section structure pointer or NULL if not found + * @return module section structure pointer or NULL if not found */ struct module_section *find_mod_sec(const struct elf_module *m, - const char *name) + const char *name) { size_t i; @@ -306,19 +306,19 @@ void dump_sections(const struct elf_module *m) /** - * @brief get the type of a symbol + * @brief get the type of a symbol * * @return 1 if symbol has been found, 0 otherwise */ static unsigned long get_symbol_type(const struct elf_module *m, - const char *name) + const char *name) { unsigned int i; unsigned int idx; size_t sym_cnt; - + Elf_Shdr *symtab; Elf_Sym *symbols; @@ -326,7 +326,7 @@ static unsigned long get_symbol_type(const struct elf_module *m, idx = find_sec(m, ".symtab"); if (!idx) { - printf("WARN: no .symtab section found\n"); + printk("WARN: no .symtab section found\n"); return -1; } @@ -336,11 +336,11 @@ static unsigned long get_symbol_type(const struct elf_module *m, printk("Error %d != %ld\n", sizeof(Elf_Sym), symtab->sh_entsize); return -1; } - + symbols = (Elf_Sym *) ((unsigned long) m->ehdr + symtab->sh_offset); - + sym_cnt = symtab->sh_size / symtab->sh_entsize; - + for (i = 0; i < sym_cnt; i++) { if(!strcmp(get_symbol_str(m, i), name)) { return ELF_ST_TYPE(symbols[i].st_info); @@ -353,19 +353,19 @@ static unsigned long get_symbol_type(const struct elf_module *m, /** - * @brief get the value of a symbol + * @brief get the value of a symbol * * @return 1 if symbol has been found, 0 otherwise */ static unsigned long get_symbol_value(const struct elf_module *m, - const char *name, unsigned long *value) + const char *name, unsigned long *value) { unsigned int i; unsigned int idx; size_t sym_cnt; - + Elf_Shdr *symtab; Elf_Sym *symbols; @@ -373,21 +373,21 @@ static unsigned long get_symbol_value(const struct elf_module *m, idx = find_sec(m, ".symtab"); if (!idx) { - printf("WARN: no .symtab section found\n"); + printk("WARN: no .symtab section found\n"); return -1; } symtab = &m->shdr[idx]; if (symtab->sh_entsize != sizeof(Elf_Sym)) { - printf("Error %d != %ld\n", sizeof(Elf_Sym), symtab->sh_entsize); + printk("Error %d != %ld\n", sizeof(Elf_Sym), symtab->sh_entsize); return -1; } - + symbols = (Elf_Sym *) ((unsigned long) m->ehdr + symtab->sh_offset); - + sym_cnt = symtab->sh_size / symtab->sh_entsize; - + for (i = 0; i < sym_cnt; i++) { if(!strcmp(get_symbol_str(m, i), name)) { (*value) = symbols[i].st_value; @@ -416,58 +416,58 @@ static int dump_symtab(struct elf_module *m) idx = find_sec(m, ".symtab"); if (!idx) { - printf("WARN: no .symtab section found\n"); + printk("WARN: no .symtab section found\n"); return -1; } symtab = &m->shdr[idx]; if (symtab->sh_entsize != sizeof(Elf_Sym)) { - printf("Error %d != %ld\n", sizeof(Elf_Sym), symtab->sh_entsize); + printk("Error %d != %ld\n", sizeof(Elf_Sym), symtab->sh_entsize); return -1; } - + symbols = (Elf_Sym *) ((unsigned long) m->ehdr + symtab->sh_offset); sym_cnt = symtab->sh_size / symtab->sh_entsize; - printf("\n.symtab contains %d entries\n" + printk("\n.symtab contains %d entries\n" "============================\n" "\t[NUM]\t[VALUE]\t\t\t[SIZE]\t[TYPE]\t[NAME]\n", sym_cnt); - + for (i = 0; i < sym_cnt; i++) { - printf("\t%d\t%016lx\t%4ld", + printk("\t%d\t%016lx\t%4ld", i, symbols[i].st_value, symbols[i].st_size); switch (ELF_ST_TYPE(symbols[i].st_info)) { case STT_NOTYPE : - printf("\tNOTYPE "); break; + printk("\tNOTYPE "); break; case STT_OBJECT : - printf("\tOBJECT "); break; + printk("\tOBJECT "); break; case STT_FUNC : - printf("\tFUNC "); break; + printk("\tFUNC "); break; case STT_SECTION : - printf("\tSECTION"); break; + printk("\tSECTION"); break; case STT_FILE : - printf("\tFILE "); break; + printk("\tFILE "); break; case STT_COMMON : - printf("\tCOMMON "); break; + printk("\tCOMMON "); break; case STT_TLS : - printf("\tTLS "); break; + printk("\tTLS "); break; default: - printf("\tUNKNOWN"); break; + printk("\tUNKNOWN"); break; } - printf("\t%-10s\n", get_symbol_str(m, i)); + printk("\t%-10s\n", get_symbol_str(m, i)); } - - return 0; + + return 0; } @@ -487,16 +487,16 @@ static void dump_strtab(const struct elf_module *m) return; - printf("\n.strtab:\n" + printk("\n.strtab:\n" "============================\n" "\t[OFF]\t[STR]\n"); while(i < m->sh_size) { - printf("\t[%d]\t%s\n", i, m->str + i); + printk("\t[%d]\t%s\n", i, m->str + i); i += strlen(m->str + i) + 1; } - printf("\n\n"); + printk("\n\n"); } @@ -554,7 +554,7 @@ static int set_dynstr(struct elf_module *m) m->dyn_size = m->shdr[idx].sh_size; return 1; } - + m->dyn_str = NULL; m->dyn_size = 0; @@ -578,7 +578,7 @@ static int set_strtab(struct elf_module *m) if (idx) { m->str = (((char *) m->ehdr) + m->shdr[idx].sh_offset); - m->str_size = m->shdr[idx].sh_size; + m->str_size = m->shdr[idx].sh_size; return 1; } @@ -613,17 +613,17 @@ static int setup_module(struct elf_module *m) m->shdr = (Elf_Shdr *) (((char *) m->ehdr) + m->ehdr->e_shoff); } else { m->shdr = NULL; - printf("ERR: no section header found\n"); + printk("ERR: no section header found\n"); return -1; } /* locate and set section header string table */ if (!set_shstrtab(m)) return -1; - + /* locate and set dynamic string table */ if (!set_dynstr(m)) { - printf("WARN: no dynamic string table found\n"); + printk("WARN: no dynamic string table found\n"); } /* locate and set string table */ @@ -632,7 +632,7 @@ static int setup_module(struct elf_module *m) /* set up for relocatable object */ if (m->ehdr->e_type == ET_REL) { - printf("TODO\n"); + printk("TODO\n"); m->align = 0x200000; /* PC */ #if 0 @@ -640,14 +640,14 @@ static int setup_module(struct elf_module *m) m->size = 0; for (i = 0; i < m->ehdr->e_shnum; i++) { if ((m->shdr[i].sh_flags & SHF_ALLOC)) { - printf("Alloc section: %s, size %ld\n", + printk("Alloc section: %s, size %ld\n", m->sh_str + m->shdr[i].sh_name, m->shdr[i].sh_size); - + m->size += m->shdr[i].sh_size; if (m->shdr[idx].sh_addralign > m->align) - + m->align = m->shdr[idx].sh_addralign; } @@ -685,7 +685,7 @@ static int module_load_mem(struct elf_module *m) m->pa = (unsigned long) mem; - printf("\n\nLoading module run-time sections\n"); + printk("\n\nLoading module run-time sections\n"); va_load = m->va; pa_load = m->pa; @@ -724,13 +724,13 @@ static int module_load_mem(struct elf_module *m) if (sec->sh_type & SHT_NOBITS) { - printf("\tZero segment %10s at %p size %ld\n", + printk("\tZero segment %10s at %p size %ld\n", s->name, (char *) va_load, sec->sh_size); bzero((void *) va_load, s->size); } else { - printf("\tCopy segment %10s from %p to %p size %ld\n", + printk("\tCopy segment %10s from %p to %p size %ld\n", s->name, (char *) m->ehdr + sec->sh_offset, (char *) va_load, @@ -743,11 +743,11 @@ static int module_load_mem(struct elf_module *m) s->addr = va_load; va_load = s->addr + s->size; - + s++; if (s > &m->sec[m->num_sec]) { - printf("Error out of section memory\n"); + printk("Error out of section memory\n"); return -1; } } @@ -764,7 +764,7 @@ static int module_relocate(struct elf_module *m) size_t rel_cnt; Elf_Shdr *sec; - + /* no dynamic linkage, so it's either self-contained or bugged, we'll @@ -787,7 +787,7 @@ static int module_relocate(struct elf_module *m) sec = get_sec(m, idx); printk("\nSection Header info: %ld\n", sec->sh_info); - + if (sec) { Elf_Rela *relatab; @@ -805,15 +805,15 @@ static int module_relocate(struct elf_module *m) char *symstr = get_symbol_str(m, symsec); struct module_section *s; struct module_section *text = find_mod_sec(m, ".text"); - - printf("OFF: %08lx INF: %8lx ADD: %3ld LNK: %ld SEC: %d NAME: %s\n\n", + + printk("OFF: %08lx INF: %8lx ADD: %3ld LNK: %ld SEC: %d NAME: %s\n\n", relatab[i].r_offset, relatab[i].r_info, relatab[i].r_addend, sec->sh_link, symsec, symstr); - + if (strlen(symstr)) { @@ -822,15 +822,15 @@ static int module_relocate(struct elf_module *m) if (!sym) { unsigned long symval; - printf("\tNot found in library, resolving in module\n"); + printk("\tNot found in library, resolving in module\n"); if (!(get_symbol_type(m, symstr) & STT_FUNC)) { - printf("\tERROR, unresolved symbol %s\n", symstr); + printk("\tERROR, unresolved symbol %s\n", symstr); return -1; } if (!get_symbol_value(m, symstr, &symval)) { - printf("\tERROR, unresolved symbol %s\n", symstr); + printk("\tERROR, unresolved symbol %s\n", symstr); return -1; } @@ -838,32 +838,32 @@ static int module_relocate(struct elf_module *m) } - printf("\tSymbol %s at %lx\n", symstr, sym); + printk("\tSymbol %s at %lx\n", symstr, sym); apply_relocate_add(m, &relatab[i], sym); } else { /* no string, symtab entry is probably a section, try to identify it */ - + char *secstr = get_shstrtab_str(m, symsec); s = find_mod_sec(m, secstr); if (!s) { - printf("Error cannot locate section %s for symbol\n", secstr); + printk("Error cannot locate section %s for symbol\n", secstr); continue; } /* target address to insert at location */ reladdr = (long) s->addr; - printf("\tRelative symbol address: %x, entry at %08lx\n", reladdr, s->addr); - + printk("\tRelative symbol address: %x, entry at %08lx\n", reladdr, s->addr); + apply_relocate_add(m, &relatab[i], reladdr); } - printf("\n"); + printk("\n"); } - printf("\n"); + printk("\n"); } } @@ -886,7 +886,7 @@ void go(entrypoint_t ep) int module_load(struct elf_module *m, void *p) { unsigned long symval; - entrypoint_t ep; + entrypoint_t ep; /* the ELF binary starts with the ELF header */ @@ -898,32 +898,30 @@ int module_load(struct elf_module *m, void *p) return -1; printk("Setting up module configuration\n"); - + if (setup_module(m)) return -1; dump_sections(m); - + if (module_load_mem(m)) return -1; - + dump_symtab(m); if (module_relocate(m)) return -1; #if 1 - //uintptr_t epaddr = (uintptr_t) (m->va); if (!get_symbol_value(m, "_module_init", &symval)) { - printf("module init not found\n"); + printk("module init not found\n"); return -1; - } ep = (entrypoint_t) (m->va + symval); - - printf("Binary entrypoint is %lx; invoking %p\n", m->ehdr->e_entry, ep); + + printk("Binary entrypoint is %lx; invoking %p\n", m->ehdr->e_entry, ep); go(ep); #endif diff --git a/kernel/module_image.c b/kernel/module_image.c new file mode 100644 index 0000000000000000000000000000000000000000..51de991cf7de44f8e86fee9767c50662d66eb97b --- /dev/null +++ b/kernel/module_image.c @@ -0,0 +1,8 @@ +/** + * linker references to embedded modules.image + */ + +extern unsigned char _binary_modules_image_start; +extern unsigned char _binary_modules_image_end; +extern unsigned char _binary_modules_image_size; + diff --git a/kernel/testmodule.c b/kernel/testmodule.c index 362018baf2465d04eb87e9cd7336c7b00310f1ff..f147c1b03740242a5ad481515f9344196d3d41f8 100644 --- a/kernel/testmodule.c +++ b/kernel/testmodule.c @@ -6,6 +6,11 @@ #include <kernel/printk.h> +void somefunction(void) +{ + printf("this is some function\n"); +} + void _module_init(void); diff --git a/lib/Kconfig b/lib/Kconfig index c504a517ddb3f547f9c11a470c2e95afbdfc8acb..80190205372148d8113dcd75c72a7de920b38d74 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -82,6 +82,16 @@ config PAGE_MAP_MOVE_NODE_AVAIL_THRESH threshold above. If unsure, use a threshold of 1. endmenu + +config CHUNK + bool "Memory Chunk Manager" + default y + help + Manages smaller than page-sized allocations with memory supplied by + a higher-tier memory-manager. This is a stepping stone to malloc(), + if unsure, say Y + + config AR bool "AR archive loading support" default y diff --git a/lib/Makefile b/lib/Makefile index 4faa3d468b88255cc6f03755a2ae3067a0714a8b..c5c84f0d404b8b95c407b82f28495af0810f52ae 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -2,3 +2,4 @@ lib-$(CONFIG_SYSCTL) += sysctl.o lib-$(CONFIG_MM) += mm.o lib-$(CONFIG_PAGE_MAP) += page.o lib-$(CONFIG_AR) += ar.o +lib-$(CONFIG_CHUNK) += chunk.o diff --git a/lib/ar.c b/lib/ar.c index 44c5307bb05ae6fa6ef3c1debd8ff8349778975d..842c09d3c1d71229e094a160c5cbd774929fcfc9 100644 --- a/lib/ar.c +++ b/lib/ar.c @@ -395,14 +395,51 @@ static unsigned int ar_get_filecount(char *p, struct archive *a) /** - * @brief print files in the archive + * @brief print symbols in the archive * * @param a a struct archive * @param name the file name to search for * * @return a pointer or NULL if not found + */ + +void ar_list_symbols(struct archive *a) +{ + unsigned long i; + + + if (!a) + return; + + if (!a->fname) + return; + + if (!a->fnamesz) + return; + + printk("AR:\n" + "AR: Symbol contents of archive at %p\n" + "AR:\n", + a->ar_base); + + printk("AR:\t[NAME]\t\t\t[ADDR]\n"); + + for (i = 0; i < a->n_sym; i++) { + printk("AR:\t%s\t\t%p\n", + a->sym[i], a->p_sym[i]); + } + + printk("AR:\n"); +} + + +/** + * @brief print files in the archive * - * @note this silently returns the first occurence only + * @param a a struct archive + * @param name the file name to search for + * + * @return a pointer or NULL if not found */ void ar_list_files(struct archive *a) @@ -423,6 +460,7 @@ void ar_list_files(struct archive *a) "AR: File contents of archive at %p\n" "AR:\n", a->ar_base); + printk("AR:\t[NAME]\t\t\t[SIZE]\t[ADDR]\n"); for (i = 0; i < a->n_file; i++) { diff --git a/lib/chunk.c b/lib/chunk.c new file mode 100644 index 0000000000000000000000000000000000000000..d23f883f0ef52c544211baa570d1b507bacd00d2 --- /dev/null +++ b/lib/chunk.c @@ -0,0 +1,405 @@ +/** + * @file lib/chunk.c + * + * + * This is a stepping stone to something like malloc() + * + * When chunk_alloc() is called, it attempts to first-fit-locate a buffer within + * its tracked allocated chunks, splitting them into smaller chunks that track + * their parent chunks. If it does not find a sufficiently large chunk, it + * uses the user-supplied function to grab memory as needed from a higher-tier + * memory manager. In case such a memory manager cannot provide buffers + * exactly the requested size, an optional function real_alloc_size() may be + * provided, so the chunk allocator can know the actual size of the allocated + * block (e.g. from kernel/page.c) + * + * When freeing an allocation with chunk_free() and the chunk is the child of + * another chunk, the reference counter of the parent is decremented and the + * child is released and upmerged IF the child was the last allocation in the + * parent's buffer, otherwise it is treated as any other available chunk. + * If a top-level chunk's refernce counter is decremented to 0, it is released + * back to the higher-tier memory manager. + * + * Note that the address verification in chunk_free() is weak, as it only + * checks if it's start address and size are within the chunk of the parent. + * + * @todo add a function chunk_alloc_aligned() that allows aligned allocations + * without wasting space. the function just grabs some memory and creates + * a single chunk up to the alignement boundary and adds it to the "full" + * pool, then returns the following (aligned) chunk. + * + */ + +#include <unistd.h> +#include <errno.h> +#include <list.h> +#include <kernel/kernel.h> +#include <kernel/printk.h> + +#include <chunk.h> + + +/** + * The header of an alloccated memory chunk. + * + * this isn't partcicularly memory-efficient for small allocations... + */ + +struct chunk { + void *mem; /* the start of the data memory */ + + size_t size; /* the allocated size of the chunk */ + size_t free; /* the free memory in the chunk */ + + unsigned long refcnt; /* number of references to this chunk */ + struct chunk *parent; /* the parent chunk */ + struct chunk *child; /* the youngest child chunk */ + struct chunk *sibling; /* the left-hand (younger) child chunk */ + + struct list_head node; +}; + + + + +/** + * @brief align a pointer to the alignment configured + * + * @param pool a struct chunk_pool + * + * @param p an address pointer + * + * @return the aligned pointer + */ + +static inline void *chunk_align(struct chunk_pool *pool, void *p) +{ + return (void *) (((unsigned long) p + pool->align) & ~pool->align); +} + + +/** + * @brief classify a chunk as full or empty + * + * @param pool a struct chunk_pool + * @param c a struct chunk + */ + +static void chunk_classify(struct chunk_pool *pool, struct chunk *c) +{ + /* if it can't fit at least one header, it's no good */ + if (c->free <= (sizeof(struct chunk) + pool->align)) + list_move_tail(&c->node, &pool->empty); + else + list_move_tail(&c->node, &pool->full); +} + + +/** + * @brief configure data pointer and free bytes of a chunk + * + * @param pool a struct chunk_pool + * @param c a struct chunk + */ + +static void chunk_setup(struct chunk_pool *pool, struct chunk *c) +{ + /* the actual memory starts after struct chunk */ + c->mem = chunk_align(pool, (void *) (c + 1)); + /* set the allocatable size of the cunk */ + c->free = ((size_t) c + c->size) - (size_t) c->mem; + + chunk_classify(pool, c); +} + + +/** + * @brief grab a new chunk of memory from the higher-tier memory manager + * + * @param pool a struct chunk_pool + * + * @param size the requested minimum size of the chunk + * + * @return a struct chunk or NULL on error + */ + +static struct chunk *chunk_grab_new(struct chunk_pool *pool, size_t size) +{ + struct chunk *c; + + + c = (struct chunk *) pool->alloc(size); + + if (!c) + return NULL; + + + /* if set, get actual chunk size */ + if (pool->real_alloc_size) { + + c->size = pool->real_alloc_size((void *) c); + + if (c->size < size) { + pr_warn("CHUNK: got less bytes than expected from " + "higher-tier memory manager\n"); + pool->free((void *) c); + return NULL; + } + + } else { + c->size = size; + } + + /* this is a new toplevel chunk, it's all alone */ + c->parent = NULL; + c->child = NULL; + c->sibling = NULL; + + /* we have no references yet */ + c->refcnt = 0; + + /* add new parent to full list by default */ + list_add_tail(&c->node, &pool->full); + + chunk_setup(pool, c); + + + return c; +} + + +/** + * @brief split of a new chunk as a child of an existing chunk + * + * @param pool a struct chunk_pool + * + * @param c a struct chunk that is the parent and can hold the child + * + * @param size the size of the new chunk + * + * @return a struct chunk or NULL on error + */ + +static struct chunk *chunk_split(struct chunk_pool *pool, + struct chunk *c, size_t size) +{ + struct chunk *new; + + + if (c->free < size) + return NULL; + + /* this chunk is now a child of a higher-order chunk */ + new = (struct chunk *) c->mem; + new->parent = c; + new->child = NULL; + new->sibling = c->child; + new->refcnt = 1; + new->free = 0; + new->size = size; + + /* the new node will be in use, add to empty list */ + list_add_tail(&new->node, &pool->empty); + + chunk_setup(pool, new); + + /* track the youngest child in the parent */ + c->child = new; + c->refcnt++; + + /* align parent chunk to start of new memory subsegment */ + c->mem = chunk_align(pool, (void *) ((size_t) c->mem + size)); + + /* update free bytes with regard to actual alignment */ + c->free = ((size_t) c + c->size) - ((size_t) new + new->size); + + return new; +} + + +/** + * @brief allocate a chunk of memory + * + * @param pool a struct chunk_pool + * @param size the number of bytes to allocate + * + * @return a pointer to a memory buffer or NULL on error + */ + +void *chunk_alloc(struct chunk_pool *pool, size_t size) +{ + size_t alloc_sz; + + struct chunk *c = NULL; + + struct chunk *p_elem; + struct chunk *p_tmp; + + + + if (!pool->alloc) { + pr_err("CHUNK: error, no allocator supplied.\n"); + return NULL; + } + + + if (!size) + return NULL; + + + /* grab a large enough chunk to satisfy alignment and overhead needs */ + alloc_sz = (size_t) chunk_align(pool, + (void *) (size + sizeof(struct chunk))); + + list_for_each_entry_safe(p_elem, p_tmp, &pool->full, node) { + + if (p_elem->free >= alloc_sz) { + c = p_elem; + break; + } + } + + /* no chunks, either list is empty or none had enough space */ + if (!c) { + + c = chunk_grab_new(pool, alloc_sz); + + if (!c) { + pr_err("CHUNK: error, got no memory from allocator \n"); + return NULL; + } + } + + c = chunk_split(pool, c, alloc_sz); + + return c->mem; +} + + +/** + * @brief free a chunk of memory + * + * @param pool a struct chunk_pool + * @param addr the address of the buffer to return to the allocator + * + */ + +void chunk_free(struct chunk_pool *pool, void *addr) +{ + unsigned long chunk_addr; + + struct chunk *c; + struct chunk *p; + + + if (!addr) + return; + + + if (!pool->alloc) { + pr_err("CHUNK: error, no de-allocator supplied.\n"); + return; + } + + + /* the start of the chunk is off by at most the size of the header minus + * the (alignment - 1) + */ + chunk_addr = (unsigned long) addr - sizeof(struct chunk) - pool->align; + c = (struct chunk *) chunk_align(pool, (void *) chunk_addr); + + + /* if the address of the data chunk does not coincide with what we just + * calculated, something is seriously wrong here + */ + BUG_ON((size_t) addr != (size_t) chunk_align(pool, (void *) (c + 1))); + + + /* if this is a toplevel chunk, release it back to the higher-tier */ + if (!c->parent) { + + BUG_ON(c->refcnt); + + pool->free((void *) c); + list_del(&c->node); + + return; + } + + + p = c->parent; + + /* weak check if this is even valid */ + if (addr < (void *) p) { + pr_warn("CHUNK: invalid address %p supplied in call to %s\n", + addr, __func__); + return; + } + + if (((size_t) c + c->size) > ((size_t) p + p->size)) { + pr_warn("CHUNK: invalid address %p, or attempted double free " + "in call to %s\n", addr, __func__); + } + + /* If this he youngest child, merge it back and update the parent. */ + if (p->child == c) { + + p->child = c->sibling; + p->refcnt--; + + /* align parent chunk */ + p->mem = chunk_align(pool, (void *) ((size_t) c)); + + /* update free parent bytes with regard to actual alignment */ + p->free = ((size_t) p + p->size) - (size_t) c; + + /* make sure this will not fit the parent without overflowing + * (except for the obvious edge case that should never happen + * anyways), so we may detect a double-free + */ + c->size = p->size - c->size + 1; + + list_del(&c->node); + + if (!p->refcnt) + chunk_free(pool, p->mem); + + } else { + chunk_setup(pool, c); + list_move_tail(&c->node, &pool->full); + } +} + + +/** + * @brief initialise a chunk pool + * + * @param pool a struct chunk pool + * @param align the memory alignment of returned memory pointers + * @param alloc a pointer to a higher-tier function that + * gives us a chunk of memory to manage + * @param free a pointer to a function that returns a chunk of memory to the + * higher-tier memory manager + * @param real_alloc_size a pointer to a function that tells us how large the + * chunk returned by alloc() actually is. This may be NULL if alloc() + * always returns a chunk the exact size we requested + * + * @note the alloc() function should at least return word-aligned addresses + */ + +void chunk_pool_init(struct chunk_pool *pool, + unsigned long align, + void *(*alloc)(size_t size), + void (*free)(void *addr), + size_t (*real_alloc_size)(void *addr)) +{ + INIT_LIST_HEAD(&pool->full); + INIT_LIST_HEAD(&pool->empty); + + pool->align = align - 1; + + pool->alloc = alloc; + pool->free = free; + + pool->real_alloc_size = real_alloc_size; +} diff --git a/lib/mm.c b/lib/mm.c index 126f8079c0b22859bb4f75a286d68a90fe4da1a8..18c8f9653bf07a3fbe868ffabea67e2de8f570d3 100644 --- a/lib/mm.c +++ b/lib/mm.c @@ -77,7 +77,7 @@ static bool mm_blk_addr_valid(struct mm_pool *mp, struct mm_blk_lnk *blk) * @param mp a struct mm_pool * @param blk a struct mm_blk_lnk * - * @return block index or -EFAULT on error + * @return block index */ static unsigned long mm_blk_idx(struct mm_pool *mp, struct mm_blk_lnk *blk) @@ -206,6 +206,7 @@ static void *mm_find_neighbour(struct mm_pool *mp, addr ^= (1UL << order); addr += mp->base; + return (void *) addr; } @@ -236,6 +237,15 @@ static struct mm_blk_lnk *mm_merge_blk(struct mm_pool *mp, * is the start of the newly created higher order block */ + /* There is a potential bug, we should never get this far if the block + * was not allocated, even if the block address in free() was incorrect + */ + if (!n->link.prev || !n->link.next) { + pr_crit("MM: corruption warning, someone tried to release an " + "invalid block.\n"); + return NULL; + } + list_del(&n->link); if (n < blk) { @@ -368,10 +378,15 @@ void *mm_alloc(struct mm_pool *mp, size_t size) unsigned long i; unsigned long order; - struct mm_blk_lnk *blk = NULL; + struct mm_blk_lnk *blk = NULL; struct list_head *list = NULL; + if (!mp) + return NULL; + + if (!size) + return NULL; order = ilog2(roundup_pow_of_two(size)); @@ -480,6 +495,34 @@ exit: } + +/** + * @brief returns the size of the block for a given address + * + * @param mp a struct mm_pool + * + * @param addr the address of the block + * + * @return the size of the block the address is in, 0 if invalid or not found + * + */ + +unsigned long mm_block_size(struct mm_pool *mp, const void *addr) +{ + unsigned long order; + + unsigned long size = 0; + + + if (mm_addr_in_pool(mp, (struct mm_blk_link *) addr)) { + order = mm_blk_get_alloc_order(mp, (struct mm_blk_lnk *) addr); + size = 1 << order; + } + + return size; +} + + /** * @brief returns number of free blocks at block granularity * @@ -662,7 +705,7 @@ void mm_exit(struct mm_pool *mp) { void mm_dump_stats(struct mm_pool *mp) { - unsigned long i; + unsigned long i __attribute__((unused)); if (!mp) diff --git a/lib/page.c b/lib/page.c index e293617f85753e2064452e54f473f8285876bb71..8911e03f038afbf1f1d14825219427dfe0e292c7 100644 --- a/lib/page.c +++ b/lib/page.c @@ -27,8 +27,8 @@ static struct page_map_node **page_mem; /* empty/busy pool lists */ -struct list_head page_map_list_full; -struct list_head page_map_list_empty; +static struct list_head page_map_list_full; +static struct list_head page_map_list_empty; /** @@ -80,6 +80,12 @@ int page_map_add(unsigned long start, unsigned long end, if (end < start) goto error; + if ((end - start) < page_size) + goto error; + + if (!page_size) + goto error; + mem_size = (size_t) end - start; @@ -176,6 +182,8 @@ error: * to consider re-adding the free segment before your boot memory back * to the page map. In that case, make sure the allocation is never * released. Make sure you configure extra ram banks if needed. + * + * @note the reserved block is at least size bytes */ void *page_map_reserve_chunk(size_t size) @@ -204,6 +212,56 @@ exit: } +/** + * @brief get the size of the chunk for an address + * + * @param addr the (page) address pointer + * + * @return the size of the chunk, or 0 on error or if not found in pool + */ + +unsigned long page_map_get_chunk_size(void *addr) +{ + unsigned long size = 0; + + struct page_map_node *p_elem; + struct page_map_node *p_tmp; + + + if (!page_mem) { + pr_err("PAGE MEM: %s no page map configured\n", __func__); + goto exit; + } + + if (!addr) { + pr_info("PAGE MEM: NULL pointer in call to %s from %p\n", + __func__, __caller(0)); + goto exit; + } + + list_for_each_entry_safe(p_elem, p_tmp, &page_map_list_empty, node) { + if (mm_addr_in_pool(p_elem->pool, addr)) { + size = mm_block_size(p_elem->pool, addr); + goto exit; + } + } + + list_for_each_entry_safe(p_elem, p_tmp, &page_map_list_full, node) { + if (mm_addr_in_pool(p_elem->pool, addr)) { + size = mm_block_size(p_elem->pool, addr); + goto exit; + } + } + +exit: + return size; +} + + + + + + /** * @brief allocates a page by trying all configured banks until one is found * @@ -224,6 +282,7 @@ void *page_alloc(void) } list_for_each_entry_safe(p_elem, p_tmp, &page_map_list_full, node) { + page = mm_alloc(p_elem->pool, PG_SIZE(p_elem)); if (!page) { diff --git a/samples/Kconfig b/samples/Kconfig index 47bdb96a373530af681b88cfa6307653e7b93ff5..91214c8d29015e0e8fcb39ea21ccb7b22ed4bc03 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -17,4 +17,10 @@ config SAMPLE_MM help Build a sample demonstrating the use of the memory management system. +config SAMPLE_CHUNK + bool "Build chunk memory allocator sample code" + depends on CHUNK + help + Build a sample demonstrating the use of the chunk memory allocator. + endif # SAMPLES diff --git a/samples/Makefile b/samples/Makefile index 951ae4999c0c6db30f4aad7dae2a38a2e9cd1ceb..541459d1acc37daa45a3f7b8da7e13a16b5c6185 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_SAMPLE_SYSCTL) += sysctl/ obj-$(CONFIG_SAMPLE_MM) += mm/ +obj-$(CONFIG_SAMPLE_CHUNK) += chunk/ diff --git a/samples/chunk/Makefile b/samples/chunk/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b2341f4cd46ce96210c6ac349cc3f1291b283183 --- /dev/null +++ b/samples/chunk/Makefile @@ -0,0 +1,28 @@ +# kbuild trick to avoid linker error. Can be omitted if a module is built. +obj- := dummy.o + +hostprogs-$(CONFIG_SAMPLE_CHUNK) := chunk_demo + +# I guess I'm too stupid to figure out the proper way to do this +# (but maybe there is none) + +ifdef CROSS_COMPILE +HOSTCC := $(CROSS_COMPILE)gcc +HOSTLD := $(CROSS_COMPILE)ld +endif + + +HOSTCFLAGS_chunk_demo.o += -I$(objtree)/include +chunk_demo-objs := chunk_demo.o + +ifndef CROSS_COMPILE +EXTRAPFLAG = -m32 +else +EXTRAPFLAG = +endif + +HOSTCFLAGS_chunk_demo.o += $(EXTRAFLAG) +HOSTLOADLIBES_chunk_demo += $(EXTRAFLAG) $(objtree)/lib/lib.a +HOSTLOADLIBES_chunk_demo += $(objtree)/kernel/built-in.o + +always := $(hostprogs-y) diff --git a/samples/chunk/chunk_demo.c b/samples/chunk/chunk_demo.c new file mode 100644 index 0000000000000000000000000000000000000000..40561e0306adc4bd03e0a8c09b1963ec4b119ca0 --- /dev/null +++ b/samples/chunk/chunk_demo.c @@ -0,0 +1,48 @@ +#include <stdio.h> + +#include <chunk.h> +#include <stdlib.h> + + + +/* configure a dummy higher-tier memory-manager */ + +#define PAGE_SIZE 4096 + +static void *page_alloc(size_t size) +{ + return malloc(PAGE_SIZE); +} + +static void page_free(void *addr) +{ + free(addr); +} + +static size_t get_alloc_size() +{ + return PAGE_SIZE; +} + + +#define DO_ALLOC 256 +#define ALIGN_BYTES 8 + +int main(void) +{ + int i; + + struct chunk_pool pool; + + void *p[DO_ALLOC]; + + + chunk_pool_init(&pool, ALIGN_BYTES, + &page_alloc, &page_free, &get_alloc_size); + + for (i = 0; i < DO_ALLOC; i++) + p[i] = chunk_alloc(&pool, (i + 1) * 17); + + for (i = DO_ALLOC - 1; i >= 0; i--) + chunk_free(&pool, p[i]); +}