/* * kcm.c - Kernel Cache Manager implementation. * * Author Alain Greiner (2016,2017,2018,2019) * * Copyright (c) UPMC Sorbonne Universites * * This file is part of ALMOS-MKH. * * ALMOS-MKH 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; version 2.0 of the License. * * ALMOS-MKH 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 ALMOS-MKH; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include ///////////////////////////////////////////////////////////////////////////////////// // Local access functions ///////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// // This static function must be called by a local thread. // It returns a pointer on a block allocated from a non-full kcm_page. // It makes a panic if no block is available in selected page. // It changes the page status as required. ////////////////////////////////////////////////////////////////////////////////////// // @ kcm : pointer on KCM allocator. // @ kcm_page : pointer on a non-full kcm_page. // @ return pointer on allocated block. ///////////////////////////////////////////////////////////////////////////////////// static void * __attribute__((noinline)) kcm_get_block( kcm_t * kcm, kcm_page_t * kcm_page ) { // initialise variables uint32_t size = 1 << kcm->order; uint32_t max = kcm->max_blocks; uint32_t count = kcm_page->count; uint64_t status = kcm_page->status; assert( (count < max) , "kcm_page should not be full" ); uint32_t index = 1; uint64_t mask = (uint64_t)0x2; uint32_t found = 0; // allocate first free block in kcm_page, update status, // and count , compute index of allocated block in kcm_page while( index <= max ) { if( (status & mask) == 0 ) // block non allocated { kcm_page->status = status | mask; kcm_page->count = count + 1; found = 1; break; } index++; mask <<= 1; } // change the page list if almost full if( count == max-1 ) { list_unlink( &kcm_page->list); kcm->active_pages_nr--; list_add_first( &kcm->full_root , &kcm_page->list ); kcm->full_pages_nr ++; } // compute return pointer void * ptr = (void *)((intptr_t)kcm_page + (index * size) ); #if (DEBUG_KCM & 1) thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM < cycle ) printk("\n[%s] thread[%x,%x] allocated block %x in page %x / size %d / count %d / cycle %d\n", __FUNCTION__, this->process->pid, this->trdid, ptr, kcm_page, size, count + 1, cycle ); #endif return ptr; } // end kcm_get_block() ///////////////////////////////////////////////////////////////////////////////////// // This private static function must be called by a local thread. // It releases a previously allocated block to the relevant kcm_page. // It makes a panic if the released block is not allocated in this page. // It changes the kcm_page status as required. ///////////////////////////////////////////////////////////////////////////////////// // @ kcm : pointer on kcm allocator. // @ kcm_page : pointer on kcm_page. // @ block_ptr : pointer on block to be released. ///////////////////////////////////////////////////////////////////////////////////// static void __attribute__((noinline)) kcm_put_block ( kcm_t * kcm, kcm_page_t * kcm_page, void * block_ptr ) { // initialise variables uint32_t max = kcm->max_blocks; uint32_t size = 1 << kcm->order; uint32_t count = kcm_page->count; uint64_t status = kcm_page->status; // compute block index from block pointer uint32_t index = ((intptr_t)block_ptr - (intptr_t)kcm_page) / size; // compute mask in bit vector uint64_t mask = ((uint64_t)0x1) << index; assert( (status & mask) , "released block not allocated : status (%x,%x) / mask(%x,%x)", GET_CXY(status), GET_PTR(status), GET_CXY(mask ), GET_PTR(mask ) ); // update status & count in kcm_page kcm_page->status = status & ~mask; kcm_page->count = count - 1; // change the page mode if page was full if( count == max ) { list_unlink( &kcm_page->list ); kcm->full_pages_nr --; list_add_last( &kcm->active_root, &kcm_page->list ); kcm->active_pages_nr ++; } #if (DEBUG_KCM & 1) thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM < cycle ) printk("\n[%s] thread[%x,%x] released block %x in page %x / size %d / count %d / cycle %d\n", __FUNCTION__, this->process->pid, this->trdid, block_ptr, kcm_page, size, count - 1, cycle ); #endif } // kcm_put_block() ///////////////////////////////////////////////////////////////////////////////////// // This private static function must be called by a local thread. // It returns one non-full kcm_page with te following policy : // - if the "active_list" is non empty, it returns the first "active" page, // without modifying the KCM state. // - if the "active_list" is empty, it allocates a new page fromm PPM, inserts // this page in the active_list, and returns it. ///////////////////////////////////////////////////////////////////////////////////// // @ kcm : local pointer on local KCM allocator. // @ return pointer on a non-full kcm page if success / returns NULL if no memory. ///////////////////////////////////////////////////////////////////////////////////// static kcm_page_t * __attribute__((noinline)) kcm_get_page( kcm_t * kcm ) { kcm_page_t * kcm_page; uint32_t active_pages_nr = kcm->active_pages_nr; if( active_pages_nr > 0 ) // return first active page { kcm_page = LIST_FIRST( &kcm->active_root , kcm_page_t , list ); } else // allocate a new page from PPM { // get one 4 Kbytes page from local PPM page_t * page = ppm_alloc_pages( 0 ); if( page == NULL ) { printk("\n[ERROR] in %s : failed to allocate page in cluster %x\n", __FUNCTION__ , local_cxy ); return NULL; } // get page base address xptr_t base_xp = ppm_page2base( XPTR( local_cxy , page ) ); // get local pointer on kcm_page kcm_page = GET_PTR( base_xp ); // initialize kcm_page descriptor kcm_page->status = 0; kcm_page->count = 0; kcm_page->kcm = kcm; kcm_page->page = page; // introduce new page in KCM active_list list_add_first( &kcm->active_root , &kcm_page->list ); kcm->active_pages_nr ++; } return kcm_page; } // end kcm_get_page() ////////////////////////////// void kcm_init( kcm_t * kcm, uint32_t order) { assert( ((order > 5) && (order < 12)) , "order must be in [6,11]" ); // initialize lock remote_busylock_init( XPTR( local_cxy , &kcm->lock ) , LOCK_KCM_STATE ); // initialize KCM page lists kcm->full_pages_nr = 0; kcm->active_pages_nr = 0; list_root_init( &kcm->full_root ); list_root_init( &kcm->active_root ); // initialize order and max_blocks kcm->order = order; kcm->max_blocks = ( CONFIG_PPM_PAGE_SIZE >> order ) - 1; #if DEBUG_KCM thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM < cycle ) printk("\n[%s] thread[%x,%x] initialised KCM / order %d / max_blocks %d\n", __FUNCTION__, this->process->pid, this->trdid, order, kcm->max_blocks ); #endif } // end kcm_init() /////////////////////////////// void kcm_destroy( kcm_t * kcm ) { kcm_page_t * kcm_page; // build extended pointer on KCM lock xptr_t lock_xp = XPTR( local_cxy , &kcm->lock ); // get KCM lock remote_busylock_acquire( lock_xp ); // release all full pages while( list_is_empty( &kcm->full_root ) == false ) { kcm_page = LIST_FIRST( &kcm->full_root , kcm_page_t , list ); list_unlink( &kcm_page->list ); ppm_free_pages( kcm_page->page ); } // release all empty pages while( list_is_empty( &kcm->active_root ) == false ) { kcm_page = LIST_FIRST( &kcm->active_root , kcm_page_t , list ); list_unlink( &kcm_page->list ); ppm_free_pages( kcm_page->page ); } // release KCM lock remote_busylock_release( lock_xp ); } ////////////////////////////////// void * kcm_alloc( uint32_t order ) { kcm_t * kcm_ptr; kcm_page_t * kcm_page; void * block_ptr; // min block size is 64 bytes if( order < 6 ) order = 6; assert( (order < 12) , "order = %d / must be less than 12" , order ); // get local pointer on relevant KCM allocator kcm_ptr = &LOCAL_CLUSTER->kcm[order - 6]; // build extended pointer on local KCM lock xptr_t lock_xp = XPTR( local_cxy , &kcm_ptr->lock ); // get KCM lock remote_busylock_acquire( lock_xp ); // get a non-full kcm_page kcm_page = kcm_get_page( kcm_ptr ); if( kcm_page == NULL ) { remote_busylock_release( lock_xp ); return NULL; } // get a block from selected active page block_ptr = kcm_get_block( kcm_ptr , kcm_page ); // release lock remote_busylock_release( lock_xp ); #if DEBUG_KCM thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM < cycle ) printk("\n[%s] thread[%x,%x] allocated block %x / order %d / kcm %x / status[%x,%x] / count %d\n", __FUNCTION__, this->process->pid, this->trdid, block_ptr, order, kcm_ptr, GET_CXY(kcm_page->status), GET_PTR(kcm_page->status), kcm_page->count ); #endif return block_ptr; } // end kcm_alloc() ///////////////////////////////// void kcm_free( void * block_ptr ) { kcm_t * kcm_ptr; kcm_page_t * kcm_page; // check argument assert( (block_ptr != NULL) , "block pointer cannot be NULL" ); // get local pointer on KCM page kcm_page = (kcm_page_t *)((intptr_t)block_ptr & ~CONFIG_PPM_PAGE_MASK); // get local pointer on KCM descriptor kcm_ptr = kcm_page->kcm; #if DEBUG_KCM thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM < cycle ) printk("\n[%s] thread[%x,%x] release block %x / order %d / kcm %x / status [%x,%x] / count %d\n", __FUNCTION__, this->process->pid, this->trdid, block_ptr, kcm_ptr->order, kcm_ptr, GET_CXY(kcm_page->status), GET_PTR(kcm_page->status), kcm_page->count ); #endif // build extended pointer on local KCM lock xptr_t lock_xp = XPTR( local_cxy , &kcm_ptr->lock ); // get lock remote_busylock_acquire( lock_xp ); // release block kcm_put_block( kcm_ptr , kcm_page , block_ptr ); // release lock remote_busylock_release( lock_xp ); } ///////////////////////////////////////////////////////////////////////////////////// // Remote access functions ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// // This static function can be called by any thread running in any cluster. // It returns a local pointer on a block allocated from an non-full kcm_page. // It makes a panic if no block available in selected page. // It changes the page status as required. ///////////////////////////////////////////////////////////////////////////////////// // @ kcm_cxy : remote KCM cluster identidfier. // @ kcm_ptr : local pointer on remote KCM allocator. // @ kcm_page : pointer on active kcm page to use. // @ return a local pointer on the allocated block. ///////////////////////////////////////////////////////////////////////////////////// static void * __attribute__((noinline)) kcm_remote_get_block( cxy_t kcm_cxy, kcm_t * kcm_ptr, kcm_page_t * kcm_page ) { uint32_t order = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->order ) ); uint32_t max = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->max_blocks ) ); uint32_t count = hal_remote_l32( XPTR( kcm_cxy , &kcm_page->count ) ); uint64_t status = hal_remote_l64( XPTR( kcm_cxy , &kcm_page->status ) ); uint32_t size = 1 << order; assert( (count < max) , "kcm_page should not be full" ); uint32_t index = 1; uint64_t mask = (uint64_t)0x2; uint32_t found = 0; // allocate first free block in kcm_page, update status, // and count , compute index of allocated block in kcm_page while( index <= max ) { if( (status & mask) == 0 ) // block non allocated { hal_remote_s64( XPTR( kcm_cxy , &kcm_page->status ) , status | mask ); hal_remote_s64( XPTR( kcm_cxy , &kcm_page->count ) , count + 1 ); found = 1; break; } index++; mask <<= 1; } // change the page list if almost full if( count == max-1 ) { list_remote_unlink( kcm_cxy , &kcm_page->list ); hal_remote_atomic_add( XPTR( kcm_cxy , &kcm_ptr->active_pages_nr ) , -1 ); list_remote_add_first( kcm_cxy , &kcm_ptr->full_root , &kcm_page->list ); hal_remote_atomic_add( XPTR( kcm_cxy , &kcm_ptr->full_pages_nr ) , 1 ); } // compute return pointer void * ptr = (void *)((intptr_t)kcm_page + (index * size) ); #if DEBUG_KCM_REMOTE thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM_REMOTE < cycle ) printk("\n[%s] thread[%x,%x] get block %x in page %x / cluster %x / size %x / count %d\n", __FUNCTION__, this->process->pid, this->trdid, ptr, kcm_page, kcm_cxy, size, count + 1 ); #endif return ptr; } // end kcm_remote_get_block() ///////////////////////////////////////////////////////////////////////////////////// // This private static function can be called by any thread running in any cluster. // It releases a previously allocated block to the relevant kcm_page. // It changes the kcm_page status as required. ///////////////////////////////////////////////////////////////////////////////////// // @ kcm_cxy : remote KCM cluster identifier // @ kcm_ptr : local pointer on remote KCM. // @ kcm_page : local pointer on kcm_page. // @ block_ptr : pointer on block to be released. ///////////////////////////////////////////////////////////////////////////////////// static void __attribute__((noinline)) kcm_remote_put_block ( cxy_t kcm_cxy, kcm_t * kcm_ptr, kcm_page_t * kcm_page, void * block_ptr ) { uint32_t max = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->max_blocks ) ); uint32_t order = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->order ) ); uint32_t count = hal_remote_l32( XPTR( kcm_cxy , &kcm_page->count ) ); uint64_t status = hal_remote_l64( XPTR( kcm_cxy , &kcm_page->status ) ); uint32_t size = 1 << order; // compute block index from block pointer uint32_t index = ((intptr_t)block_ptr - (intptr_t)kcm_page) / size; // compute mask in bit vector uint64_t mask = 1 << index; assert( (status & mask) , "released page not allocated" ); // update status & count in kcm_page hal_remote_s64( XPTR( kcm_cxy , &kcm_page->status ) , status & ~mask ); hal_remote_s32( XPTR( kcm_cxy , &kcm_page->count ) , count - 1 ); // change the page list if page was full if( count == max ) { list_remote_unlink( kcm_cxy , &kcm_page->list ); hal_remote_atomic_add( XPTR( kcm_cxy , &kcm_ptr->full_pages_nr ) , -1 ); list_remote_add_last( kcm_cxy , &kcm_ptr->active_root, &kcm_page->list ); hal_remote_atomic_add( XPTR( kcm_cxy , &kcm_ptr->active_pages_nr ) , 1 ); } #if (DEBUG_KCM_REMOTE & 1) thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM_REMOTE < cycle ) printk("\n[%s] thread[%x,%x] released block %x in page %x / cluster %x / size %x / count %d\n", __FUNCTION__, this->process->pid, this->trdid, block_ptr, kcm_page, size, count - 1 ) #endif } // end kcm_remote_put_block() ///////////////////////////////////////////////////////////////////////////////////// // This private static function can be called by any thread running in any cluster. // It gets one non-full KCM page from the remote KCM. // It allocates a page from remote PPM to populate the freelist, and initialises // the kcm_page descriptor when required. ///////////////////////////////////////////////////////////////////////////////////// static kcm_page_t * __attribute__((noinline)) kcm_remote_get_page( cxy_t kcm_cxy, kcm_t * kcm_ptr ) { kcm_page_t * kcm_page; // local pointer on remote KCM page uint32_t active_pages_nr = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->active_pages_nr ) ); if( active_pages_nr > 0 ) // return first active page { kcm_page = LIST_REMOTE_FIRST( kcm_cxy , &kcm_ptr->active_root , kcm_page_t , list ); } else // allocate a new page from PPM { // get one 4 Kbytes page from remote PPM page_t * page = ppm_remote_alloc_pages( kcm_cxy , 0 ); if( page == NULL ) { printk("\n[ERROR] in %s : failed to allocate page in cluster %x\n", __FUNCTION__ , kcm_cxy ); return NULL; } // get remote page base address xptr_t base_xp = ppm_page2base( XPTR( kcm_cxy , page ) ); // get local pointer on kcm_page kcm_page = GET_PTR( base_xp ); // initialize kcm_page descriptor hal_remote_s32( XPTR( kcm_cxy , &kcm_page->count ) , 0 ); hal_remote_s64( XPTR( kcm_cxy , &kcm_page->status ) , 0 ); hal_remote_spt( XPTR( kcm_cxy , &kcm_page->kcm ) , kcm_ptr ); hal_remote_spt( XPTR( kcm_cxy , &kcm_page->page ) , page ); // introduce new page in remote KCM active_list list_remote_add_first( kcm_cxy , &kcm_ptr->active_root , &kcm_page->list ); hal_remote_atomic_add( XPTR( kcm_cxy , &kcm_ptr->active_pages_nr ) , 1 ); } return kcm_page; } // end kcm_remote_get_page() ///////////////////////////////////////// void * kcm_remote_alloc( cxy_t kcm_cxy, uint32_t order ) { kcm_t * kcm_ptr; kcm_page_t * kcm_page; void * block_ptr; if( order < 6 ) order = 6; assert( (order < 12) , "order = %d / must be less than 12" , order ); // get local pointer on relevant KCM allocator kcm_ptr = &LOCAL_CLUSTER->kcm[order - 6]; // build extended pointer on remote KCM lock xptr_t lock_xp = XPTR( kcm_cxy , &kcm_ptr->lock ); // get lock remote_busylock_acquire( lock_xp ); // get a non-full kcm_page kcm_page = kcm_remote_get_page( kcm_cxy , kcm_ptr ); if( kcm_page == NULL ) { remote_busylock_release( lock_xp ); return NULL; } // get a block from selected active page block_ptr = kcm_remote_get_block( kcm_cxy , kcm_ptr , kcm_page ); // release lock remote_busylock_release( lock_xp ); #if DEBUG_KCM_REMOTE thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_KCM_REMOTE < cycle ) printk("\n[%s] thread[%x,%x] allocated block %x / order %d / kcm[%x,%x]\n", __FUNCTION__, this->process->pid, this->trdid, block_ptr, order, kcm_cxy, kcm_ptr ); #endif return block_ptr; } // end kcm_remote_alloc() ///////////////////////////////////// void kcm_remote_free( cxy_t kcm_cxy, void * block_ptr ) { kcm_t * kcm_ptr; kcm_page_t * kcm_page; // check argument assert( (block_ptr != NULL) , "block pointer cannot be NULL" ); // get local pointer on remote KCM page kcm_page = (kcm_page_t *)((intptr_t)block_ptr & ~CONFIG_PPM_PAGE_MASK); // get local pointer on remote KCM kcm_ptr = hal_remote_lpt( XPTR( kcm_cxy , &kcm_page->kcm ) ); // build extended pointer on remote KCM lock xptr_t lock_xp = XPTR( kcm_cxy , &kcm_ptr->lock ); // get lock remote_busylock_acquire( lock_xp ); // release block kcm_remote_put_block( kcm_cxy , kcm_ptr , kcm_page , block_ptr ); // release lock remote_busylock_release( lock_xp ); #if DEBUG_KCM_REMOTE thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); uint32_t order = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->order ) ); if( DEBUG_KCM_REMOTE < cycle ) printk("\n[%s] thread[%x,%x] released block %x / order %d / kcm[%x,%x]\n", __FUNCTION__, this->process->pid, this->trdid, block_ptr, order, kcm_cxy, kcm_ptr ); #endif } // end kcm_remote_free ///////////////////////////////////////// void kcm_remote_display( cxy_t kcm_cxy, kcm_t * kcm_ptr ) { uint32_t order = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->order) ); uint32_t full_pages_nr = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->full_pages_nr ) ); uint32_t active_pages_nr = hal_remote_l32( XPTR( kcm_cxy , &kcm_ptr->active_pages_nr ) ); printk("*** KCM / cxy %x / order %d / full_pages %d / empty_pages %d / active_pages %d\n", kcm_cxy, order, full_pages_nr, active_pages_nr ); }