/* * Xtensa MMU support * * Private data declarations * * Copyright (c) 2022 Intel Corporation * SPDX-License-Identifier: Apache-2.0 */ #ifndef ZEPHYR_ARCH_XTENSA_XTENSA_MMU_PRIV_H_ #define ZEPHYR_ARCH_XTENSA_XTENSA_MMU_PRIV_H_ #include #include #include #include /** * @defgroup xtensa_mmu_internal_apis Xtensa Memory Management Unit (MMU) Internal APIs * @ingroup xtensa_mmu_apis * @{ */ /** Mask for VPN in PTE */ #define XTENSA_MMU_PTE_VPN_MASK 0xFFFFF000U /** Mask for PPN in PTE */ #define XTENSA_MMU_PTE_PPN_MASK 0xFFFFF000U /** Mask for attributes in PTE */ #define XTENSA_MMU_PTE_ATTR_MASK 0x0000000FU /** Mask for cache mode in PTE */ #define XTENSA_MMU_PTE_ATTR_CACHED_MASK 0x0000000CU /** Mask used to figure out which L1 page table to use */ #define XTENSA_MMU_L1_MASK 0x3FF00000U /** Mask used to figure out which L2 page table to use */ #define XTENSA_MMU_L2_MASK 0x3FFFFFU #define XTENSA_MMU_PTEBASE_MASK 0xFFC00000 /** Number of bits to shift for PPN in PTE */ #define XTENSA_MMU_PTE_PPN_SHIFT 12U /** Mask for ring in PTE */ #define XTENSA_MMU_PTE_RING_MASK 0x00000030U /** Number of bits to shift for ring in PTE */ #define XTENSA_MMU_PTE_RING_SHIFT 4U /** Number of bits to shift for SW reserved ared in PTE */ #define XTENSA_MMU_PTE_SW_SHIFT 6U /** Mask for SW bits in PTE */ #define XTENSA_MMU_PTE_SW_MASK 0x00000FC0U /** * Internal bit just used to indicate that the attr field must * be set in the SW bits too. It is used later when duplicating the * kernel page tables. */ #define XTENSA_MMU_PTE_ATTR_ORIGINAL BIT(31) /** Construct a page table entry (PTE) */ #define XTENSA_MMU_PTE(paddr, ring, sw, attr) \ (((paddr) & XTENSA_MMU_PTE_PPN_MASK) | \ (((ring) << XTENSA_MMU_PTE_RING_SHIFT) & XTENSA_MMU_PTE_RING_MASK) | \ (((sw) << XTENSA_MMU_PTE_SW_SHIFT) & XTENSA_MMU_PTE_SW_MASK) | \ ((attr) & XTENSA_MMU_PTE_ATTR_MASK)) /** Get the attributes from a PTE */ #define XTENSA_MMU_PTE_ATTR_GET(pte) \ ((pte) & XTENSA_MMU_PTE_ATTR_MASK) /** Set the attributes in a PTE */ #define XTENSA_MMU_PTE_ATTR_SET(pte, attr) \ (((pte) & ~XTENSA_MMU_PTE_ATTR_MASK) | (attr & XTENSA_MMU_PTE_ATTR_MASK)) /** Set the SW field in a PTE */ #define XTENSA_MMU_PTE_SW_SET(pte, sw) \ (((pte) & ~XTENSA_MMU_PTE_SW_MASK) | (sw << XTENSA_MMU_PTE_SW_SHIFT)) /** Get the SW field from a PTE */ #define XTENSA_MMU_PTE_SW_GET(pte) \ (((pte) & XTENSA_MMU_PTE_SW_MASK) >> XTENSA_MMU_PTE_SW_SHIFT) /** Set the ring in a PTE */ #define XTENSA_MMU_PTE_RING_SET(pte, ring) \ (((pte) & ~XTENSA_MMU_PTE_RING_MASK) | \ ((ring) << XTENSA_MMU_PTE_RING_SHIFT)) /** Get the ring from a PTE */ #define XTENSA_MMU_PTE_RING_GET(pte) \ (((pte) & XTENSA_MMU_PTE_RING_MASK) >> XTENSA_MMU_PTE_RING_SHIFT) /** Get the ASID from the RASID register corresponding to the ring in a PTE */ #define XTENSA_MMU_PTE_ASID_GET(pte, rasid) \ (((rasid) >> ((((pte) & XTENSA_MMU_PTE_RING_MASK) \ >> XTENSA_MMU_PTE_RING_SHIFT) * 8)) & 0xFF) /** Calculate the L2 page table position from a virtual address */ #define XTENSA_MMU_L2_POS(vaddr) \ (((vaddr) & XTENSA_MMU_L2_MASK) >> 12U) /** Calculate the L1 page table position from a virtual address */ #define XTENSA_MMU_L1_POS(vaddr) \ ((vaddr) >> 22U) /** * @def XTENSA_MMU_PAGE_TABLE_ATTR * * PTE attributes for entries in the L1 page table. Should never be * writable, may be cached in non-SMP contexts only */ #if CONFIG_MP_MAX_NUM_CPUS == 1 #define XTENSA_MMU_PAGE_TABLE_ATTR XTENSA_MMU_CACHED_WB #else #define XTENSA_MMU_PAGE_TABLE_ATTR 0 #endif /** This ASID is shared between all domains and kernel. */ #define XTENSA_MMU_SHARED_ASID 255 /** Fixed data TLB way to map the page table */ #define XTENSA_MMU_PTE_WAY 7 /** Fixed data TLB way to map the vecbase */ #define XTENSA_MMU_VECBASE_WAY 8 /** Kernel specific ASID. Ring field in the PTE */ #define XTENSA_MMU_KERNEL_RING 0 /** User specific ASID. Ring field in the PTE */ #define XTENSA_MMU_USER_RING 2 /** Ring value for MMU_SHARED_ASID */ #define XTENSA_MMU_SHARED_RING 3 /** Number of data TLB ways [0-9] */ #define XTENSA_MMU_NUM_DTLB_WAYS 10 /** Number of instruction TLB ways [0-6] */ #define XTENSA_MMU_NUM_ITLB_WAYS 7 /** Number of auto-refill ways */ #define XTENSA_MMU_NUM_TLB_AUTOREFILL_WAYS 4 /** Indicate PTE is illegal. */ #define XTENSA_MMU_PTE_ILLEGAL (BIT(3) | BIT(2)) /** * PITLB HIT bit. * * For more information see * Xtensa Instruction Set Architecture (ISA) Reference Manual * 4.6.5.7 Formats for Probing MMU Option TLB Entries */ #define XTENSA_MMU_PITLB_HIT BIT(3) /** * PDTLB HIT bit. * * For more information see * Xtensa Instruction Set Architecture (ISA) Reference Manual * 4.6.5.7 Formats for Probing MMU Option TLB Entries */ #define XTENSA_MMU_PDTLB_HIT BIT(4) /** * Virtual address where the page table is mapped */ #define XTENSA_MMU_PTEVADDR CONFIG_XTENSA_MMU_PTEVADDR /** * Find the PTE entry address of a given vaddr. * * For example, assuming PTEVADDR in 0xE0000000, * the page spans from 0xE0000000 - 0xE03FFFFF * * address 0x00 is in 0xE0000000 * address 0x1000 is in 0xE0000004 * ..... * address 0xE0000000 (where the page is) is in 0xE0380000 * * Generalizing it, any PTE virtual address can be calculated this way: * * PTE_ENTRY_ADDRESS = PTEVADDR + ((VADDR / 4096) * 4) */ #define XTENSA_MMU_PTE_ENTRY_VADDR(base, vaddr) \ ((base) + (((vaddr) / KB(4)) * 4)) /** * Get ASID for a given ring from RASID register. * * RASID contains four 8-bit ASIDs, one per ring. */ #define XTENSA_MMU_RASID_ASID_GET(rasid, ring) \ (((rasid) >> ((ring) * 8)) & 0xff) /** * @brief Set RASID register. * * @param rasid Value to be set. */ static ALWAYS_INLINE void xtensa_rasid_set(uint32_t rasid) { __asm__ volatile("wsr %0, rasid\n\t" "isync\n" : : "a"(rasid)); } /** * @brief Get RASID register. * * @return Register value. */ static ALWAYS_INLINE uint32_t xtensa_rasid_get(void) { uint32_t rasid; __asm__ volatile("rsr %0, rasid" : "=a"(rasid)); return rasid; } /** * @brief Set a ring in RASID register to be particular value. * * @param asid ASID to be set. * @param ring ASID of which ring to be manipulated. */ static ALWAYS_INLINE void xtensa_rasid_asid_set(uint8_t asid, uint8_t ring) { uint32_t rasid = xtensa_rasid_get(); rasid = (rasid & ~(0xff << (ring * 8))) | ((uint32_t)asid << (ring * 8)); xtensa_rasid_set(rasid); } /** * @brief Invalidate a particular instruction TLB entry. * * @param entry Entry to be invalidated. */ static ALWAYS_INLINE void xtensa_itlb_entry_invalidate(uint32_t entry) { __asm__ volatile("iitlb %0\n\t" : : "a" (entry)); } /** * @brief Synchronously invalidate of a particular instruction TLB entry. * * @param entry Entry to be invalidated. */ static ALWAYS_INLINE void xtensa_itlb_entry_invalidate_sync(uint32_t entry) { __asm__ volatile("iitlb %0\n\t" "isync\n\t" : : "a" (entry)); } /** * @brief Synchronously invalidate of a particular data TLB entry. * * @param entry Entry to be invalidated. */ static ALWAYS_INLINE void xtensa_dtlb_entry_invalidate_sync(uint32_t entry) { __asm__ volatile("idtlb %0\n\t" "dsync\n\t" : : "a" (entry)); } /** * @brief Invalidate a particular data TLB entry. * * @param entry Entry to be invalidated. */ static ALWAYS_INLINE void xtensa_dtlb_entry_invalidate(uint32_t entry) { __asm__ volatile("idtlb %0\n\t" : : "a" (entry)); } /** * @brief Synchronously write to a particular data TLB entry. * * @param pte Value to be written. * @param entry Entry to be written. */ static ALWAYS_INLINE void xtensa_dtlb_entry_write_sync(uint32_t pte, uint32_t entry) { __asm__ volatile("wdtlb %0, %1\n\t" "dsync\n\t" : : "a" (pte), "a"(entry)); } /** * @brief Write to a particular data TLB entry. * * @param pte Value to be written. * @param entry Entry to be written. */ static ALWAYS_INLINE void xtensa_dtlb_entry_write(uint32_t pte, uint32_t entry) { __asm__ volatile("wdtlb %0, %1\n\t" : : "a" (pte), "a"(entry)); } /** * @brief Synchronously write to a particular instruction TLB entry. * * @param pte Value to be written. * @param entry Entry to be written. */ static ALWAYS_INLINE void xtensa_itlb_entry_write(uint32_t pte, uint32_t entry) { __asm__ volatile("witlb %0, %1\n\t" : : "a" (pte), "a"(entry)); } /** * @brief Synchronously write to a particular instruction TLB entry. * * @param pte Value to be written. * @param entry Entry to be written. */ static ALWAYS_INLINE void xtensa_itlb_entry_write_sync(uint32_t pte, uint32_t entry) { __asm__ volatile("witlb %0, %1\n\t" "isync\n\t" : : "a" (pte), "a"(entry)); } /** * @brief Invalidate all autorefill DTLB and ITLB entries. * * This should be used carefully since all refill entries in the data * and instruction TLB. At least two pages, the current code page and * the current stack, will be repopulated by this code as it returns. * * This needs to be called in any circumstance where the mappings for * a previously-used page table change. It does not need to be called * on context switch, where ASID tagging isolates entries for us. */ static inline void xtensa_tlb_autorefill_invalidate(void) { uint8_t way, i, entries; entries = BIT(MAX(XCHAL_ITLB_ARF_ENTRIES_LOG2, XCHAL_DTLB_ARF_ENTRIES_LOG2)); for (way = 0; way < XTENSA_MMU_NUM_TLB_AUTOREFILL_WAYS; way++) { for (i = 0; i < entries; i++) { uint32_t entry = way + (i << XTENSA_MMU_PTE_PPN_SHIFT); xtensa_dtlb_entry_invalidate(entry); xtensa_itlb_entry_invalidate(entry); } } __asm__ volatile("isync"); } /** * @brief Set the page tables. * * The page tables is set writing ptevaddr address. * * @param ptables The page tables address (virtual address) */ static ALWAYS_INLINE void xtensa_ptevaddr_set(void *ptables) { __asm__ volatile("wsr.ptevaddr %0" : : "a"((uint32_t)ptables)); } /** * @brief Get the current page tables. * * The page tables is obtained by reading ptevaddr address. * * @return ptables The page tables address (virtual address) */ static ALWAYS_INLINE void *xtensa_ptevaddr_get(void) { uint32_t ptables; __asm__ volatile("rsr.ptevaddr %0" : "=a" (ptables)); return (void *)(ptables & XTENSA_MMU_PTEBASE_MASK); } /** * @brief Get the virtual address associated with a particular data TLB entry. * * @param entry TLB entry to be queried. */ static ALWAYS_INLINE void *xtensa_dtlb_vaddr_read(uint32_t entry) { uint32_t vaddr; __asm__ volatile("rdtlb0 %0, %1\n\t" : "=a" (vaddr) : "a" (entry)); return (void *)(vaddr & XTENSA_MMU_PTE_VPN_MASK); } /** * @brief Get the physical address associated with a particular data TLB entry. * * @param entry TLB entry to be queried. */ static ALWAYS_INLINE uint32_t xtensa_dtlb_paddr_read(uint32_t entry) { uint32_t paddr; __asm__ volatile("rdtlb1 %0, %1\n\t" : "=a" (paddr) : "a" (entry)); return (paddr & XTENSA_MMU_PTE_PPN_MASK); } /** * @brief Get the virtual address associated with a particular instruction TLB entry. * * @param entry TLB entry to be queried. */ static ALWAYS_INLINE void *xtensa_itlb_vaddr_read(uint32_t entry) { uint32_t vaddr; __asm__ volatile("ritlb0 %0, %1\n\t" : "=a" (vaddr), "+a" (entry)); return (void *)(vaddr & XTENSA_MMU_PTE_VPN_MASK); } /** * @brief Get the physical address associated with a particular instruction TLB entry. * * @param entry TLB entry to be queried. */ static ALWAYS_INLINE uint32_t xtensa_itlb_paddr_read(uint32_t entry) { uint32_t paddr; __asm__ volatile("ritlb1 %0, %1\n\t" : "=a" (paddr), "+a" (entry)); return (paddr & XTENSA_MMU_PTE_PPN_MASK); } /** * @brief Probe for instruction TLB entry from a virtual address. * * @param vaddr Virtual address. * * @return Return of the PITLB instruction. */ static ALWAYS_INLINE uint32_t xtensa_itlb_probe(void *vaddr) { uint32_t ret; __asm__ __volatile__("pitlb %0, %1\n\t" : "=a" (ret) : "a" ((uint32_t)vaddr)); return ret; } /** * @brief Probe for data TLB entry from a virtual address. * * @param vaddr Virtual address. * * @return Return of the PDTLB instruction. */ static ALWAYS_INLINE uint32_t xtensa_dtlb_probe(void *vaddr) { uint32_t ret; __asm__ __volatile__("pdtlb %0, %1\n\t" : "=a" (ret) : "a" ((uint32_t)vaddr)); return ret; } /** * @brief Invalidate an instruction TLB entry associated with a virtual address. * * This invalidated an instruction TLB entry associated with a virtual address * if such TLB entry exists. Otherwise, do nothing. * * @param vaddr Virtual address. */ static inline void xtensa_itlb_vaddr_invalidate(void *vaddr) { uint32_t entry = xtensa_itlb_probe(vaddr); if (entry & XTENSA_MMU_PITLB_HIT) { xtensa_itlb_entry_invalidate_sync(entry); } } /** * @brief Invalidate a data TLB entry associated with a virtual address. * * This invalidated a data TLB entry associated with a virtual address * if such TLB entry exists. Otherwise, do nothing. * * @param vaddr Virtual address. */ static inline void xtensa_dtlb_vaddr_invalidate(void *vaddr) { uint32_t entry = xtensa_dtlb_probe(vaddr); if (entry & XTENSA_MMU_PDTLB_HIT) { xtensa_dtlb_entry_invalidate_sync(entry); } } /** * @brief Tell hardware to use a page table very first time after boot. * * @param l1_page Pointer to the page table to be used. */ void xtensa_init_paging(uint32_t *l1_page); /** * @brief Switch to a new page table. * * @param asid The ASID of the memory domain associated with the incoming page table. * @param l1_page Page table to be switched to. */ void xtensa_set_paging(uint32_t asid, uint32_t *l1_page); /** * @} */ #endif /* ZEPHYR_ARCH_XTENSA_XTENSA_MMU_PRIV_H_ */