1957 lines · 53.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * arch/parisc/kernel/firmware.c  - safe PDC access routines * *	PDC == Processor Dependent Code * * See PDC documentation at * https://parisc.wiki.kernel.org/index.php/Technical_Documentation * for documentation describing the entry points and calling * conventions defined below. * * Copyright 1999 SuSE GmbH Nuernberg (Philipp Rumpf, prumpf@tux.org) * Copyright 1999 The Puffin Group, (Alex deVries, David Kennedy) * Copyright 2003 Grant Grundler <grundler parisc-linux org> * Copyright 2003,2004 Ryan Bradetich <rbrad@parisc-linux.org> * Copyright 2004,2006 Thibaut VARENE <varenet@parisc-linux.org> */ /*	I think it would be in everyone's best interest to follow this *	guidelines when writing PDC wrappers: * *	 - the name of the pdc wrapper should match one of the macros *	   used for the first two arguments *	 - don't use caps for random parts of the name *	 - use the static PDC result buffers and "copyout" to structs *	   supplied by the caller to encapsulate alignment restrictions *	 - hold pdc_lock while in PDC or using static result buffers *	 - use __pa() to convert virtual (kernel) pointers to physical *	   ones. *	 - the name of the struct used for pdc return values should equal *	   one of the macros used for the first two arguments to the *	   corresponding PDC call *	 - keep the order of arguments *	 - don't be smart (setting trailing NUL bytes for strings, return *	   something useful even if the call failed) unless you are sure *	   it's not going to affect functionality or performance * *	Example: *	int pdc_cache_info(struct pdc_cache_info *cache_info ) *	{ *		int retval; * *		spin_lock_irq(&pdc_lock); *		retval = mem_pdc_call(PDC_CACHE,PDC_CACHE_INFO,__pa(cache_info),0); *		convert_to_wide(pdc_result); *		memcpy(cache_info, pdc_result, sizeof(*cache_info)); *		spin_unlock_irq(&pdc_lock); * *		return retval; *	} *					prumpf	991016	 */ #include <linux/stdarg.h> #include <linux/delay.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/string.h>#include <linux/spinlock.h> #include <asm/page.h>#include <asm/pdc.h>#include <asm/pdcpat.h>#include <asm/processor.h>	/* for boot_cpu_data */ #if defined(BOOTLOADER)# undef  spin_lock_irqsave# define spin_lock_irqsave(a, b) { b = 1; }# undef  spin_unlock_irqrestore# define spin_unlock_irqrestore(a, b)#elsestatic DEFINE_SPINLOCK(pdc_lock);#endif static unsigned long pdc_result[NUM_PDC_RESULT]  __aligned(8);static unsigned long pdc_result2[NUM_PDC_RESULT] __aligned(8); #ifdef CONFIG_64BIT#define WIDE_FIRMWARE		PDC_MODEL_OS64#define NARROW_FIRMWARE		PDC_MODEL_OS32 /* Firmware needs to be initially set to narrow to determine the * actual firmware width. */int parisc_narrow_firmware __ro_after_init = NARROW_FIRMWARE;#endif /* On most currently-supported platforms, IODC I/O calls are 32-bit calls * and MEM_PDC calls are always the same width as the OS. * Some PAT boxes may have 64-bit IODC I/O. * * Ryan Bradetich added the now obsolete CONFIG_PDC_NARROW to allow * 64-bit kernels to run on systems with 32-bit MEM_PDC calls. * This allowed wide kernels to run on Cxxx boxes. * We now detect 32-bit-only PDC and dynamically switch to 32-bit mode * when running a 64-bit kernel on such boxes (e.g. C200 or C360). */ #ifdef CONFIG_64BITlong real64_call(unsigned long function, ...);#endiflong real32_call(unsigned long function, ...); #ifdef CONFIG_64BIT#   define MEM_PDC (unsigned long)(PAGE0->mem_pdc_hi) << 32 | PAGE0->mem_pdc#   define mem_pdc_call(args...) unlikely(parisc_narrow_firmware) ? real32_call(MEM_PDC, args) : real64_call(MEM_PDC, args)#else#   define MEM_PDC (unsigned long)PAGE0->mem_pdc#   define mem_pdc_call(args...) real32_call(MEM_PDC, args)#endif  /** * f_extend - Convert PDC addresses to kernel addresses. * @address: Address returned from PDC. * * This function is used to convert PDC addresses into kernel addresses * when the PDC address size and kernel address size are different. */static unsigned long f_extend(unsigned long address){#ifdef CONFIG_64BIT	if(unlikely(parisc_narrow_firmware)) {		if((address & 0xff000000) == 0xf0000000)			return (0xfffffff0UL << 32) | (u32)address; 		if((address & 0xf0000000) == 0xf0000000)			return (0xffffffffUL << 32) | (u32)address;	}#endif	return address;} /** * convert_to_wide - Convert the return buffer addresses into kernel addresses. * @addr: The return buffer from PDC. * * This function is used to convert the return buffer addresses retrieved from PDC * into kernel addresses when the PDC address size and kernel address size are * different. */static void convert_to_wide(unsigned long *addr){#ifdef CONFIG_64BIT	int i;	unsigned int *p = (unsigned int *)addr; 	if (unlikely(parisc_narrow_firmware)) {		for (i = (NUM_PDC_RESULT-1); i >= 0; --i)			addr[i] = p[i];	}#endif} #ifdef CONFIG_64BITvoid set_firmware_width_unlocked(void){	int ret; 	ret = mem_pdc_call(PDC_MODEL, PDC_MODEL_CAPABILITIES,		__pa(pdc_result), 0);	if (ret < 0)		return;	convert_to_wide(pdc_result);	if (pdc_result[0] != NARROW_FIRMWARE)		parisc_narrow_firmware = 0;} /** * set_firmware_width - Determine if the firmware is wide or narrow. * * This function must be called before any pdc_* function that uses the * convert_to_wide function. */void set_firmware_width(void){	unsigned long flags; 	/* already initialized? */	if (parisc_narrow_firmware != NARROW_FIRMWARE)		return; 	spin_lock_irqsave(&pdc_lock, flags);	set_firmware_width_unlocked();	spin_unlock_irqrestore(&pdc_lock, flags);}#elsevoid set_firmware_width_unlocked(void){	return;} void set_firmware_width(void){	return;}#endif /*CONFIG_64BIT*/  #if !defined(BOOTLOADER)/** * pdc_emergency_unlock - Unlock the linux pdc lock * * This call unlocks the linux pdc lock in case we need some PDC functions * (like pdc_add_valid) during kernel stack dump. */void pdc_emergency_unlock(void){ 	/* Spinlock DEBUG code freaks out if we unconditionally unlock */        if (spin_is_locked(&pdc_lock))		spin_unlock(&pdc_lock);}  /** * pdc_add_valid - Verify address can be accessed without causing a HPMC. * @address: Address to be verified. * * This PDC call attempts to read from the specified address and verifies * if the address is valid. *  * The return value is PDC_OK (0) in case accessing this address is valid. */int pdc_add_valid(unsigned long address){        int retval;	unsigned long flags;         spin_lock_irqsave(&pdc_lock, flags);        retval = mem_pdc_call(PDC_ADD_VALID, PDC_ADD_VALID_VERIFY, address);        spin_unlock_irqrestore(&pdc_lock, flags);         return retval;}EXPORT_SYMBOL(pdc_add_valid); /** * pdc_instr - Get instruction that invokes PDCE_CHECK in HPMC handler. * @instr: Pointer to variable which will get instruction opcode. * * The return value is PDC_OK (0) in case call succeeded. */int __init pdc_instr(unsigned int *instr){	int retval;	unsigned long flags; 	spin_lock_irqsave(&pdc_lock, flags);	retval = mem_pdc_call(PDC_INSTR, 0UL, __pa(pdc_result));	convert_to_wide(pdc_result);	*instr = pdc_result[0];	spin_unlock_irqrestore(&pdc_lock, flags); 	return retval;} /** * pdc_chassis_info - Return chassis information. * @chassis_info: The memory buffer address. * @led_info: The size of the memory buffer address. * @len: The size of the memory buffer address. * * An HVERSION dependent call for returning the chassis information. */int __init pdc_chassis_info(struct pdc_chassis_info *chassis_info, void *led_info, unsigned long len){        int retval;	unsigned long flags;         spin_lock_irqsave(&pdc_lock, flags);        memcpy(&pdc_result, chassis_info, sizeof(*chassis_info));        memcpy(&pdc_result2, led_info, len);        retval = mem_pdc_call(PDC_CHASSIS, PDC_RETURN_CHASSIS_INFO,                              __pa(pdc_result), __pa(pdc_result2), len);        memcpy(chassis_info, pdc_result, sizeof(*chassis_info));        memcpy(led_info, pdc_result2, len);        spin_unlock_irqrestore(&pdc_lock, flags);         return retval;} /** * pdc_pat_chassis_send_log - Sends a PDC PAT CHASSIS log message. * @state: state of the machine * @data: value for that state *  * Must be correctly formatted or expect system crash */#ifdef CONFIG_64BITint pdc_pat_chassis_send_log(unsigned long state, unsigned long data){	int retval = 0;	unsigned long flags;        	if (!is_pdc_pat())		return -1; 	spin_lock_irqsave(&pdc_lock, flags);	retval = mem_pdc_call(PDC_PAT_CHASSIS_LOG, PDC_PAT_CHASSIS_WRITE_LOG, __pa(&state), __pa(&data));	spin_unlock_irqrestore(&pdc_lock, flags); 	return retval;}#endif /** * pdc_chassis_disp - Updates chassis code * @disp: value to show on display */int pdc_chassis_disp(unsigned long disp){	int retval = 0;	unsigned long flags; 	spin_lock_irqsave(&pdc_lock, flags);	retval = mem_pdc_call(PDC_CHASSIS, PDC_CHASSIS_DISP, disp);	spin_unlock_irqrestore(&pdc_lock, flags); 	return retval;} /** * __pdc_cpu_rendezvous - Stop currently executing CPU and do not return. */int __pdc_cpu_rendezvous(void){	if (is_pdc_pat())		return mem_pdc_call(PDC_PAT_CPU, PDC_PAT_CPU_RENDEZVOUS);	else		return mem_pdc_call(PDC_PROC, 1, 0);} /** * pdc_cpu_rendezvous_lock - Lock PDC while transitioning to rendezvous state */void pdc_cpu_rendezvous_lock(void) __acquires(&pdc_lock){	spin_lock(&pdc_lock);} /** * pdc_cpu_rendezvous_unlock - Unlock PDC after reaching rendezvous state */void pdc_cpu_rendezvous_unlock(void) __releases(&pdc_lock){	spin_unlock(&pdc_lock);} /** * pdc_pat_get_PDC_entrypoint - Get PDC entry point for current CPU * @pdc_entry: pointer to where the PDC entry point should be stored */int pdc_pat_get_PDC_entrypoint(unsigned long *pdc_entry){	int retval = 0;	unsigned long flags; 	if (!IS_ENABLED(CONFIG_SMP) || !is_pdc_pat()) {		*pdc_entry = MEM_PDC;		return 0;	} 	spin_lock_irqsave(&pdc_lock, flags);	retval = mem_pdc_call(PDC_PAT_CPU, PDC_PAT_CPU_GET_PDC_ENTRYPOINT,			__pa(pdc_result));	*pdc_entry = pdc_result[0];	spin_unlock_irqrestore(&pdc_lock, flags); 	return retval;}/** * pdc_chassis_warn - Fetches chassis warnings * @warn: The warning value to be shown */int pdc_chassis_warn(unsigned long *warn){	int retval = 0;	unsigned long flags; 	spin_lock_irqsave(&pdc_lock, flags);	retval = mem_pdc_call(PDC_CHASSIS, PDC_CHASSIS_WARN, __pa(pdc_result));	*warn = pdc_result[0];	spin_unlock_irqrestore(&pdc_lock, flags); 	return retval;} int pdc_coproc_cfg_unlocked(struct pdc_coproc_cfg *pdc_coproc_info){	int ret; 	ret = mem_pdc_call(PDC_COPROC, PDC_COPROC_CFG, __pa(pdc_result));	convert_to_wide(pdc_result);	pdc_coproc_info->ccr_functional = pdc_result[0];	pdc_coproc_info->ccr_present = pdc_result[1];	pdc_coproc_info->revision = pdc_result[17];	pdc_coproc_info->model = pdc_result[18]; 	return ret;} /** * pdc_coproc_cfg - To identify coprocessors attached to the processor. * @pdc_coproc_info: Return buffer address. * * This PDC call returns the presence and status of all the coprocessors * attached to the processor. */int pdc_coproc_cfg(struct pdc_coproc_cfg *pdc_coproc_info){	int ret;	unsigned long flags; 	spin_lock_irqsave(&pdc_lock, flags);	ret = pdc_coproc_cfg_unlocked(pdc_coproc_info);	spin_unlock_irqrestore(&pdc_lock, flags); 	return ret;} /** * pdc_iodc_read - Read data from the modules IODC. * @actcnt: The actual number of bytes. * @hpa: The HPA of the module for the iodc read. * @index: The iodc entry point. * @iodc_data: A buffer memory for the iodc options. * @iodc_data_size: Size of the memory buffer. * * This PDC call reads from the IODC of the module specified by the hpa * argument. */int pdc_iodc_read(unsigned long *actcnt, unsigned long hpa, unsigned int index,		  void *iodc_data, unsigned int iodc_data_size){	int retval;	unsigned long flags; 	spin_lock_irqsave(&pdc_lock, flags);	retval = mem_pdc_call(PDC_IODC, PDC_IODC_READ, __pa(pdc_result), hpa, 			      index, __pa(pdc_result2), iodc_data_size);	convert_to_wide(pdc_result);	*actcnt = pdc_result[0];	memcpy(iodc_data, pdc_result2, iodc_data_size);	spin_unlock_irqrestore(&pdc_lock, flags); 	return retval;}EXPORT_SYMBOL(pdc_iodc_read); /** * pdc_system_map_find_mods - Locate unarchitected modules. * @pdc_mod_info: Return buffer address. * @mod_path: pointer to dev path structure. * @mod_index: fixed address module index. * * To locate and identify modules which reside at fixed I/O addresses, which * do not self-identify via architected bus walks. */int pdc_system_map_find_mods(struct pdc_system_map_mod_info *pdc_mod_info,			     struct pdc_module_path *mod_path, long mod_index){	int retval;	unsigned long flags; 	spin_lock_irqsave(&pdc_lock, flags);	memcpy(pdc_result2, mod_path, sizeof(*mod_path));	retval = mem_pdc_call(PDC_SYSTEM_MAP, PDC_FIND_MODULE, __pa(pdc_result),			      __pa(pdc_result2), mod_index);	convert_to_wide(pdc_result);	memcpy(pdc_mod_info, pdc_result, sizeof(*pdc_mod_info));	memcpy(mod_path, pdc_result2, sizeof(*mod_path));	spin_unlock_irqrestore(&pdc_lock, flags); 	pdc_mod_info->mod_addr = f_extend(pdc_mod_info->mod_addr);	return retval;} /** * pdc_system_map_find_addrs - Retrieve additional address ranges. * @pdc_addr_info: Return buffer address. * @mod_index: Fixed address module index. * @addr_index: Address range index. *  * Retrieve additional information about subsequent address ranges for modules * with multiple address ranges.   */int pdc_system_map_find_addrs(struct pdc_system_map_addr_info *pdc_addr_info, 			      long mod_index, long addr_index){	int retval;	unsigned long flags; 	spin_lock_irqsave(&pdc_lock, flags);	retval = mem_pdc_call(PDC_SYSTEM_MAP, PDC_FIND_ADDRESS, __pa(pdc_result),			      mod_index, addr_index);	convert_to_wide(pdc_result);	memcpy(pdc_addr_info, pdc_result, sizeof(*pdc_addr_info));	spin_unlock_irqrestore(&pdc_lock, flags); 

Lines 1–500 of 1957.