// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2015 IBM Corp. */ #include #include #include #include #include "hcalls.h" #include "trace.h" #define CXL_HCALL_TIMEOUT 60000 #define CXL_HCALL_TIMEOUT_DOWNLOAD 120000 #define H_ATTACH_CA_PROCESS 0x344 #define H_CONTROL_CA_FUNCTION 0x348 #define H_DETACH_CA_PROCESS 0x34C #define H_COLLECT_CA_INT_INFO 0x350 #define H_CONTROL_CA_FAULTS 0x354 #define H_DOWNLOAD_CA_FUNCTION 0x35C #define H_DOWNLOAD_CA_FACILITY 0x364 #define H_CONTROL_CA_FACILITY 0x368 #define H_CONTROL_CA_FUNCTION_RESET 1 /* perform a reset */ #define H_CONTROL_CA_FUNCTION_SUSPEND_PROCESS 2 /* suspend a process from being executed */ #define H_CONTROL_CA_FUNCTION_RESUME_PROCESS 3 /* resume a process to be executed */ #define H_CONTROL_CA_FUNCTION_READ_ERR_STATE 4 /* read the error state */ #define H_CONTROL_CA_FUNCTION_GET_AFU_ERR 5 /* collect the AFU error buffer */ #define H_CONTROL_CA_FUNCTION_GET_CONFIG 6 /* collect configuration record */ #define H_CONTROL_CA_FUNCTION_GET_DOWNLOAD_STATE 7 /* query to return download status */ #define H_CONTROL_CA_FUNCTION_TERMINATE_PROCESS 8 /* terminate the process before completion */ #define H_CONTROL_CA_FUNCTION_COLLECT_VPD 9 /* collect VPD */ #define H_CONTROL_CA_FUNCTION_GET_FUNCTION_ERR_INT 11 /* read the function-wide error data based on an interrupt */ #define H_CONTROL_CA_FUNCTION_ACK_FUNCTION_ERR_INT 12 /* acknowledge function-wide error data based on an interrupt */ #define H_CONTROL_CA_FUNCTION_GET_ERROR_LOG 13 /* retrieve the Platform Log ID (PLID) of an error log */ #define H_CONTROL_CA_FAULTS_RESPOND_PSL 1 #define H_CONTROL_CA_FAULTS_RESPOND_AFU 2 #define H_CONTROL_CA_FACILITY_RESET 1 /* perform a reset */ #define H_CONTROL_CA_FACILITY_COLLECT_VPD 2 /* collect VPD */ #define H_DOWNLOAD_CA_FACILITY_DOWNLOAD 1 /* download adapter image */ #define H_DOWNLOAD_CA_FACILITY_VALIDATE 2 /* validate adapter image */ #define _CXL_LOOP_HCALL(call, rc, retbuf, fn, ...) \ { \ unsigned int delay, total_delay = 0; \ u64 token = 0; \ \ memset(retbuf, 0, sizeof(retbuf)); \ while (1) { \ rc = call(fn, retbuf, __VA_ARGS__, token); \ token = retbuf[0]; \ if (rc != H_BUSY && !H_IS_LONG_BUSY(rc)) \ break; \ \ if (rc == H_BUSY) \ delay = 10; \ else \ delay = get_longbusy_msecs(rc); \ \ total_delay += delay; \ if (total_delay > CXL_HCALL_TIMEOUT) { \ WARN(1, "Warning: Giving up waiting for CXL hcall " \ "%#x after %u msec\n", fn, total_delay); \ rc = H_BUSY; \ break; \ } \ msleep(delay); \ } \ } #define CXL_H_WAIT_UNTIL_DONE(...) _CXL_LOOP_HCALL(plpar_hcall, __VA_ARGS__) #define CXL_H9_WAIT_UNTIL_DONE(...) _CXL_LOOP_HCALL(plpar_hcall9, __VA_ARGS__) #define _PRINT_MSG(rc, format, ...) \ { \ if ((rc != H_SUCCESS) && (rc != H_CONTINUE)) \ pr_err(format, __VA_ARGS__); \ else \ pr_devel(format, __VA_ARGS__); \ } \ static char *afu_op_names[] = { "UNKNOWN_OP", /* 0 undefined */ "RESET", /* 1 */ "SUSPEND_PROCESS", /* 2 */ "RESUME_PROCESS", /* 3 */ "READ_ERR_STATE", /* 4 */ "GET_AFU_ERR", /* 5 */ "GET_CONFIG", /* 6 */ "GET_DOWNLOAD_STATE", /* 7 */ "TERMINATE_PROCESS", /* 8 */ "COLLECT_VPD", /* 9 */ "UNKNOWN_OP", /* 10 undefined */ "GET_FUNCTION_ERR_INT", /* 11 */ "ACK_FUNCTION_ERR_INT", /* 12 */ "GET_ERROR_LOG", /* 13 */ }; static char *control_adapter_op_names[] = { "UNKNOWN_OP", /* 0 undefined */ "RESET", /* 1 */ "COLLECT_VPD", /* 2 */ }; static char *download_op_names[] = { "UNKNOWN_OP", /* 0 undefined */ "DOWNLOAD", /* 1 */ "VALIDATE", /* 2 */ }; static char *op_str(unsigned int op, char *name_array[], int array_len) { if (op >= array_len) return "UNKNOWN_OP"; return name_array[op]; } #define OP_STR(op, name_array) op_str(op, name_array, ARRAY_SIZE(name_array)) #define OP_STR_AFU(op) OP_STR(op, afu_op_names) #define OP_STR_CONTROL_ADAPTER(op) OP_STR(op, control_adapter_op_names) #define OP_STR_DOWNLOAD_ADAPTER(op) OP_STR(op, download_op_names) long cxl_h_attach_process(u64 unit_address, struct cxl_process_element_hcall *element, u64 *process_token, u64 *mmio_addr, u64 *mmio_size) { unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; long rc; CXL_H_WAIT_UNTIL_DONE(rc, retbuf, H_ATTACH_CA_PROCESS, unit_address, virt_to_phys(element)); _PRINT_MSG(rc, "cxl_h_attach_process(%#.16llx, %#.16lx): %li\n", unit_address, virt_to_phys(element), rc); trace_cxl_hcall_attach(unit_address, virt_to_phys(element), retbuf[0], retbuf[1], retbuf[2], rc); pr_devel("token: 0x%.8lx mmio_addr: 0x%lx mmio_size: 0x%lx\nProcess Element Structure:\n", retbuf[0], retbuf[1], retbuf[2]); cxl_dump_debug_buffer(element, sizeof(*element)); switch (rc) { case H_SUCCESS: /* The process info is attached to the coherent platform function */ *process_token = retbuf[0]; if (mmio_addr) *mmio_addr = retbuf[1]; if (mmio_size) *mmio_size = retbuf[2]; return 0; case H_PARAMETER: /* An incorrect parameter was supplied. */ case H_FUNCTION: /* The function is not supported. */ return -EINVAL; case H_AUTHORITY: /* The partition does not have authority to perform this hcall */ case H_RESOURCE: /* The coherent platform function does not have enough additional resource to attach the process */ case H_HARDWARE: /* A hardware event prevented the attach operation */ case H_STATE: /* The coherent platform function is not in a valid state */ case H_BUSY: return -EBUSY; default: WARN(1, "Unexpected return code: %lx", rc); return -EINVAL; } } /* * cxl_h_detach_process - Detach a process element from a coherent * platform function. */ long cxl_h_detach_process(u64 unit_address, u64 process_token) { unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; long rc; CXL_H_WAIT_UNTIL_DONE(rc, retbuf, H_DETACH_CA_PROCESS, unit_address, process_token); _PRINT_MSG(rc, "cxl_h_detach_process(%#.16llx, 0x%.8llx): %li\n", unit_address, process_token, rc); trace_cxl_hcall_detach(unit_address, process_token, rc); switch (rc) { case H_SUCCESS: /* The process was detached from the coherent platform function */ return 0; case H_PARAMETER: /* An incorrect parameter was supplied. */ return -EINVAL; case H_AUTHORITY: /* The partition does not have authority to perform this hcall */ case H_RESOURCE: /* The function has page table mappings for MMIO */ case H_HARDWARE: /* A hardware event prevented the detach operation */ case H_STATE: /* The coherent platform function is not in a valid state */ case H_BUSY: return -EBUSY; default: WARN(1, "Unexpected return code: %lx", rc); return -EINVAL; } } /* * cxl_h_control_function - This H_CONTROL_CA_FUNCTION hypervisor call allows * the partition to manipulate or query * certain coherent platform function behaviors. */ static long cxl_h_control_function(u64 unit_address, u64 op, u64 p1, u64 p2, u64 p3, u64 p4, u64 *out) { unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; long rc; CXL_H9_WAIT_UNTIL_DONE(rc, retbuf, H_CONTROL_CA_FUNCTION, unit_address, op, p1, p2, p3, p4); _PRINT_MSG(rc, "cxl_h_control_function(%#.16llx, %s(%#llx, %#llx, %#llx, %#llx, R4: %#lx)): %li\n", unit_address, OP_STR_AFU(op), p1, p2, p3, p4, retbuf[0], rc); trace_cxl_hcall_control_function(unit_address, OP_STR_AFU(op), p1, p2, p3, p4, retbuf[0], rc); switch (rc) { case H_SUCCESS: /* The operation is completed for the coherent platform function */ if ((op == H_CONTROL_CA_FUNCTION_GET_FUNCTION_ERR_INT || op == H_CONTROL_CA_FUNCTION_READ_ERR_STATE || op == H_CONTROL_CA_FUNCTION_COLLECT_VPD)) *out = retbuf[0]; return 0; case H_PARAMETER: /* An incorrect parameter was supplied. */ case H_FUNCTION: /* The function is not supported. */ case H_NOT_FOUND: /* The operation supplied was not valid */ case H_NOT_AVAILABLE: /* The operation cannot be performed because the AFU has not been downloaded */ case H_SG_LIST: /* An block list entry was invalid */ return -EINVAL; case H_AUTHORITY: /* The partition does not have authority to perform this hcall */ case H_RESOURCE: /* The function has page table mappings for MMIO */ case H_HARDWARE: /* A hardware event prevented the attach operation */ case H_STATE: /* The coherent platform function is not in a valid state */ case H_BUSY: return -EBUSY; default: WARN(1, "Unexpected return code: %lx", rc); return -EINVAL; } } /* * cxl_h_reset_afu - Perform a reset to the coherent platform function. */ long cxl_h_reset_afu(u64 unit_address) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_RESET, 0, 0, 0, 0, NULL); } /* * cxl_h_suspend_process - Suspend a process from being executed * Parameter1 = process-token as returned from H_ATTACH_CA_PROCESS when * process was attached. */ long cxl_h_suspend_process(u64 unit_address, u64 process_token) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_SUSPEND_PROCESS, process_token, 0, 0, 0, NULL); } /* * cxl_h_resume_process - Resume a process to be executed * Parameter1 = process-token as returned from H_ATTACH_CA_PROCESS when * process was attached. */ long cxl_h_resume_process(u64 unit_address, u64 process_token) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_RESUME_PROCESS, process_token, 0, 0, 0, NULL); } /* * cxl_h_read_error_state - Checks the error state of the coherent * platform function. * R4 contains the error state */ long cxl_h_read_error_state(u64 unit_address, u64 *state) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_READ_ERR_STATE, 0, 0, 0, 0, state); } /* * cxl_h_get_afu_err - collect the AFU error buffer * Parameter1 = byte offset into error buffer to retrieve, valid values * are between 0 and (ibm,error-buffer-size - 1) * Parameter2 = 4K aligned real address of error buffer, to be filled in * Parameter3 = length of error buffer, valid values are 4K or less */ long cxl_h_get_afu_err(u64 unit_address, u64 offset, u64 buf_address, u64 len) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_GET_AFU_ERR, offset, buf_address, len, 0, NULL); } /* * cxl_h_get_config - collect configuration record for the * coherent platform function * Parameter1 = # of configuration record to retrieve, valid values are * between 0 and (ibm,#config-records - 1) * Parameter2 = byte offset into configuration record to retrieve, * valid values are between 0 and (ibm,config-record-size - 1) * Parameter3 = 4K aligned real address of configuration record buffer, * to be filled in * Parameter4 = length of configuration buffer, valid values are 4K or less */ long cxl_h_get_config(u64 unit_address, u64 cr_num, u64 offset, u64 buf_address, u64 len) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_GET_CONFIG, cr_num, offset, buf_address, len, NULL); } /* * cxl_h_terminate_process - Terminate the process before completion * Parameter1 = process-token as returned from H_ATTACH_CA_PROCESS when * process was attached. */ long cxl_h_terminate_process(u64 unit_address, u64 process_token) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_TERMINATE_PROCESS, process_token, 0, 0, 0, NULL); } /* * cxl_h_collect_vpd - Collect VPD for the coherent platform function. * Parameter1 = # of VPD record to retrieve, valid values are between 0 * and (ibm,#config-records - 1). * Parameter2 = 4K naturally aligned real buffer containing block * list entries * Parameter3 = number of block list entries in the block list, valid * values are between 0 and 256 */ long cxl_h_collect_vpd(u64 unit_address, u64 record, u64 list_address, u64 num, u64 *out) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_COLLECT_VPD, record, list_address, num, 0, out); } /* * cxl_h_get_fn_error_interrupt - Read the function-wide error data based on an interrupt */ long cxl_h_get_fn_error_interrupt(u64 unit_address, u64 *reg) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_GET_FUNCTION_ERR_INT, 0, 0, 0, 0, reg); } /* * cxl_h_ack_fn_error_interrupt - Acknowledge function-wide error data * based on an interrupt * Parameter1 = value to write to the function-wide error interrupt register */ long cxl_h_ack_fn_error_interrupt(u64 unit_address, u64 value) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_ACK_FUNCTION_ERR_INT, value, 0, 0, 0, NULL); } /* * cxl_h_get_error_log - Retrieve the Platform Log ID (PLID) of * an error log */ long cxl_h_get_error_log(u64 unit_address, u64 value) { return cxl_h_control_function(unit_address, H_CONTROL_CA_FUNCTION_GET_ERROR_LOG, 0, 0, 0, 0, NULL); } /* * cxl_h_collect_int_info - Collect interrupt info about a coherent * platform function after an interrupt occurred. */ long cxl_h_collect_int_info(u64 unit_address, u64 process_token, struct cxl_irq_info *info) { long rc; BUG_ON(sizeof(*info) != sizeof(unsigned long[PLPAR_HCALL9_BUFSIZE])); rc = plpar_hcall9(H_COLLECT_CA_INT_INFO, (unsigned long *) info, unit_address, process_token); _PRINT_MSG(rc, "cxl_h_collect_int_info(%#.16llx, 0x%llx): %li\n", unit_address, process_token, rc); trace_cxl_hcall_collect_int_info(unit_address, process_token, rc); switch (rc) { case H_SUCCESS: /* The interrupt info is returned in return registers. */ pr_devel("dsisr:%#llx, dar:%#llx, dsr:%#llx, pid_tid:%#llx, afu_err:%#llx, errstat:%#llx\n", info->dsisr, info->dar, info->dsr, info->reserved, info->afu_err, info->errstat); return 0; case H_PARAMETER: /* An incorrect parameter was supplied. */ return -EINVAL; case H_AUTHORITY: /* The partition does not have authority to perform this hcall. */ case H_HARDWARE: /* A hardware event prevented the collection of the interrupt info.*/ case H_STATE: /* The coherent platform function is not in a valid state to collect interrupt info. */ return -EBUSY; default: WARN(1, "Unexpected return code: %lx", rc); return -EINVAL; } } /* * cxl_h_control_faults - Control the operation of a coherent platform * function after a fault occurs. * * Parameters * control-mask: value to control the faults * looks like PSL_TFC_An shifted >> 32 * reset-mask: mask to control reset of function faults * Set reset_mask = 1 to reset PSL errors */ long cxl_h_control_faults(u64 unit_address, u64 process_token, u64 control_mask, u64 reset_mask) { unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; long rc; memset(retbuf, 0, sizeof(retbuf)); rc = plpar_hcall(H_CONTROL_CA_FAULTS, retbuf, unit_address, H_CONTROL_CA_FAULTS_RESPOND_PSL, process_token, control_mask, reset_mask); _PRINT_MSG(rc, "cxl_h_control_faults(%#.16llx, 0x%llx, %#llx, %#llx): %li (%#lx)\n", unit_address, process_token, control_mask, reset_mask, rc, retbuf[0]); trace_cxl_hcall_control_faults(unit_address, process_token, control_mask, reset_mask, retbuf[0], rc); switch (rc) { case H_SUCCESS: /* Faults were successfully controlled for the function. */ return 0; case H_PARAMETER: /* An incorrect parameter was supplied. */ return -EINVAL; case H_HARDWARE: /* A hardware event prevented the control of faults. */ case H_STATE: /* The function was in an invalid state. */ case H_AUTHORITY: /* The partition does not have authority to perform this hcall; the coherent platform facilities may need to be licensed. */ return -EBUSY; case H_FUNCTION: /* The function is not supported */ case H_NOT_FOUND: /* The operation supplied was not valid */ return -EINVAL; default: WARN(1, "Unexpected return code: %lx", rc); return -EINVAL; } } /* * cxl_h_control_facility - This H_CONTROL_CA_FACILITY hypervisor call * allows the partition to manipulate or query * certain coherent platform facility behaviors. */ static long cxl_h_control_facility(u64 unit_address, u64 op, u64 p1, u64 p2, u64 p3, u64 p4, u64 *out) { unsigned long retbuf[PLPAR_HCALL9_BUFSIZE]; long rc; CXL_H9_WAIT_UNTIL_DONE(rc, retbuf, H_CONTROL_CA_FACILITY, unit_address, op, p1, p2, p3, p4); _PRINT_MSG(rc, "cxl_h_control_facility(%#.16llx, %s(%#llx, %#llx, %#llx, %#llx, R4: %#lx)): %li\n", unit_address, OP_STR_CONTROL_ADAPTER(op), p1, p2, p3, p4, retbuf[0], rc); trace_cxl_hcall_control_facility(unit_address, OP_STR_CONTROL_ADAPTER(op), p1, p2, p3, p4, retbuf[0], rc); switch (rc) { case H_SUCCESS: /* The operation is completed for the coherent platform facility */ if (op == H_CONTROL_CA_FACILITY_COLLECT_VPD) *out = retbuf[0]; return 0; case H_PARAMETER: /* An incorrect parameter was supplied. */ case H_FUNCTION: /* The function is not supported. */ case H_NOT_FOUND: /* The operation supplied was not valid */ case H_NOT_AVAILABLE: /* The operation cannot be performed because the AFU has not been downloaded */ case H_SG_LIST: /* An block list entry was invalid */ return -EINVAL; case H_AUTHORITY: /* The partition does not have authority to perform this hcall */ case H_RESOURCE: /* The function has page table mappings for MMIO */ case H_HARDWARE: /* A hardware event prevented the attach operation */ case H_STATE: /* The coherent platform facility is not in a valid state */ case H_BUSY: return -EBUSY; default: WARN(1, "Unexpected return code: %lx", rc); return -EINVAL; } } /* * cxl_h_reset_adapter - Perform a reset to the coherent platform facility. */ long cxl_h_reset_adapter(u64 unit_address) { return cxl_h_control_facility(unit_address, H_CONTROL_CA_FACILITY_RESET, 0, 0, 0, 0, NULL); } /* * cxl_h_collect_vpd - Collect VPD for the coherent platform function. * Parameter1 = 4K naturally aligned real buffer containing block * list entries * Parameter2 = number of block list entries in the block list, valid * values are between 0 and 256 */ long cxl_h_collect_vpd_adapter(u64 unit_address, u64 list_address, u64 num, u64 *out) { return cxl_h_control_facility(unit_address, H_CONTROL_CA_FACILITY_COLLECT_VPD, list_address, num, 0, 0, out); } /* * cxl_h_download_facility - This H_DOWNLOAD_CA_FACILITY * hypervisor call provide platform support for * downloading a base adapter image to the coherent * platform facility, and for validating the entire * image after the download. * Parameters * op: operation to perform to the coherent platform function * Download: operation = 1, the base image in the coherent platform * facility is first erased, and then * programmed using the image supplied * in the scatter/gather list. * Validate: operation = 2, the base image in the coherent platform * facility is compared with the image * supplied in the scatter/gather list. * list_address: 4K naturally aligned real buffer containing * scatter/gather list entries. * num: number of block list entries in the scatter/gather list. */ static long cxl_h_download_facility(u64 unit_address, u64 op, u64 list_address, u64 num, u64 *out) { unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; unsigned int delay, total_delay = 0; u64 token = 0; long rc; if (*out != 0) token = *out; memset(retbuf, 0, sizeof(retbuf)); while (1) { rc = plpar_hcall(H_DOWNLOAD_CA_FACILITY, retbuf, unit_address, op, list_address, num, token); token = retbuf[0]; if (rc != H_BUSY && !H_IS_LONG_BUSY(rc)) break; if (rc != H_BUSY) { delay = get_longbusy_msecs(rc); total_delay += delay; if (total_delay > CXL_HCALL_TIMEOUT_DOWNLOAD) { WARN(1, "Warning: Giving up waiting for CXL hcall " "%#x after %u msec\n", H_DOWNLOAD_CA_FACILITY, total_delay); rc = H_BUSY; break; } msleep(delay); } } _PRINT_MSG(rc, "cxl_h_download_facility(%#.16llx, %s(%#llx, %#llx), %#lx): %li\n", unit_address, OP_STR_DOWNLOAD_ADAPTER(op), list_address, num, retbuf[0], rc); trace_cxl_hcall_download_facility(unit_address, OP_STR_DOWNLOAD_ADAPTER(op), list_address, num, retbuf[0], rc); switch (rc) { case H_SUCCESS: /* The operation is completed for the coherent platform facility */ return 0; case H_PARAMETER: /* An incorrect parameter was supplied */ case H_FUNCTION: /* The function is not supported. */ case H_SG_LIST: /* An block list entry was invalid */ case H_BAD_DATA: /* Image verification failed */ return -EINVAL; case H_AUTHORITY: /* The partition does not have authority to perform this hcall */ case H_RESOURCE: /* The function has page table mappings for MMIO */ case H_HARDWARE: /* A hardware event prevented the attach operation */ case H_STATE: /* The coherent platform facility is not in a valid state */ case H_BUSY: return -EBUSY; case H_CONTINUE: *out = retbuf[0]; return 1; /* More data is needed for the complete image */ default: WARN(1, "Unexpected return code: %lx", rc); return -EINVAL; } } /* * cxl_h_download_adapter_image - Download the base image to the coherent * platform facility. */ long cxl_h_download_adapter_image(u64 unit_address, u64 list_address, u64 num, u64 *out) { return cxl_h_download_facility(unit_address, H_DOWNLOAD_CA_FACILITY_DOWNLOAD, list_address, num, out); } /* * cxl_h_validate_adapter_image - Validate the base image in the coherent * platform facility. */ long cxl_h_validate_adapter_image(u64 unit_address, u64 list_address, u64 num, u64 *out) { return cxl_h_download_facility(unit_address, H_DOWNLOAD_CA_FACILITY_VALIDATE, list_address, num, out); }