2014-10-08 19:55:02 +11:00
/*
* Copyright 2014 IBM Corp .
*
* 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 .
*/
# include <linux/workqueue.h>
2017-02-02 19:15:33 +01:00
# include <linux/sched/signal.h>
2017-02-08 18:51:29 +01:00
# include <linux/sched/mm.h>
2014-10-08 19:55:02 +11:00
# include <linux/pid.h>
# include <linux/mm.h>
# include <linux/moduleparam.h>
# undef MODULE_PARAM_PREFIX
# define MODULE_PARAM_PREFIX "cxl" "."
# include <asm/current.h>
# include <asm/copro.h>
# include <asm/mmu.h>
# include "cxl.h"
2015-01-09 20:34:36 +11:00
# include "trace.h"
2014-10-08 19:55:02 +11:00
2014-10-28 14:25:30 +11:00
static bool sste_matches ( struct cxl_sste * sste , struct copro_slb * slb )
{
return ( ( sste - > vsid_data = = cpu_to_be64 ( slb - > vsid ) ) & &
( sste - > esid_data = = cpu_to_be64 ( slb - > esid ) ) ) ;
}
/*
* This finds a free SSTE for the given SLB , or returns NULL if it ' s already in
* the segment table .
*/
2014-10-28 14:25:28 +11:00
static struct cxl_sste * find_free_sste ( struct cxl_context * ctx ,
struct copro_slb * slb )
2014-10-08 19:55:02 +11:00
{
2014-10-28 14:25:30 +11:00
struct cxl_sste * primary , * sste , * ret = NULL ;
2014-10-28 14:25:28 +11:00
unsigned int mask = ( ctx - > sst_size > > 7 ) - 1 ; /* SSTP0[SegTableSize] */
2014-10-28 14:25:27 +11:00
unsigned int entry ;
2014-10-28 14:25:28 +11:00
unsigned int hash ;
if ( slb - > vsid & SLB_VSID_B_1T )
hash = ( slb - > esid > > SID_SHIFT_1T ) & mask ;
else /* 256M */
hash = ( slb - > esid > > SID_SHIFT ) & mask ;
2014-10-08 19:55:02 +11:00
2014-10-28 14:25:28 +11:00
primary = ctx - > sstp + ( hash < < 3 ) ;
for ( entry = 0 , sste = primary ; entry < 8 ; entry + + , sste + + ) {
2014-10-28 14:25:30 +11:00
if ( ! ret & & ! ( be64_to_cpu ( sste - > esid_data ) & SLB_ESID_V ) )
ret = sste ;
if ( sste_matches ( sste , slb ) )
return NULL ;
2014-10-08 19:55:02 +11:00
}
2014-10-28 14:25:30 +11:00
if ( ret )
return ret ;
2014-10-28 14:25:28 +11:00
2014-10-08 19:55:02 +11:00
/* Nothing free, select an entry to cast out */
2014-10-28 14:25:30 +11:00
ret = primary + ctx - > sst_lru ;
2014-10-28 14:25:28 +11:00
ctx - > sst_lru = ( ctx - > sst_lru + 1 ) & 0x7 ;
2014-10-08 19:55:02 +11:00
2014-10-28 14:25:30 +11:00
return ret ;
2014-10-08 19:55:02 +11:00
}
static void cxl_load_segment ( struct cxl_context * ctx , struct copro_slb * slb )
{
/* mask is the group index, we search primary and secondary here. */
struct cxl_sste * sste ;
unsigned long flags ;
spin_lock_irqsave ( & ctx - > sste_lock , flags ) ;
2014-10-28 14:25:28 +11:00
sste = find_free_sste ( ctx , slb ) ;
2014-10-28 14:25:30 +11:00
if ( ! sste )
goto out_unlock ;
2014-10-08 19:55:02 +11:00
pr_devel ( " CXL Populating SST[%li]: %#llx %#llx \n " ,
sste - ctx - > sstp , slb - > vsid , slb - > esid ) ;
2015-01-09 20:34:36 +11:00
trace_cxl_ste_write ( ctx , sste - ctx - > sstp , slb - > esid , slb - > vsid ) ;
2014-10-08 19:55:02 +11:00
sste - > vsid_data = cpu_to_be64 ( slb - > vsid ) ;
sste - > esid_data = cpu_to_be64 ( slb - > esid ) ;
2014-10-28 14:25:30 +11:00
out_unlock :
2014-10-08 19:55:02 +11:00
spin_unlock_irqrestore ( & ctx - > sste_lock , flags ) ;
}
static int cxl_fault_segment ( struct cxl_context * ctx , struct mm_struct * mm ,
u64 ea )
{
struct copro_slb slb = { 0 , 0 } ;
int rc ;
if ( ! ( rc = copro_calculate_slb ( mm , ea , & slb ) ) ) {
cxl_load_segment ( ctx , & slb ) ;
}
return rc ;
}
static void cxl_ack_ae ( struct cxl_context * ctx )
{
unsigned long flags ;
2016-03-04 12:26:28 +01:00
cxl_ops - > ack_irq ( ctx , CXL_PSL_TFC_An_AE , 0 ) ;
2014-10-08 19:55:02 +11:00
spin_lock_irqsave ( & ctx - > lock , flags ) ;
ctx - > pending_fault = true ;
ctx - > fault_addr = ctx - > dar ;
ctx - > fault_dsisr = ctx - > dsisr ;
spin_unlock_irqrestore ( & ctx - > lock , flags ) ;
wake_up_all ( & ctx - > wq ) ;
}
static int cxl_handle_segment_miss ( struct cxl_context * ctx ,
struct mm_struct * mm , u64 ea )
{
int rc ;
pr_devel ( " CXL interrupt: Segment fault pe: %i ea: %#llx \n " , ctx - > pe , ea ) ;
2015-01-09 20:34:36 +11:00
trace_cxl_ste_miss ( ctx , ea ) ;
2014-10-08 19:55:02 +11:00
if ( ( rc = cxl_fault_segment ( ctx , mm , ea ) ) )
cxl_ack_ae ( ctx ) ;
else {
mb ( ) ; /* Order seg table write to TFC MMIO write */
2016-03-04 12:26:28 +01:00
cxl_ops - > ack_irq ( ctx , CXL_PSL_TFC_An_R , 0 ) ;
2014-10-08 19:55:02 +11:00
}
return IRQ_HANDLED ;
}
static void cxl_handle_page_fault ( struct cxl_context * ctx ,
struct mm_struct * mm , u64 dsisr , u64 dar )
{
unsigned flt = 0 ;
int result ;
2014-12-04 11:00:14 +05:30
unsigned long access , flags , inv_flags = 0 ;
2014-10-08 19:55:02 +11:00
2015-01-09 20:34:36 +11:00
trace_cxl_pte_miss ( ctx , dsisr , dar ) ;
2014-10-08 19:55:02 +11:00
if ( ( result = copro_handle_mm_fault ( mm , dar , dsisr , & flt ) ) ) {
pr_devel ( " copro_handle_mm_fault failed: %#x \n " , result ) ;
return cxl_ack_ae ( ctx ) ;
}
/*
* update_mmu_cache ( ) will not have loaded the hash since current - > trap
* is not a 0x400 or 0x300 , so just call hash_page_mm ( ) here .
*/
2016-04-29 23:25:30 +10:00
access = _PAGE_PRESENT | _PAGE_READ ;
2014-10-08 19:55:02 +11:00
if ( dsisr & CXL_PSL_DSISR_An_S )
2016-04-29 23:25:30 +10:00
access | = _PAGE_WRITE ;
2016-04-29 23:25:34 +10:00
access | = _PAGE_PRIVILEGED ;
2016-04-20 03:59:47 -04:00
if ( ( ! ctx - > kernel ) | | ( REGION_ID ( dar ) = = USER_REGION_ID ) )
2016-04-29 23:25:34 +10:00
access & = ~ _PAGE_PRIVILEGED ;
2014-12-04 11:00:14 +05:30
if ( dsisr & DSISR_NOHPTE )
inv_flags | = HPTE_NOHPTE_UPDATE ;
2014-10-08 19:55:02 +11:00
local_irq_save ( flags ) ;
2014-12-04 11:00:14 +05:30
hash_page_mm ( mm , dar , access , 0x300 , inv_flags ) ;
2014-10-08 19:55:02 +11:00
local_irq_restore ( flags ) ;
pr_devel ( " Page fault successfully handled for pe: %i! \n " , ctx - > pe ) ;
2016-03-04 12:26:28 +01:00
cxl_ops - > ack_irq ( ctx , CXL_PSL_TFC_An_R , 0 ) ;
2014-10-08 19:55:02 +11:00
}
2015-11-24 16:26:18 +05:30
/*
* Returns the mm_struct corresponding to the context ctx via ctx - > pid
* In case the task has exited we use the task group leader accessible
* via ctx - > glpid to find the next task in the thread group that has a
* valid mm_struct associated with it . If a task with valid mm_struct
* is found the ctx - > pid is updated to use the task struct for subsequent
* translations . In case no valid mm_struct is found in the task group to
* service the fault a NULL is returned .
*/
static struct mm_struct * get_mem_context ( struct cxl_context * ctx )
{
struct task_struct * task = NULL ;
struct mm_struct * mm = NULL ;
struct pid * old_pid = ctx - > pid ;
if ( old_pid = = NULL ) {
pr_warn ( " %s: Invalid context for pe=%d \n " ,
__func__ , ctx - > pe ) ;
return NULL ;
}
task = get_pid_task ( old_pid , PIDTYPE_PID ) ;
/*
* pid_alive may look racy but this saves us from costly
* get_task_mm when the task is a zombie . In worst case
* we may think a task is alive , which is about to die
* but get_task_mm will return NULL .
*/
if ( task ! = NULL & & pid_alive ( task ) )
mm = get_task_mm ( task ) ;
/* release the task struct that was taken earlier */
if ( task )
put_task_struct ( task ) ;
else
pr_devel ( " %s: Context owning pid=%i for pe=%i dead \n " ,
__func__ , pid_nr ( old_pid ) , ctx - > pe ) ;
/*
* If we couldn ' t find the mm context then use the group
* leader to iterate over the task group and find a task
* that gives us mm_struct .
*/
if ( unlikely ( mm = = NULL & & ctx - > glpid ! = NULL ) ) {
rcu_read_lock ( ) ;
task = pid_task ( ctx - > glpid , PIDTYPE_PID ) ;
if ( task )
do {
mm = get_task_mm ( task ) ;
if ( mm ) {
ctx - > pid = get_task_pid ( task ,
PIDTYPE_PID ) ;
break ;
}
task = next_thread ( task ) ;
} while ( task & & ! thread_group_leader ( task ) ) ;
rcu_read_unlock ( ) ;
/* check if we switched pid */
if ( ctx - > pid ! = old_pid ) {
if ( mm )
pr_devel ( " %s:pe=%i switch pid %i->%i \n " ,
__func__ , ctx - > pe , pid_nr ( old_pid ) ,
pid_nr ( ctx - > pid ) ) ;
else
pr_devel ( " %s:Cannot find mm for pid=%i \n " ,
__func__ , pid_nr ( old_pid ) ) ;
/* drop the reference to older pid */
put_pid ( old_pid ) ;
}
}
return mm ;
}
2014-10-08 19:55:02 +11:00
void cxl_handle_fault ( struct work_struct * fault_work )
{
struct cxl_context * ctx =
container_of ( fault_work , struct cxl_context , fault_work ) ;
u64 dsisr = ctx - > dsisr ;
u64 dar = ctx - > dar ;
2015-05-27 16:07:11 +10:00
struct mm_struct * mm = NULL ;
2014-10-08 19:55:02 +11:00
2016-03-04 12:26:30 +01:00
if ( cpu_has_feature ( CPU_FTR_HVMODE ) ) {
if ( cxl_p2n_read ( ctx - > afu , CXL_PSL_DSISR_An ) ! = dsisr | |
cxl_p2n_read ( ctx - > afu , CXL_PSL_DAR_An ) ! = dar | |
cxl_p2n_read ( ctx - > afu , CXL_PSL_PEHandle_An ) ! = ctx - > pe ) {
/* Most likely explanation is harmless - a dedicated
* process has detached and these were cleared by the
* PSL purge , but warn about it just in case
*/
dev_notice ( & ctx - > afu - > dev , " cxl_handle_fault: Translation fault regs changed \n " ) ;
return ;
}
2014-10-08 19:55:02 +11:00
}
2014-12-08 19:17:58 +11:00
/* Early return if the context is being / has been detached */
if ( ctx - > status = = CLOSED ) {
cxl_ack_ae ( ctx ) ;
return ;
}
2014-10-08 19:55:02 +11:00
pr_devel ( " CXL BOTTOM HALF handling fault for afu pe: %i. "
" DSISR: %#llx DAR: %#llx \n " , ctx - > pe , dsisr , dar ) ;
2015-05-27 16:07:11 +10:00
if ( ! ctx - > kernel ) {
2015-11-24 16:26:18 +05:30
mm = get_mem_context ( ctx ) ;
/* indicates all the thread in task group have exited */
if ( mm = = NULL ) {
pr_devel ( " %s: unable to get mm for pe=%d pid=%i \n " ,
__func__ , ctx - > pe , pid_nr ( ctx - > pid ) ) ;
2015-05-27 16:07:11 +10:00
cxl_ack_ae ( ctx ) ;
return ;
2015-11-24 16:26:18 +05:30
} else {
pr_devel ( " Handling page fault for pe=%d pid=%i \n " ,
ctx - > pe , pid_nr ( ctx - > pid ) ) ;
2015-05-27 16:07:11 +10:00
}
2014-10-08 19:55:02 +11:00
}
if ( dsisr & CXL_PSL_DSISR_An_DS )
cxl_handle_segment_miss ( ctx , mm , dar ) ;
else if ( dsisr & CXL_PSL_DSISR_An_DM )
cxl_handle_page_fault ( ctx , mm , dsisr , dar ) ;
else
WARN ( 1 , " cxl_handle_fault has nothing to handle \n " ) ;
2015-05-27 16:07:11 +10:00
if ( mm )
mmput ( mm ) ;
2014-10-08 19:55:02 +11:00
}
static void cxl_prefault_one ( struct cxl_context * ctx , u64 ea )
{
struct mm_struct * mm ;
2015-11-24 16:26:18 +05:30
mm = get_mem_context ( ctx ) ;
if ( mm = = NULL ) {
2014-10-08 19:55:02 +11:00
pr_devel ( " cxl_prefault_one unable to get mm %i \n " ,
pid_nr ( ctx - > pid ) ) ;
return ;
}
2015-11-24 16:26:18 +05:30
cxl_fault_segment ( ctx , mm , ea ) ;
2014-10-08 19:55:02 +11:00
mmput ( mm ) ;
}
static u64 next_segment ( u64 ea , u64 vsid )
{
if ( vsid & SLB_VSID_B_1T )
ea | = ( 1ULL < < 40 ) - 1 ;
else
ea | = ( 1ULL < < 28 ) - 1 ;
return ea + 1 ;
}
static void cxl_prefault_vma ( struct cxl_context * ctx )
{
u64 ea , last_esid = 0 ;
struct copro_slb slb ;
struct vm_area_struct * vma ;
int rc ;
struct mm_struct * mm ;
2015-11-24 16:26:18 +05:30
mm = get_mem_context ( ctx ) ;
if ( mm = = NULL ) {
2014-10-08 19:55:02 +11:00
pr_devel ( " cxl_prefault_vm unable to get mm %i \n " ,
pid_nr ( ctx - > pid ) ) ;
2015-11-24 16:26:18 +05:30
return ;
2014-10-08 19:55:02 +11:00
}
down_read ( & mm - > mmap_sem ) ;
for ( vma = mm - > mmap ; vma ; vma = vma - > vm_next ) {
for ( ea = vma - > vm_start ; ea < vma - > vm_end ;
ea = next_segment ( ea , slb . vsid ) ) {
rc = copro_calculate_slb ( mm , ea , & slb ) ;
if ( rc )
continue ;
if ( last_esid = = slb . esid )
continue ;
cxl_load_segment ( ctx , & slb ) ;
last_esid = slb . esid ;
}
}
up_read ( & mm - > mmap_sem ) ;
mmput ( mm ) ;
}
void cxl_prefault ( struct cxl_context * ctx , u64 wed )
{
switch ( ctx - > afu - > prefault_mode ) {
case CXL_PREFAULT_WED :
cxl_prefault_one ( ctx , wed ) ;
break ;
case CXL_PREFAULT_ALL :
cxl_prefault_vma ( ctx ) ;
break ;
default :
break ;
}
}