diff options
author | Alex AUVOLAT <alex.auvolat@ens.fr> | 2014-03-28 09:44:01 +0100 |
---|---|---|
committer | Alex AUVOLAT <alex.auvolat@ens.fr> | 2014-03-28 09:44:01 +0100 |
commit | bdce62a91076aa1a076d484ce1d8564a8ba0988f (patch) | |
tree | 27fae66616a07caaa18fbbf7ce1c39d91fea6d29 /sos-code-article6/hwcore/paging.c | |
parent | b5dc9d799f8eca793a4fd1d9b5111155ae6af6d8 (diff) | |
download | SOS-bdce62a91076aa1a076d484ce1d8564a8ba0988f.tar.gz SOS-bdce62a91076aa1a076d484ce1d8564a8ba0988f.zip |
Import article 5, does not compile.
Diffstat (limited to 'sos-code-article6/hwcore/paging.c')
-rw-r--r-- | sos-code-article6/hwcore/paging.c | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/sos-code-article6/hwcore/paging.c b/sos-code-article6/hwcore/paging.c new file mode 100644 index 0000000..11f3da6 --- /dev/null +++ b/sos-code-article6/hwcore/paging.c @@ -0,0 +1,465 @@ +/* Copyright (C) 2004 David Decotigny + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + USA. +*/ +#include <sos/physmem.h> +#include <sos/klibc.h> +#include <sos/assert.h> + +#include "paging.h" + + +/** The structure of a page directory entry. See Intel vol 3 section + 3.6.4 */ +struct x86_pde +{ + sos_ui32_t present :1; /* 1=PT mapped */ + sos_ui32_t write :1; /* 0=read-only, 1=read/write */ + sos_ui32_t user :1; /* 0=supervisor, 1=user */ + sos_ui32_t write_through :1; /* 0=write-back, 1=write-through */ + sos_ui32_t cache_disabled :1; /* 1=cache disabled */ + sos_ui32_t accessed :1; /* 1=read/write access since last clear */ + sos_ui32_t zero :1; /* Intel reserved */ + sos_ui32_t page_size :1; /* 0=4kB, 1=4MB or 2MB (depending on PAE) */ + sos_ui32_t global_page :1; /* Ignored (Intel reserved) */ + sos_ui32_t custom :3; /* Do what you want with them */ + sos_ui32_t pt_paddr :20; +} __attribute__ ((packed)); + + +/** The structure of a page table entry. See Intel vol 3 section + 3.6.4 */ +struct x86_pte +{ + sos_ui32_t present :1; /* 1=PT mapped */ + sos_ui32_t write :1; /* 0=read-only, 1=read/write */ + sos_ui32_t user :1; /* 0=supervisor, 1=user */ + sos_ui32_t write_through :1; /* 0=write-back, 1=write-through */ + sos_ui32_t cache_disabled :1; /* 1=cache disabled */ + sos_ui32_t accessed :1; /* 1=read/write access since last clear */ + sos_ui32_t dirty :1; /* 1=write access since last clear */ + sos_ui32_t zero :1; /* Intel reserved */ + sos_ui32_t global_page :1; /* 1=No TLB invalidation upon cr3 switch + (when PG set in cr4) */ + sos_ui32_t custom :3; /* Do what you want with them */ + sos_ui32_t paddr :20; +} __attribute__ ((packed)); + + +/** Structure of the x86 CR3 register: the Page Directory Base + Register. See Intel x86 doc Vol 3 section 2.5 */ +struct x86_pdbr +{ + sos_ui32_t zero1 :3; /* Intel reserved */ + sos_ui32_t write_through :1; /* 0=write-back, 1=write-through */ + sos_ui32_t cache_disabled :1; /* 1=cache disabled */ + sos_ui32_t zero2 :7; /* Intel reserved */ + sos_ui32_t pd_paddr :20; +} __attribute__ ((packed)); + + +/** + * Helper macro to control the MMU: invalidate the TLB entry for the + * page located at the given virtual address. See Intel x86 vol 3 + * section 3.7. + */ +#define invlpg(vaddr) \ + do { \ + __asm__ __volatile__("invlpg %0"::"m"(*((unsigned *)(vaddr)))); \ + } while(0) + + +/** + * Helper macro to control the MMU: invalidate the whole TLB. See + * Intel x86 vol 3 section 3.7. + */ +#define flush_tlb() \ + do { \ + unsigned long tmpreg; \ + asm volatile("movl %%cr3,%0\n\tmovl %0,%%cr3" :"=r" \ + (tmpreg) : :"memory"); \ + } while (0) + + +/** + * Helper macro to compute the index in the PD for the given virtual + * address + */ +#define virt_to_pd_index(vaddr) \ + (((unsigned)(vaddr)) >> 22) + + +/** + * Helper macro to compute the index in the PT for the given virtual + * address + */ +#define virt_to_pt_index(vaddr) \ + ( (((unsigned)(vaddr)) >> 12) & 0x3ff ) + + +/** + * Helper macro to compute the offset in the page for the given virtual + * address + */ +#define virt_to_page_offset(vaddr) \ + (((unsigned)(vaddr)) & SOS_PAGE_MASK) + + +/** + * Helper function to map a page in the pd.\ Suppose that the RAM + * is identity mapped to resolve PT actual (CPU) address from the PD + * entry + */ +static sos_ret_t paging_setup_map_helper(struct x86_pde * pd, + sos_paddr_t ppage, + sos_vaddr_t vaddr) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + + /* Make sure the page table was mapped */ + struct x86_pte * pt; + if (pd[index_in_pd].present) + { + pt = (struct x86_pte*) (pd[index_in_pd].pt_paddr << 12); + + /* If we allocate a new entry in the PT, increase its reference + count. This test will always be TRUE here, since the setup + routine scans the kernel pages in a strictly increasing + order: at each step, the map will result in the allocation of + a new PT entry. For the sake of clarity, we keep the test + here. */ + if (! pt[index_in_pt].present) + sos_physmem_ref_physpage_at((sos_paddr_t)pt); + + /* The previous test should always be TRUE */ + else + SOS_ASSERT_FATAL(FALSE); /* indicate a fatal error */ + } + else + { + /* No : allocate a new one */ + pt = (struct x86_pte*) sos_physmem_ref_physpage_new(FALSE); + if (! pt) + return -SOS_ENOMEM; + + memset((void*)pt, 0x0, SOS_PAGE_SIZE); + + pd[index_in_pd].present = TRUE; + pd[index_in_pd].write = 1; /* It would be too complicated to + determine whether it + corresponds to a real R/W area + of the kernel code/data or + read-only */ + pd[index_in_pd].pt_paddr = ((sos_paddr_t)pt) >> 12; + } + + + /* Map the page in the page table */ + pt[index_in_pt].present = 1; + pt[index_in_pt].write = 1; /* It would be too complicated to + determine whether it corresponds to + a real R/W area of the kernel + code/data or R/O only */ + pt[index_in_pt].user = 0; + pt[index_in_pt].paddr = ppage >> 12; + + return SOS_OK; +} + + +sos_ret_t sos_paging_subsystem_setup(sos_paddr_t identity_mapping_base, + sos_paddr_t identity_mapping_top) +{ + /* The PDBR we will setup below */ + struct x86_pdbr cr3; + + /* Get the PD for the kernel */ + struct x86_pde * pd + = (struct x86_pde*) sos_physmem_ref_physpage_new(FALSE); + + /* The iterator for scanning the kernel area */ + sos_paddr_t paddr; + + /* Reset the PD. For the moment, there is still an IM for the whole + RAM, so that the paddr are also vaddr */ + memset((void*)pd, + 0x0, + SOS_PAGE_SIZE); + + /* Identity-map the identity_mapping_* area */ + for (paddr = identity_mapping_base ; + paddr < identity_mapping_top ; + paddr += SOS_PAGE_SIZE) + { + if (paging_setup_map_helper(pd, paddr, paddr)) + return -SOS_ENOMEM; + } + + /* Identity-map the PC-specific BIOS/Video area */ + for (paddr = BIOS_N_VIDEO_START ; + paddr < BIOS_N_VIDEO_END ; + paddr += SOS_PAGE_SIZE) + { + if (paging_setup_map_helper(pd, paddr, paddr)) + return -SOS_ENOMEM; + } + + /* Ok, kernel is now identity mapped in the PD. We still have to set + up the mirroring */ + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].present = TRUE; + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].write = 1; + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].user = 0; + pd[virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)].pt_paddr + = ((sos_paddr_t)pd)>>12; + + /* We now just have to configure the MMU to use our PD. See Intel + x86 doc vol 3, section 3.6.3 */ + memset(& cr3, 0x0, sizeof(struct x86_pdbr)); /* Reset the PDBR */ + cr3.pd_paddr = ((sos_paddr_t)pd) >> 12; + + /* Actual loading of the PDBR in the MMU: setup cr3 + bits 31[Paging + Enabled] and 16[Write Protect] of cr0, see Intel x86 doc vol 3, + sections 2.5, 3.6.1 and 4.11.3 + note table 4-2 */ + asm volatile ("movl %0,%%cr3\n\t" + "movl %%cr0,%%eax\n\t" + "orl $0x80010000, %%eax\n\t" /* bit 31 | bit 16 */ + "movl %%eax,%%cr0\n\t" + "jmp 1f\n\t" + "1:\n\t" + "movl $2f, %%eax\n\t" + "jmp *%%eax\n\t" + "2:\n\t" ::"r"(cr3):"memory","eax"); + + /* + * Here, the only memory available is: + * - The BIOS+video area + * - the identity_mapping_base .. identity_mapping_top area + * - the PD mirroring area (4M) + * All accesses to other virtual addresses will generate a #PF + */ + + return SOS_OK; +} + + +/* Suppose that the current address is configured with the mirroring + * enabled to access the PD and PT. */ +sos_ret_t sos_paging_map(sos_paddr_t ppage_paddr, + sos_vaddr_t vpage_vaddr, + sos_bool_t is_user_page, + sos_ui32_t flags) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vpage_vaddr); + unsigned index_in_pt = virt_to_pt_index(vpage_vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* The mapping of anywhere in the PD mirroring is FORBIDDEN ;) */ + if ((vpage_vaddr >= SOS_PAGING_MIRROR_VADDR) + && (vpage_vaddr < SOS_PAGING_MIRROR_VADDR + SOS_PAGING_MIRROR_SIZE)) + return -SOS_EINVAL; + + /* Map a page for the PT if necessary */ + if (! pd[index_in_pd].present) + { + + /* No : allocate a new one */ + sos_paddr_t pt_ppage + = sos_physmem_ref_physpage_new(! (flags & SOS_VM_MAP_ATOMIC)); + if (! pt_ppage) + { + return -SOS_ENOMEM; + } + + pd[index_in_pd].present = TRUE; + pd[index_in_pd].write = 1; /* Ignored in supervisor mode, see + Intel vol 3 section 4.12 */ + pd[index_in_pd].user = (is_user_page)?1:0; + pd[index_in_pd].pt_paddr = ((sos_paddr_t)pt_ppage) >> 12; + + /* + * The PT is now mapped in the PD mirroring + */ + + /* Invalidate TLB for the page we just added */ + invlpg(pt); + + /* Reset this new PT */ + memset((void*)pt, 0x0, SOS_PAGE_SIZE); + } + + /* If we allocate a new entry in the PT, increase its reference + count. */ + else if (! pt[index_in_pt].present) + sos_physmem_ref_physpage_at(pd[index_in_pd].pt_paddr << 12); + + /* Otherwise, that means that a physical page is implicitely + unmapped */ + else + sos_physmem_unref_physpage(pt[index_in_pt].paddr << 12); + + /* Map the page in the page table */ + pt[index_in_pt].present = TRUE; + pt[index_in_pt].write = (flags & SOS_VM_MAP_PROT_WRITE)?1:0; + pt[index_in_pt].user = (is_user_page)?1:0; + pt[index_in_pt].paddr = ppage_paddr >> 12; + sos_physmem_ref_physpage_at(ppage_paddr); + + /* + * The page is now mapped in the current address space + */ + + /* Invalidate TLB for the page we just added */ + invlpg(vpage_vaddr); + + return SOS_OK; +} + + +sos_ret_t sos_paging_unmap(sos_vaddr_t vpage_vaddr) +{ + sos_ret_t pt_unref_retval; + + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vpage_vaddr); + unsigned index_in_pt = virt_to_pt_index(vpage_vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return -SOS_EINVAL; + if (! pt[index_in_pt].present) + return -SOS_EINVAL; + + /* The unmapping of anywhere in the PD mirroring is FORBIDDEN ;) */ + if ((vpage_vaddr >= SOS_PAGING_MIRROR_VADDR) + && (vpage_vaddr < SOS_PAGING_MIRROR_VADDR + SOS_PAGING_MIRROR_SIZE)) + return -SOS_EINVAL; + + /* Reclaim the physical page */ + sos_physmem_unref_physpage(pt[index_in_pt].paddr << 12); + + /* Unmap the page in the page table */ + memset(pt + index_in_pt, 0x0, sizeof(struct x86_pte)); + + /* Invalidate TLB for the page we just unmapped */ + invlpg(vpage_vaddr); + + /* Reclaim this entry in the PT, which may free the PT */ + pt_unref_retval = sos_physmem_unref_physpage(pd[index_in_pd].pt_paddr << 12); + SOS_ASSERT_FATAL(pt_unref_retval >= 0); + if (pt_unref_retval == TRUE) + /* If the PT is now completely unused... */ + { + union { struct x86_pde pde; sos_ui32_t ui32; } u; + + /* + * Reset the PDE + */ + + /* Mark the PDE as unavailable */ + u.ui32 = 0; + + /* Update the PD */ + pd[index_in_pd] = u.pde; + + /* Update the TLB */ + invlpg(pt); + } + + return SOS_OK; +} + + +int sos_paging_get_prot(sos_vaddr_t vaddr) +{ + int retval; + + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return SOS_VM_MAP_PROT_NONE; + if (! pt[index_in_pt].present) + return SOS_VM_MAP_PROT_NONE; + + /* Default access right of an available page is "read" on x86 */ + retval = SOS_VM_MAP_PROT_READ; + if (pd[index_in_pd].write && pt[index_in_pt].write) + retval |= SOS_VM_MAP_PROT_WRITE; + + return retval; +} + + +sos_paddr_t sos_paging_get_paddr(sos_vaddr_t vaddr) +{ + /* Get the page directory entry and table entry index for this + address */ + unsigned index_in_pd = virt_to_pd_index(vaddr); + unsigned index_in_pt = virt_to_pt_index(vaddr); + unsigned offset_in_page = virt_to_page_offset(vaddr); + + /* Get the PD of the current context */ + struct x86_pde *pd = (struct x86_pde*) + (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*virt_to_pd_index(SOS_PAGING_MIRROR_VADDR)); + + /* Address of the PT in the mirroring */ + struct x86_pte * pt = (struct x86_pte*) (SOS_PAGING_MIRROR_VADDR + + SOS_PAGE_SIZE*index_in_pd); + + /* No page mapped at this address ? */ + if (! pd[index_in_pd].present) + return (sos_paddr_t)NULL; + if (! pt[index_in_pt].present) + return (sos_paddr_t)NULL; + + return (pt[index_in_pt].paddr << 12) + offset_in_page; +} |