#include <types.h>
#include <kern/errno.h>
#include <lib.h>
#include <spinlock.h>
#include <synch.h>
#include <thread.h>
#include <addrspace.h>
#include <synch.h>
#include <wchan.h>
#include <vm.h>
#include <vm/page.h>
#include <vm/region.h>
#include <vm/swap.h>
#include <current.h>
#include <machine/coremap.h>
struct wchan *wc_transit;
static
int
vm_page_new( struct vm_page **vmp_ret, paddr_t *paddr_ret ) {
struct vm_page *vmp;
paddr_t paddr;
vmp = vm_page_create();
if( vmp == NULL )
return ENOMEM;
vmp->vmp_swapaddr = swap_alloc();
if( vmp->vmp_swapaddr == INVALID_SWAPADDR ) {
vm_page_destroy( vmp );
return ENOSPC;
}
paddr = coremap_alloc( vmp, true );
if( paddr == INVALID_PADDR ) {
vm_page_destroy( vmp );
return ENOSPC;
}
vm_page_lock( vmp );
KASSERT( coremap_is_wired( paddr ) );
vmp->vmp_paddr = paddr;
*vmp_ret = vmp;
*paddr_ret = paddr;
return 0;
}
static
void
vm_page_acquire( struct vm_page *vmp ) {
paddr_t paddr;
paddr_t wired;
wired = INVALID_PADDR;
vm_page_lock( vmp );
for( ;; ) {
paddr = vmp->vmp_paddr & PAGE_FRAME;
if( paddr == wired )
break;
vm_page_unlock( vmp );
if( wired != INVALID_PADDR )
coremap_unwire( wired );
if( paddr == INVALID_PADDR ) {
vm_page_lock( vmp );
KASSERT( (vmp->vmp_paddr & PAGE_FRAME) == INVALID_PADDR );
break;
}
coremap_wire( paddr );
wired = paddr;
vm_page_lock( vmp );
}
if( paddr != INVALID_PADDR )
KASSERT( coremap_is_wired( paddr ) );
KASSERT( spinlock_do_i_hold( &vmp->vmp_lk ) );
}
void
vm_page_destroy( struct vm_page *vmp ) {
paddr_t paddr;
vm_page_acquire( vmp );
paddr = vmp->vmp_paddr & PAGE_FRAME;
if( paddr != INVALID_PADDR ) {
vmp->vmp_paddr = INVALID_PADDR;
KASSERT( coremap_is_wired( paddr ) );
vm_page_unlock( vmp );
coremap_free( paddr, false );
}
else {
vm_page_unlock( vmp );
}
if( vmp->vmp_swapaddr != INVALID_SWAPADDR )
swap_dealloc( vmp->vmp_swapaddr );
spinlock_cleanup( &vmp->vmp_lk );
kfree( vmp );
}
void
vm_page_lock( struct vm_page *vmp ) {
KASSERT( !spinlock_do_i_hold( &vmp->vmp_lk ) );
KASSERT( curthread->t_vmp_count == 0 || curthread->t_clone );
spinlock_acquire( &vmp->vmp_lk );
++curthread->t_vmp_count;
}
void
vm_page_unlock( struct vm_page *vmp ) {
KASSERT( spinlock_do_i_hold( &vmp->vmp_lk ) );
KASSERT( curthread->t_vmp_count == 1 || curthread->t_clone );
spinlock_release( &vmp->vmp_lk );
--curthread->t_vmp_count;
}
int
vm_page_clone( struct vm_page *source, struct vm_page **target ) {
struct vm_page *vmp;
int res;
paddr_t paddr;
paddr_t source_paddr;
off_t swap_addr;
curthread->t_clone = 1;
res = vm_page_new( &vmp, &paddr );
if( res ) {
curthread->t_clone = 0;
return res;
}
KASSERT( coremap_is_wired( paddr ) );
KASSERT( spinlock_do_i_hold( &vmp->vmp_lk ) );
KASSERT( curthread->t_vmp_count == 1 );
vm_page_acquire( source );
source_paddr = source->vmp_paddr & PAGE_FRAME;
if( source_paddr == INVALID_PADDR ) {
swap_addr = source->vmp_swapaddr;
vm_page_unlock( source );
source_paddr = coremap_alloc( source, true );
if( source_paddr == INVALID_PADDR ) {
coremap_unwire( paddr );
vm_page_unlock( vmp );
vm_page_destroy( vmp );
curthread->t_clone = 0;
return ENOMEM;
}
LOCK_PAGING_GIANT();
swap_in( source_paddr, swap_addr );
vm_page_lock( source );
UNLOCK_PAGING_GIANT();
KASSERT( (source->vmp_paddr & PAGE_FRAME) == INVALID_PADDR );
source->vmp_paddr = source_paddr;
}
KASSERT( coremap_is_wired( source_paddr ) );
KASSERT( coremap_is_wired( paddr ) );
coremap_clone( source_paddr, paddr );
vm_page_unlock( source );
vm_page_unlock( vmp );
coremap_unwire( source_paddr );
coremap_unwire( paddr );
*target = vmp;
curthread->t_clone = 0;
return 0;
}
struct vm_page *
vm_page_create( ) {
struct vm_page *vmp;
vmp = kmalloc( sizeof( struct vm_page ) );
if( vmp == NULL )
return NULL;
spinlock_init( &vmp->vmp_lk );
vmp->vmp_paddr = INVALID_PADDR;
vmp->vmp_swapaddr = INVALID_SWAPADDR;
vmp->vmp_in_transit = false;
return vmp;
}
int
vm_page_new_blank( struct vm_page **ret ) {
struct vm_page *vmp;
paddr_t paddr;
int res;
res = vm_page_new( &vmp, &paddr );
if( res )
return res;
KASSERT( coremap_is_wired( paddr ) );
vm_page_unlock( vmp );
coremap_zero( paddr );
coremap_unwire( paddr );
*ret = vmp;
return 0;
}
static
void
vm_page_wait_for_transit( struct vm_page *vmp ) {
wchan_lock( wc_transit );
vm_page_unlock( vmp );
KASSERT( curthread->t_vmp_count == 0 );
wchan_sleep( wc_transit );
vm_page_lock( vmp );
}
int
vm_page_fault( struct vm_page *vmp, struct addrspace *as, int fault_type, vaddr_t fault_vaddr ) {
paddr_t paddr;
int writeable;
off_t swap_addr;
bool success;
(void) as;
switch( fault_type ) {
case VM_FAULT_READ:
writeable = 0;
break;
case VM_FAULT_WRITE:
case VM_FAULT_READONLY:
writeable = 1;
break;
default:
return EINVAL;
}
do {
success = true;
vm_page_lock( vmp );
while( vmp->vmp_in_transit == true )
vm_page_wait_for_transit( vmp );
vm_page_unlock( vmp );
vm_page_acquire( vmp );
if( vmp->vmp_in_transit ) {
success = false;
coremap_unwire( (vmp->vmp_paddr & PAGE_FRAME ) );
vm_page_unlock( vmp );
}
} while( !success );
paddr = vmp->vmp_paddr & PAGE_FRAME;
if( paddr == INVALID_PADDR ) {
swap_addr = vmp->vmp_swapaddr;
KASSERT( vmp->vmp_swapaddr != INVALID_SWAPADDR );
vm_page_unlock( vmp );
paddr = coremap_alloc( vmp, true );
if( paddr == INVALID_PADDR )
return ENOMEM;
KASSERT( coremap_is_wired( paddr ) );
LOCK_PAGING_GIANT();
swap_in( paddr, swap_addr );
vm_page_lock( vmp );
UNLOCK_PAGING_GIANT();
KASSERT( vmp->vmp_paddr == INVALID_PADDR );
KASSERT( vmp->vmp_swapaddr == swap_addr );
KASSERT( coremap_is_wired( paddr ) );
vmp->vmp_paddr = paddr;
}
vm_map( fault_vaddr, paddr, writeable );
coremap_unwire( paddr );
vm_page_unlock( vmp );
return 0;
}
void
vm_page_evict( struct vm_page *victim ) {
paddr_t paddr;
off_t swap_addr;
KASSERT( lock_do_i_hold( giant_paging_lock ) );
vm_page_lock( victim );
paddr = victim->vmp_paddr & PAGE_FRAME;
swap_addr = victim->vmp_swapaddr;
KASSERT( paddr != INVALID_PADDR );
KASSERT( swap_addr != INVALID_SWAPADDR );
KASSERT( coremap_is_wired( paddr ) );
KASSERT( victim->vmp_in_transit == false );
victim->vmp_in_transit = true;
vm_page_unlock( victim );
swap_out( paddr, swap_addr );
vm_page_lock( victim );
KASSERT( victim->vmp_in_transit == true );
KASSERT( (victim->vmp_paddr & PAGE_FRAME) == paddr );
KASSERT( coremap_is_wired( paddr ) );
victim->vmp_in_transit = false;
victim->vmp_paddr = INVALID_PADDR;
wchan_wakeall( wc_transit );
vm_page_unlock( victim );
}