// IBM_PROLOG_BEGIN_TAG // This is an automatically generated prolog. // // $Source: src/kernel/block.C $ // // IBM CONFIDENTIAL // // COPYRIGHT International Business Machines Corp. 2011 // // p1 // // Object Code Only (OCO) source materials // Licensed Internal Code Source Materials // IBM HostBoot Licensed Internal Code // // The source code for this program is not published or other- // wise divested of its trade secrets, irrespective of what has // been deposited with the U.S. Copyright Office. // // Origin: 30 // // IBM_PROLOG_END #include #include #include #include #include #include #include #include #include #include #include #include #include #include Block::~Block() { // Release shadow PTE array. delete[] iv_ptes; //Release message handlers delete iv_readMsgHdlr; delete iv_writeMsgHdlr; // Delete next block in the chain. if (iv_nextBlock) { delete iv_nextBlock; } } void Block::init(MessageQueue* i_msgQ) { // Create a shadow PTE for each page. iv_ptes = new ShadowPTE[iv_size / PAGESIZE]; if (i_msgQ != NULL) { //Create message handler to handle read operations for this block this->iv_readMsgHdlr = new BlockReadMsgHdlr(VmmManager::getLock(),i_msgQ,this); //Create message handler to handle write operations for this block this->iv_writeMsgHdlr = new BlockWriteMsgHdlr(VmmManager::getLock(),i_msgQ,this); } } ShadowPTE* Block::getPTE(uint64_t i_addr) const { return &iv_ptes[(i_addr - iv_baseAddr) / PAGESIZE]; }; bool Block::handlePageFault(task_t* i_task, uint64_t i_addr) { // Check containment, call down chain if address isn't in this block. if (!isContained(i_addr)) { return (iv_nextBlock ? iv_nextBlock->handlePageFault(i_task, i_addr) : false); } // Calculate page aligned virtual address. uint64_t l_addr_palign = (i_addr / PAGESIZE) * PAGESIZE; ShadowPTE* pte = getPTE(l_addr_palign); // If the page table entry has default permission settings if (getPermission(pte) == NO_ACCESS) { printkd("handle page fault.. Permission not set for addr = 0x%.lX\n", (uint64_t)l_addr_palign); // return false because permission have not been set. return false; } if (!pte->isPresent()) { if (this->iv_readMsgHdlr != NULL) { void* l_page = reinterpret_cast(pte->getPageAddr()); //If the page data is zero, create the page if (pte->getPage() == 0) { l_page = PageManager::allocatePage(); //Add to ShadowPTE pte->setPageAddr(reinterpret_cast(l_page)); } this->iv_readMsgHdlr->sendMessage(MSG_MM_RP_READ, reinterpret_cast(l_addr_palign),l_page,i_task); //Done(waiting for response) return true; } else if (pte->isAllocateFromZero()) { void* l_page = PageManager::allocatePage(); // set the permission of the physical address pte entry to NO_ACCESS now that // we have handled the page fault and have a SPTE entry for that VA. if (BaseSegment::mmSetPermission(reinterpret_cast(l_page), 0, NO_ACCESS)) { // Did not set permission.. printkd("handle page fault.. Set Permission failed for physical addr = 0x%.lX\n", (uint64_t)l_page); } memset(l_page, '\0', PAGESIZE); pte->setPageAddr(reinterpret_cast(l_page)); pte->setPresent(true); } else { return false; //TODO - Swap kernel base block pages for user pages } } PageTableManager::addEntry( l_addr_palign, pte->getPage(), (pte->isExecutable() ? EXECUTABLE : (pte->isWritable() ? WRITABLE : READ_ONLY))); return true; } void Block::setPhysicalPage(uint64_t i_vAddr, uint64_t i_pAddr, uint64_t i_access) { // Check containment, call down chain if address isn't in this block. if (!isContained(i_vAddr)) { if (iv_nextBlock) { iv_nextBlock->setPhysicalPage(i_vAddr, i_pAddr, i_access); } else { // No block owns this address. Code bug. kassert(iv_nextBlock); } return; } // Create virtual to physical mapping. ShadowPTE* pte = getPTE(i_vAddr); if (i_pAddr != 0) { pte->setPageAddr(i_pAddr); pte->setPresent(true); // Modified an SPTE, clear the HPTE. PageTableManager::delEntry(i_vAddr); } // If the page is already present, we might be changing permissions, so // clear the HPTE. else if (pte->isPresent()) { PageTableManager::delEntry(i_vAddr); } // make a call that sets the permssions on a // shadow page table entry if (setPermSPTE(pte, i_access)) { kassert(false); } } void Block::attachSPTE(void *i_vaddr) { uint64_t l_vaddr = reinterpret_cast(i_vaddr); ShadowPTE* l_pte = getPTE(l_vaddr); //Set the present bit for the address associated with this block l_pte->setPresent(true); //Add page table entry PageTableManager::addEntry((l_vaddr / PAGESIZE) * PAGESIZE, l_pte->getPage(), (l_pte->isExecutable() ? EXECUTABLE : (l_pte->isWritable() ? WRITABLE : READ_ONLY))); // update permission for the page that corresponds to the physical page addr. // now that we have handled the page fault. if (BaseSegment::mmSetPermission(reinterpret_cast(l_pte->getPageAddr()), 0, NO_ACCESS)) { printkd("Got an error trying to set permissions in handle Response msg handler \n"); } } uint64_t Block::findPhysicalAddress(uint64_t i_vaddr) const { uint64_t paddr = -EFAULT; if(!isContained(i_vaddr)) { return (iv_nextBlock ? iv_nextBlock->findPhysicalAddress(i_vaddr) : paddr); } ShadowPTE* pte = getPTE(i_vaddr); if (pte->isPresent() && pte->getPage() != 0) { paddr = pte->getPageAddr(); paddr += i_vaddr % PAGESIZE; } return paddr; } void Block::releaseAllPages() { // Release all pages from page table. PageTableManager::delRangeVA(iv_baseAddr, iv_baseAddr + iv_size); // Free all pages back to page manager. for(uint64_t page = iv_baseAddr; page < (iv_baseAddr + iv_size); page += PAGESIZE) { ShadowPTE* pte = getPTE(page); if (pte->isPresent()) { uint64_t addr = pte->getPageAddr(); if (0 != addr) { releaseSPTE(pte); PageManager::freePage(reinterpret_cast(addr)); } } } } void Block::updateRefCount( uint64_t i_vaddr, PageTableManager::UsageStats_t i_stats ) { // Check containment, call down chain if address isn't in this block. if (!isContained(i_vaddr)) { if (iv_nextBlock) { iv_nextBlock->updateRefCount(i_vaddr, i_stats); } else { // No block owns this address. Code bug. printk("updateRefCount> i_vaddr=0x%.lX\n", i_vaddr ); kassert(iv_nextBlock); } return; } ShadowPTE* spte = getPTE(i_vaddr); // Adjust the LRU statistics if( i_stats.R ) { spte->zeroLRU(); } else { spte->incLRU(); } // track the changed/dirty bit if (i_stats.C) { spte->setDirty( i_stats.C ); } } void Block::castOutPages(uint64_t i_type) { void* l_vaddr = NULL; // drill down if(iv_nextBlock) { iv_nextBlock->castOutPages(i_type); } // TODO We will eventually need to skip other blocks as well, such as // when the memory space grows. if(iv_baseAddr != 0) // Skip base area { size_t rw_constraint = 5; size_t ro_constraint = 3; if(i_type == VmmManager::CRITICAL) { rw_constraint = 2; ro_constraint = 1; } //printk("Block = %p:%ld\n",(void*)iv_baseAddr,iv_size / PAGESIZE); for(uint64_t page = iv_baseAddr; page < (iv_baseAddr + iv_size); page += PAGESIZE) { ShadowPTE* pte = getPTE(page); if (pte->isPresent() && (0 != pte->getPageAddr())) { //if(pte->isExecutable()) printk("x"); //else if(pte->isWritable()) printk("w"); //else printk("r"); //printk("%d",(int)pte->getLRU()); if(pte->isWritable()) { if(pte->getLRU() > rw_constraint && pte->isWriteTracked()) { //'EVICT' single page l_vaddr = reinterpret_cast(page); this->removePages(VmmManager::EVICT,l_vaddr, PAGESIZE,NULL); //printk("+"); } } else // ro and/or executable { if(pte->getLRU() > ro_constraint) { //'EVICT' single page l_vaddr = reinterpret_cast(page); this->removePages(VmmManager::EVICT,l_vaddr, PAGESIZE,NULL); } } } } } } int Block::mmSetPermission(uint64_t i_va, uint64_t i_size,uint64_t i_access_type) { int l_rc = 0; // Need to align the page address and the size on a page boundary. uint64_t l_aligned_va = ALIGN_PAGE_DOWN(i_va); uint64_t l_aligned_size = ALIGN_PAGE(i_size); if(!isContained(l_aligned_va)) { return (iv_nextBlock ? iv_nextBlock->mmSetPermission(i_va,i_size,i_access_type):-EINVAL); } //printk("\n aligned VA = 0x%.lX aligned size = %ld access_type = 0x%.lX\n", l_aligned_va, l_aligned_size, i_access_type); // if i_size is zero..we are only updating 1 page..inncrement the size to 1 page if (i_size == 0) { l_aligned_size+=PAGESIZE; } // loop through all the pages asked for based on passed aligned // Virtual address and passed in aligned size. for(uint64_t cur_page_addr = l_aligned_va; cur_page_addr < (l_aligned_va + l_aligned_size); cur_page_addr += PAGESIZE) { ShadowPTE* spte = getPTE(cur_page_addr); // if the page present need to delete the hardware // page table entry before we set permissions. if (spte->isPresent()) { // delete the hardware page table entry PageTableManager::delEntry(cur_page_addr); } if (setPermSPTE(spte, i_access_type)) { printkd(" SET PERMISSIONS.. FAILED \n"); return -EINVAL; } } return l_rc; } int Block::setPermSPTE( ShadowPTE* i_spte, uint64_t i_access_type) { // If read_only if ( i_access_type & READ_ONLY) { // If the writable, executable, write_tracked // or allocate from zero access bits are set // we have an invalid combination.. return error if ((i_access_type & WRITABLE) || (i_access_type & EXECUTABLE) || (i_access_type & WRITE_TRACKED) || (i_access_type & ALLOCATE_FROM_ZERO)) { return -EINVAL; } // Set the bits after we have verified // the valid combinations so if we are setting // permissions on a range only the first page would // get set to READ_ONLY before we fail. i_spte->setReadable(true); i_spte->setExecutable(false); i_spte->setWritable(false); } // if writable else if ( i_access_type & WRITABLE) { if (i_access_type & EXECUTABLE) { return -EINVAL; } i_spte->setReadable(true); i_spte->setWritable(true); i_spte->setExecutable(false); } // if executable else if ( i_access_type & EXECUTABLE) { i_spte->setReadable(true); i_spte->setExecutable(true); i_spte->setWritable(false); } // if write_tracked if ( i_access_type & WRITE_TRACKED) { // TODO.. fail if no message handler when trying to // set a page to write tracked. // If the page is already READ_ONLY // you cannot set to WRITE_TRACKED if (getPermission(i_spte) == READ_ONLY) { return -EINVAL; } i_spte->setWriteTracked(true); } else { i_spte->setWriteTracked(false); } // if Allocate from zero if ( i_access_type & ALLOCATE_FROM_ZERO) { // If the page is already READ_ONLY // you cannot set to ALLOCATE_FROM_ZERO if (getPermission(i_spte) == READ_ONLY) { return -EINVAL; } i_spte->setAllocateFromZero(true); } // not allocated from zero else { i_spte->setAllocateFromZero(false); } // if no access if ( i_access_type & NO_ACCESS) { i_spte->setReadable(false); i_spte->setExecutable(false); i_spte->setWritable(false); i_spte->setAllocateFromZero(false); i_spte->setWriteTracked(false); } return 0; } uint64_t Block::getPermission( ShadowPTE* i_spte) { uint64_t l_accessType = 0; if ((!i_spte->isReadable())&& (!i_spte->isExecutable())&& (!i_spte->isWritable())&& (!i_spte->isAllocateFromZero())&& (!i_spte->isWriteTracked())) { return NO_ACCESS; } if (i_spte->isReadable()&& (!i_spte->isExecutable())&& (!i_spte->isWritable())) { return READ_ONLY; } if (i_spte->isWritable()) { l_accessType |= WRITABLE; } if (i_spte->isExecutable()) { l_accessType |= EXECUTABLE; } if (i_spte->isWriteTracked()) { l_accessType |= WRITE_TRACKED; } if (i_spte->isAllocateFromZero()) { l_accessType |= ALLOCATE_FROM_ZERO; } return l_accessType; } int Block::removePages(VmmManager::PAGE_REMOVAL_OPS i_op, void* i_vaddr, uint64_t i_size, task_t* i_task) { uint64_t l_vaddr = reinterpret_cast(i_vaddr); //Align virtual address & size to page boundary /*The given virtual address will be 'rounded' down to the nearest page boundary, along with the given size will be 'rounded' up to the nearest divisible page size.*/ uint64_t l_aligned_va = ALIGN_PAGE_DOWN(l_vaddr); uint64_t l_aligned_size = ALIGN_PAGE(i_size); //Find block containing virtual address if(!this->isContained(l_aligned_va)) { return (iv_nextBlock ? iv_nextBlock->removePages(i_op,i_vaddr,i_size,i_task):-EINVAL); } else if ((l_aligned_va+l_aligned_size) > (this->iv_baseAddr+this->iv_size)) { return -EINVAL; } //Perform requested page removal operation for (l_vaddr = l_aligned_va;l_vaddr < (l_aligned_va+l_aligned_size); l_vaddr+= PAGESIZE) { ShadowPTE* pte = getPTE(l_vaddr); uint64_t pageAddr = pte->getPageAddr(); if (pte->isPresent() && (0 != pageAddr)) { //Delete from HW page table immediately PageTableManager::delEntry(l_vaddr); if (pte->isDirty() && pte->isWriteTracked() && this->iv_writeMsgHdlr != NULL) { releaseSPTE(pte); //Send write msg with the page address if (i_task != NULL) { this->iv_writeMsgHdlr->incMsgCount(i_task); } // TODO.. Need to map the physical page here before sending the message because // the RP uses the physical page off the message queue.. (comment from patrick) this->iv_writeMsgHdlr->addVirtAddr( reinterpret_cast(l_vaddr),pageAddr); this->iv_writeMsgHdlr->sendMessage(MSG_MM_RP_WRITE, reinterpret_cast(l_vaddr), reinterpret_cast(pageAddr),i_task); } else if (pte->isDirty() && !pte->isWriteTracked() && i_op == VmmManager::EVICT) { //Skip page } else if (i_op != VmmManager::FLUSH) { //'Release' page entry releaseSPTE(pte); PageManager::freePage(reinterpret_cast(pageAddr)); } } } return 0; } void Block::releaseSPTE(ShadowPTE* i_pte) { i_pte->setDirty(false); i_pte->setPresent(false); // set the permission of the physical address pte entry back to writable now that // the associated VA Spte has been released. if (BaseSegment::mmSetPermission(reinterpret_cast(i_pte->getPageAddr()), 0, WRITABLE)) { printkd("Got an error setting permission during Flush\n"); } i_pte->setPageAddr(NULL); }