/* * Copyright (c) 2000-2010 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #if CONFIG_FREEZE #ifndef CONFIG_MEMORYSTATUS #error "CONFIG_FREEZE defined without matching CONFIG_MEMORYSTATUS" #endif #include /* * Indicates that a page has been faulted back in. */ #define FREEZER_OFFSET_ABSENT ((vm_object_offset_t)(-1)) lck_grp_attr_t default_freezer_handle_lck_grp_attr; lck_grp_t default_freezer_handle_lck_grp; void default_freezer_init(void) { lck_grp_attr_setdefault(&default_freezer_handle_lck_grp_attr); lck_grp_init(&default_freezer_handle_lck_grp, "default_freezer_handle", &default_freezer_handle_lck_grp_attr); } /* * Create the mapping table that will * tell us the object/offset pair that * corresponds to the page being sent * out or being brought back in. */ default_freezer_mapping_table_t default_freezer_mapping_create(vm_object_t object, vm_offset_t offset) { default_freezer_mapping_table_t table; table = kalloc(sizeof(struct default_freezer_mapping_table)); if (table) { memset(table, 0, sizeof(*table)); } else { panic("Could not allocate mapping table\n"); } table->object = object; table->offset = offset; return table; } /* * Table modifications/lookup are done behind * the compact_object lock. */ void default_freezer_mapping_free(default_freezer_mapping_table_t *table_p, boolean_t all) { default_freezer_mapping_table_t freezer_table = *table_p; assert(freezer_table); if (all) { do { default_freezer_mapping_table_t next = freezer_table->next; kfree(freezer_table, sizeof(*freezer_table)); freezer_table = next; } while (freezer_table); } else { kfree(freezer_table, sizeof(*freezer_table)); } } kern_return_t default_freezer_mapping_store( default_freezer_mapping_table_t table, memory_object_offset_t table_offset, memory_object_t memory_object, memory_object_offset_t offset) { default_freezer_mapping_table_entry_t entry; uint32_t index; assert(table); while (table->next) { table = table->next; } if (table->index >= MAX_FREEZE_TABLE_ENTRIES) { vm_object_t compact_object = table->object; default_freezer_mapping_table_t next; next = default_freezer_mapping_create(compact_object, table_offset); if (!next) { return KERN_FAILURE; } table->next = next; } index = (table)->index++; entry = &(table)->entry[index]; entry->memory_object = memory_object; entry->offset = offset; return KERN_SUCCESS; } kern_return_t default_freezer_mapping_update( default_freezer_mapping_table_t table, memory_object_t memory_object, memory_object_offset_t offset, memory_object_offset_t *table_offset, /*OUT: contains the offset into the compact object*/ boolean_t remove_entry) { kern_return_t kr = KERN_SUCCESS; vm_object_offset_t compact_offset; default_freezer_mapping_table_entry_t entry; uint32_t index = 0; if (table == NULL){ return KERN_FAILURE; } compact_offset = table->offset; while (1) { if (index >= table->index) { if (table->next) { table = table->next; index = 0; } else { /* End of tables and we didn't find our candidate entry */ kr = KERN_FAILURE; break; } } entry = &table->entry[index]; if (memory_object == entry->memory_object && offset == entry->offset) { if (remove_entry == TRUE) { /* * Mark the page absent whilst retaining the object * for cleanup during thaw. */ entry->offset = FREEZER_OFFSET_ABSENT; } if (table_offset != NULL) { *table_offset = compact_offset; } break; } index++; compact_offset += PAGE_SIZE; } return kr; } /* * Create a freezer memory object for this * vm object. This will be one of the vm * objects that will pack the compact object. */ void default_freezer_memory_object_create( vm_object_t object, default_freezer_handle_t df_handle) { default_freezer_memory_object_t fo = NULL; fo = kalloc(sizeof(struct default_freezer_memory_object)); if (fo) { memory_object_control_t control = NULL; memset(fo, 0, sizeof(*fo)); control = memory_object_control_allocate(object); assert (control != MEMORY_OBJECT_CONTROL_NULL); df_memory_object_init((memory_object_t)fo, control, 0); fo->fo_df_handle = df_handle; default_freezer_handle_reference_locked(fo->fo_df_handle); object->pager = (memory_object_t)fo; object->pager_created = TRUE; object->pager_initialized = TRUE; object->pager_ready = TRUE; object->pager_trusted = TRUE; object->pager_control = control; } else { panic(" Could not allocate freezer object\n"); } } kern_return_t default_freezer_pack( unsigned int *purgeable_count, unsigned int *wired_count, unsigned int *clean_count, unsigned int *dirty_count, unsigned int dirty_budget, boolean_t *shared, vm_object_t src_object, default_freezer_handle_t df_handle) { kern_return_t kr = KERN_SUCCESS; if (df_handle) { default_freezer_handle_lock(df_handle); } kr = vm_object_pack(purgeable_count, wired_count, clean_count, dirty_count, dirty_budget, shared, src_object, df_handle); if (df_handle) { default_freezer_handle_unlock(df_handle); } return kr; } /* * Called with freezer_handle locked. * default_freezer_pack locks the handle, calls * vm_object_pack which, in turn, will call * default_freezer_pack_page(). */ void default_freezer_pack_page( vm_page_t p, default_freezer_handle_t df_handle) { default_freezer_mapping_table_t freeze_table = NULL; memory_object_t memory_object = NULL; vm_object_t compact_object = VM_OBJECT_NULL; assert(df_handle); compact_object = df_handle->dfh_compact_object; assert(compact_object); freeze_table = df_handle->dfh_table; memory_object = p->object->pager; if (memory_object == NULL) { default_freezer_memory_object_create(p->object, df_handle); memory_object = p->object->pager; } else { assert(df_handle == ((default_freezer_memory_object_t)memory_object)->fo_df_handle); } vm_object_lock(compact_object); default_freezer_mapping_store(freeze_table, df_handle->dfh_compact_offset, memory_object, p->offset + p->object->paging_offset); vm_page_rename(p, compact_object, df_handle->dfh_compact_offset, FALSE); vm_object_unlock(compact_object); df_handle->dfh_compact_offset += PAGE_SIZE; } void default_freezer_unpack( default_freezer_handle_t df_handle) { vm_page_t compact_page = VM_PAGE_NULL, src_page = VM_PAGE_NULL; uint32_t index = 0; vm_object_t src_object = VM_OBJECT_NULL; vm_object_t compact_object = VM_OBJECT_NULL; memory_object_t src_mem_object = MEMORY_OBJECT_NULL; memory_object_offset_t src_offset = 0; vm_object_offset_t compact_offset = 0; default_freezer_memory_object_t fo = NULL; default_freezer_mapping_table_t freeze_table = NULL; boolean_t should_unlock_handle = FALSE; assert(df_handle); default_freezer_handle_lock(df_handle); should_unlock_handle = TRUE; freeze_table = df_handle->dfh_table; compact_object = df_handle->dfh_compact_object; assert(compact_object); assert(compact_object->alive); assert(!compact_object->terminating); assert(compact_object->pager_ready); /* Bring the pages back in */ if (vm_object_pagein(compact_object) != KERN_SUCCESS) { if (should_unlock_handle) { default_freezer_handle_unlock(df_handle); } return; } vm_object_lock(compact_object); for (index = 0, compact_offset = 0; ; index++, compact_offset += PAGE_SIZE){ if (index >= freeze_table->index) { default_freezer_mapping_table_t table_next; table_next = freeze_table->next; /* Free the tables as we go along */ default_freezer_mapping_free(&freeze_table, FALSE); if (table_next == NULL){ break; } freeze_table = table_next; index = 0; } /* * Skip slots that represent deallocated memory objects. */ src_mem_object = freeze_table->entry[index].memory_object; if (src_mem_object == MEMORY_OBJECT_NULL) continue; /* * Skip slots that represent faulted pages. */ src_offset = freeze_table->entry[index].offset; if (src_offset != FREEZER_OFFSET_ABSENT) { compact_page = vm_page_lookup(compact_object, compact_offset); assert(compact_page); fo = (default_freezer_memory_object_t)src_mem_object; src_object = memory_object_control_to_vm_object(fo->fo_pager_control); /* Move back over from the freeze object to the original */ vm_object_lock(src_object); src_page = vm_page_lookup(src_object, src_offset - src_object->paging_offset); if (src_page != VM_PAGE_NULL){ /* * We might be racing with a VM fault. * So handle that gracefully. */ assert(src_page->absent == TRUE); VM_PAGE_FREE(src_page); } vm_page_rename(compact_page, src_object, src_offset - src_object->paging_offset, FALSE); vm_object_unlock(src_object); } } vm_object_unlock(compact_object); vm_object_deallocate(compact_object); if (should_unlock_handle) { df_handle->dfh_table = NULL; df_handle->dfh_compact_object = VM_OBJECT_NULL; df_handle->dfh_compact_offset = 0; default_freezer_handle_unlock(df_handle); } } void df_memory_object_reference(__unused memory_object_t mem_obj) { /* No-op */ } void df_memory_object_deallocate(memory_object_t mem_obj) { default_freezer_memory_object_t fo = (default_freezer_memory_object_t)mem_obj; assert(fo); if (fo->fo_df_handle != NULL) { default_freezer_mapping_table_t table = NULL; default_freezer_mapping_table_entry_t entry; boolean_t found = FALSE; uint32_t index = 0; vm_object_t compact_object = VM_OBJECT_NULL; default_freezer_handle_lock(fo->fo_df_handle); compact_object = fo->fo_df_handle->dfh_compact_object; table = fo->fo_df_handle->dfh_table; if (compact_object == VM_OBJECT_NULL || table == NULL) { /*Nothing to do. A thaw must have cleared it all out.*/ } else { vm_object_lock(compact_object); /* Remove from table */ while (1) { if (index >= table->index) { if (table->next) { table = table->next; index = 0; } else { /* End of tables */ break; } } entry = &table->entry[index]; if (mem_obj == entry->memory_object) { /* It matches, so clear the entry */ if (!found) { found = TRUE; } entry->memory_object = MEMORY_OBJECT_NULL; entry->offset = 0; } else if (MEMORY_OBJECT_NULL != entry->memory_object) { /* We have a different valid object; we're done */ if (found) { break; } } index++; } vm_object_unlock(compact_object); } if (default_freezer_handle_deallocate_locked(fo->fo_df_handle)) { default_freezer_handle_unlock(fo->fo_df_handle); } } kfree(fo, sizeof(*fo)); } kern_return_t df_memory_object_init( memory_object_t mem_obj, memory_object_control_t control, __unused memory_object_cluster_size_t pager_page_size) { default_freezer_memory_object_t fo = (default_freezer_memory_object_t)mem_obj; assert(fo); fo->fo_pager_ops = &default_freezer_ops; fo->fo_pager_header.io_bits = IKOT_MEMORY_OBJECT; fo->fo_pager_control = control; return KERN_SUCCESS; } kern_return_t df_memory_object_terminate(memory_object_t mem_obj) { default_freezer_memory_object_t fo = (default_freezer_memory_object_t)mem_obj; assert(fo); memory_object_control_deallocate(fo->fo_pager_control); return KERN_SUCCESS; } kern_return_t df_memory_object_data_request( memory_object_t mem_obj, memory_object_offset_t offset, memory_object_cluster_size_t length, vm_prot_t protection_required, memory_object_fault_info_t fault_info) { vm_object_t src_object = VM_OBJECT_NULL, compact_object = VM_OBJECT_NULL; memory_object_offset_t compact_offset = 0; memory_object_t pager = NULL; kern_return_t kr = KERN_SUCCESS; boolean_t drop_object_ref = FALSE; default_freezer_memory_object_t fo = (default_freezer_memory_object_t)mem_obj; default_freezer_handle_t df_handle = NULL; df_handle = fo->fo_df_handle; if (df_handle == NULL) { kr = KERN_FAILURE; } else { default_freezer_handle_lock(df_handle); src_object = memory_object_control_to_vm_object(fo->fo_pager_control); compact_object = fo->fo_df_handle->dfh_compact_object; if (compact_object == NULL) { kr = KERN_FAILURE; } else { vm_object_lock(compact_object); vm_object_reference_locked(compact_object); drop_object_ref = TRUE; kr = default_freezer_mapping_update(fo->fo_df_handle->dfh_table, mem_obj, offset, &compact_offset, FALSE); vm_object_unlock(compact_object); } default_freezer_handle_unlock(df_handle); } if (length == 0){ /*Caller is just querying to see if we have the page*/ if (drop_object_ref) { vm_object_deallocate(compact_object); } return kr; } if (kr != KERN_SUCCESS){ unsigned int request_flags; upl_t upl; unsigned int page_list_count = 0; request_flags = UPL_NO_SYNC | UPL_RET_ONLY_ABSENT | UPL_SET_LITE; /* * Should we decide to activate USE_PRECIOUS (from default_pager_internal.h) * here, then the request_flags will need to add these to the ones above: * * request_flags |= UPL_PRECIOUS | UPL_CLEAN_IN_PLACE */ request_flags |= UPL_REQUEST_SET_DIRTY; memory_object_super_upl_request(fo->fo_pager_control, (memory_object_offset_t)offset, PAGE_SIZE, PAGE_SIZE, &upl, NULL, &page_list_count, request_flags); upl_range_needed(upl, 0, 1); upl_abort(upl, UPL_ABORT_UNAVAILABLE); upl_deallocate(upl); if (drop_object_ref) { vm_object_deallocate(compact_object); } return KERN_SUCCESS; } assert(compact_object->alive); assert(!compact_object->terminating); assert(compact_object->pager_ready); vm_object_lock(compact_object); vm_object_paging_wait(compact_object, THREAD_UNINT); vm_object_paging_begin(compact_object); compact_object->blocked_access = TRUE; pager = (memory_object_t)compact_object->pager; vm_object_unlock(compact_object); ((vm_object_fault_info_t) fault_info)->io_sync = TRUE; /* * We have a reference on both the default_freezer * memory object handle and the compact object. */ kr = dp_memory_object_data_request(pager, compact_offset, length, protection_required, fault_info); if (kr == KERN_SUCCESS){ vm_page_t compact_page = VM_PAGE_NULL, dst_page = VM_PAGE_NULL; vm_object_lock(compact_object); compact_object->blocked_access = FALSE; vm_object_paging_end(compact_object); vm_object_lock(src_object); if ((compact_page = vm_page_lookup(compact_object, compact_offset)) != VM_PAGE_NULL){ dst_page = vm_page_lookup(src_object, offset - src_object->paging_offset); if (!dst_page->absent){ /* * Someone raced us here and unpacked * the object behind us. * So cleanup before we return. */ VM_PAGE_FREE(compact_page); } else { VM_PAGE_FREE(dst_page); vm_page_rename(compact_page, src_object, offset - src_object->paging_offset, FALSE); if (default_freezer_mapping_update(fo->fo_df_handle->dfh_table, mem_obj, offset, NULL, TRUE) != KERN_SUCCESS) { printf("Page for object: 0x%lx at offset: 0x%lx not found in table\n", (uintptr_t)src_object, (uintptr_t)offset); } PAGE_WAKEUP_DONE(compact_page); } } else { printf("%d: default_freezer: compact_object doesn't have the page for object 0x%lx at offset 0x%lx \n", kr, (uintptr_t)compact_object, (uintptr_t)compact_offset); kr = KERN_SUCCESS; } vm_object_unlock(src_object); vm_object_unlock(compact_object); vm_object_deallocate(compact_object); } else { panic("%d: default_freezer TOC pointed us to default_pager incorrectly\n", kr); } return kr; } kern_return_t df_memory_object_data_return( __unused memory_object_t mem_obj, __unused memory_object_offset_t offset, __unused memory_object_cluster_size_t size, __unused memory_object_offset_t *resid_offset, __unused int *io_error, __unused boolean_t dirty, __unused boolean_t kernel_copy, __unused int upl_flags) { panic(" default_freezer: df_memory_object_data_return should not be called\n"); return KERN_SUCCESS; } kern_return_t df_memory_object_data_initialize( __unused memory_object_t mem_obj, __unused memory_object_offset_t offset, __unused memory_object_cluster_size_t size) { panic(" default_freezer: df_memory_object_data_initialize should not be called\n"); return KERN_SUCCESS; } kern_return_t df_memory_object_data_unlock( __unused memory_object_t mem_obj, __unused memory_object_offset_t offset, __unused memory_object_size_t length, __unused vm_prot_t prot) { panic(" default_freezer: df_memory_object_data_unlock should not be called\n"); return KERN_FAILURE; } kern_return_t df_memory_object_synchronize( __unused memory_object_t mem_obj, __unused memory_object_offset_t offset, __unused memory_object_size_t length, __unused vm_sync_t flags) { panic(" default_freezer: df_memory_object_synchronize should not be called\n"); return KERN_FAILURE; } kern_return_t df_memory_object_map( __unused memory_object_t mem_obj, __unused vm_prot_t prot) { panic(" default_freezer: df_memory_object_map should not be called\n"); return KERN_FAILURE; } kern_return_t df_memory_object_last_unmap(__unused memory_object_t mem_obj) { panic(" default_freezer: df_memory_object_last_unmap should not be called\n"); return KERN_FAILURE; } kern_return_t df_memory_object_data_reclaim( __unused memory_object_t mem_obj, __unused boolean_t reclaim_backing_store) { panic("df_memory_object_data_reclaim\n"); return KERN_SUCCESS; } /* * The freezer handle is used to make sure that * we don't race against the lookup and termination * of the compact object. */ void default_freezer_handle_lock(default_freezer_handle_t df_handle) { lck_rw_lock_exclusive(&df_handle->dfh_lck); } void default_freezer_handle_unlock(default_freezer_handle_t df_handle) { lck_rw_done(&df_handle->dfh_lck); } default_freezer_handle_t default_freezer_handle_allocate(void) { default_freezer_handle_t df_handle = NULL; df_handle = kalloc(sizeof(struct default_freezer_handle)); if (df_handle) { memset(df_handle, 0, sizeof(struct default_freezer_handle)); lck_rw_init(&df_handle->dfh_lck, &default_freezer_handle_lck_grp, NULL); /* No one knows of this handle yet so no need to lock it. */ default_freezer_handle_reference_locked(df_handle); } else { panic("Failed to allocated default_freezer_handle structure\n"); } return df_handle; } kern_return_t default_freezer_handle_init( default_freezer_handle_t df_handle) { kern_return_t kr = KERN_SUCCESS; vm_object_t compact_object = VM_OBJECT_NULL; if (df_handle == NULL || df_handle->dfh_table != NULL) { kr = KERN_FAILURE; } else { /* Create our compact object */ compact_object = vm_object_allocate((vm_map_offset_t)(VM_MAX_ADDRESS) - (vm_map_offset_t)(VM_MIN_ADDRESS)); if (!compact_object) { kr = KERN_FAILURE; } else { df_handle->dfh_compact_object = compact_object; df_handle->dfh_compact_offset = 0; df_handle->dfh_table = default_freezer_mapping_create(df_handle->dfh_compact_object, df_handle->dfh_compact_offset); if (!df_handle->dfh_table) { kr = KERN_FAILURE; } } } return kr; } void default_freezer_handle_reference_locked( default_freezer_handle_t df_handle) { assert(df_handle); df_handle->dfh_ref_count++; } void default_freezer_handle_deallocate( default_freezer_handle_t df_handle) { assert(df_handle); default_freezer_handle_lock(df_handle); if (default_freezer_handle_deallocate_locked(df_handle)) { default_freezer_handle_unlock(df_handle); } } boolean_t default_freezer_handle_deallocate_locked( default_freezer_handle_t df_handle) { boolean_t should_unlock = TRUE; assert(df_handle); df_handle->dfh_ref_count--; if (df_handle->dfh_ref_count == 0) { lck_rw_destroy(&df_handle->dfh_lck, &default_freezer_handle_lck_grp); kfree(df_handle, sizeof(struct default_freezer_handle)); should_unlock = FALSE; } return should_unlock; } void default_freezer_pageout( default_freezer_handle_t df_handle) { assert(df_handle); vm_object_pageout(df_handle->dfh_compact_object); } #endif /* CONFIG_FREEZE */