diff options
85 files changed, 7820 insertions, 1073 deletions
| diff --git a/Documentation/ABI/testing/debugfs-pfo-nx-crypto b/Documentation/ABI/testing/debugfs-pfo-nx-crypto new file mode 100644 index 000000000000..685d5a448423 --- /dev/null +++ b/Documentation/ABI/testing/debugfs-pfo-nx-crypto @@ -0,0 +1,45 @@ +What:		/sys/kernel/debug/nx-crypto/* +Date:		March 2012 +KernelVersion:	3.4 +Contact:	Kent Yoder <key@linux.vnet.ibm.com> +Description: + +  These debugfs interfaces are built by the nx-crypto driver, built in +arch/powerpc/crypto/nx. + +Error Detection +=============== + +errors: +- A u32 providing a total count of errors since the driver was loaded. The +only errors counted here are those returned from the hcall, H_COP_OP. + +last_error: +- The most recent non-zero return code from the H_COP_OP hcall. -EBUSY is not +recorded here (the hcall will retry until -EBUSY goes away). + +last_error_pid: +- The process ID of the process who received the most recent error from the +hcall. + +Device Use +========== + +aes_bytes: +- The total number of bytes encrypted using AES in any of the driver's +supported modes. + +aes_ops: +- The total number of AES operations submitted to the hardware. + +sha256_bytes: +- The total number of bytes hashed by the hardware using SHA-256. + +sha256_ops: +- The total number of SHA-256 operations submitted to the hardware. + +sha512_bytes: +- The total number of bytes hashed by the hardware using SHA-512. + +sha512_ops: +- The total number of SHA-512 operations submitted to the hardware. diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index 8a01098eaaca..0a947bd9c076 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -350,7 +350,7 @@ config ARCH_ENABLE_MEMORY_HOTREMOVE  config KEXEC  	bool "kexec system call (EXPERIMENTAL)" -	depends on (PPC_BOOK3S || FSL_BOOKE || (44x && !SMP && !PPC_47x)) && EXPERIMENTAL +	depends on (PPC_BOOK3S || FSL_BOOKE || (44x && !SMP)) && EXPERIMENTAL  	help  	  kexec is a system call that implements the ability to shutdown your  	  current kernel, and to start another kernel.  It is like a reboot @@ -367,7 +367,7 @@ config KEXEC  config CRASH_DUMP  	bool "Build a kdump crash kernel" -	depends on PPC64 || 6xx || FSL_BOOKE || (44x && !SMP && !PPC_47x) +	depends on PPC64 || 6xx || FSL_BOOKE || (44x && !SMP)  	select RELOCATABLE if PPC64 || 44x  	select DYNAMIC_MEMSTART if FSL_BOOKE  	help diff --git a/arch/powerpc/Makefile b/arch/powerpc/Makefile index 6524c6e21896..950d1f7a5a39 100644 --- a/arch/powerpc/Makefile +++ b/arch/powerpc/Makefile @@ -69,6 +69,16 @@ LDFLAGS_vmlinux	:= $(LDFLAGS_vmlinux-y)  CFLAGS-$(CONFIG_PPC64)	:= -mminimal-toc -mtraceback=no -mcall-aixdesc  CFLAGS-$(CONFIG_PPC32)	:= -ffixed-r2 -mmultiple + +CFLAGS-$(CONFIG_GENERIC_CPU) += $(call cc-option,-mtune=power7,-mtune=power4) +CFLAGS-$(CONFIG_CELL_CPU) += $(call cc-option,-mcpu=cell) +CFLAGS-$(CONFIG_POWER4_CPU) += $(call cc-option,-mcpu=power4) +CFLAGS-$(CONFIG_POWER5_CPU) += $(call cc-option,-mcpu=power5) +CFLAGS-$(CONFIG_POWER6_CPU) += $(call cc-option,-mcpu=power6) +CFLAGS-$(CONFIG_POWER7_CPU) += $(call cc-option,-mcpu=power7) + +CFLAGS-$(CONFIG_TUNE_CELL) += $(call cc-option,-mtune=cell) +  KBUILD_CPPFLAGS	+= -Iarch/$(ARCH)  KBUILD_AFLAGS	+= -Iarch/$(ARCH)  KBUILD_CFLAGS	+= -msoft-float -pipe -Iarch/$(ARCH) $(CFLAGS-y) @@ -76,32 +86,11 @@ CPP		= $(CC) -E $(KBUILD_CFLAGS)  CHECKFLAGS	+= -m$(CONFIG_WORD_SIZE) -D__powerpc__ -D__powerpc$(CONFIG_WORD_SIZE)__ -ifeq ($(CONFIG_PPC64),y) -GCC_BROKEN_VEC	:= $(call cc-ifversion, -lt, 0400, y) - -ifeq ($(CONFIG_POWER4_ONLY),y) -ifeq ($(CONFIG_ALTIVEC),y) -ifeq ($(GCC_BROKEN_VEC),y) -	KBUILD_CFLAGS += $(call cc-option,-mcpu=970) -else -	KBUILD_CFLAGS += $(call cc-option,-mcpu=power4) -endif -else -	KBUILD_CFLAGS += $(call cc-option,-mcpu=power4) -endif -else -	KBUILD_CFLAGS += $(call cc-option,-mtune=power4) -endif -endif -  KBUILD_LDFLAGS_MODULE += arch/powerpc/lib/crtsavres.o -ifeq ($(CONFIG_TUNE_CELL),y) -	KBUILD_CFLAGS += $(call cc-option,-mtune=cell) -endif - -# No AltiVec instruction when building kernel +# No AltiVec or VSX instructions when building kernel  KBUILD_CFLAGS += $(call cc-option,-mno-altivec) +KBUILD_CFLAGS += $(call cc-option,-mno-vsx)  # No SPE instruction when building kernel  # (We use all available options to help semi-broken compilers) @@ -160,6 +149,7 @@ core-$(CONFIG_KVM) 		+= arch/powerpc/kvm/  core-$(CONFIG_PERF_EVENTS)	+= arch/powerpc/perf/  drivers-$(CONFIG_OPROFILE)	+= arch/powerpc/oprofile/ +drivers-$(CONFIG_CRYPTO_DEV_NX) += drivers/crypto/nx/  # Default to zImage, override when needed  all: zImage @@ -234,10 +224,11 @@ archprepare: checkbin  # Use the file '.tmp_gas_check' for binutils tests, as gas won't output  # to stdout and these checks are run even on install targets.  TOUT	:= .tmp_gas_check -# Ensure this is binutils 2.12.1 (or 2.12.90.0.7) or later for altivec -# instructions. -# gcc-3.4 and binutils-2.14 are a fatal combination. +# Check gcc and binutils versions: +# - gcc-3.4 and binutils-2.14 are a fatal combination +# - Require gcc 4.0 or above on 64-bit +# - gcc-4.2.0 has issues compiling modules on 64-bit  checkbin:  	@if test "$(call cc-version)" = "0304" ; then \  		if ! /bin/echo mftb 5 | $(AS) -v -mppc -many -o $(TOUT) >/dev/null 2>&1 ; then \ @@ -247,6 +238,12 @@ checkbin:  			false; \  		fi ; \  	fi +	@if test "$(call cc-version)" -lt "0400" \ +	    && test "x${CONFIG_PPC64}" = "xy" ; then \ +                echo -n "Sorry, GCC v4.0 or above is required to build " ; \ +                echo "the 64-bit powerpc kernel." ; \ +                false ; \ +        fi  	@if test "$(call cc-fullversion)" = "040200" \  	    && test "x${CONFIG_MODULES}${CONFIG_PPC64}" = "xyy" ; then \  		echo -n '*** GCC-4.2.0 cannot compile the 64-bit powerpc ' ; \ diff --git a/arch/powerpc/boot/dts/bluestone.dts b/arch/powerpc/boot/dts/bluestone.dts index 7bda373f10ef..9d4917aebe6b 100644 --- a/arch/powerpc/boot/dts/bluestone.dts +++ b/arch/powerpc/boot/dts/bluestone.dts @@ -373,5 +373,30 @@  				0x0 0x0 0x0 0x3 &UIC3 0xe 0x4 /* swizzled int C */  				0x0 0x0 0x0 0x4 &UIC3 0xf 0x4 /* swizzled int D */>;  		}; + +		MSI: ppc4xx-msi@C10000000 { +			compatible = "amcc,ppc4xx-msi", "ppc4xx-msi"; +			reg = < 0xC 0x10000000 0x100 +				0xC 0x10000000 0x100>; +			sdr-base = <0x36C>; +			msi-data = <0x00004440>; +			msi-mask = <0x0000ffe0>; +			interrupts =<0 1 2 3 4 5 6 7>; +			interrupt-parent = <&MSI>; +			#interrupt-cells = <1>; +			#address-cells = <0>; +			#size-cells = <0>; +			msi-available-ranges = <0x0 0x100>; +			interrupt-map = < +				0 &UIC3 0x18 1 +				1 &UIC3 0x19 1 +				2 &UIC3 0x1A 1 +				3 &UIC3 0x1B 1 +				4 &UIC3 0x1C 1 +				5 &UIC3 0x1D 1 +				6 &UIC3 0x1E 1 +				7 &UIC3 0x1F 1 +			>; +		};  	};  }; diff --git a/arch/powerpc/configs/g5_defconfig b/arch/powerpc/configs/g5_defconfig index 1196c34163b7..07b7f2af2dca 100644 --- a/arch/powerpc/configs/g5_defconfig +++ b/arch/powerpc/configs/g5_defconfig @@ -1,5 +1,4 @@  CONFIG_PPC64=y -CONFIG_POWER4_ONLY=y  CONFIG_ALTIVEC=y  CONFIG_SMP=y  CONFIG_NR_CPUS=4 diff --git a/arch/powerpc/configs/maple_defconfig b/arch/powerpc/configs/maple_defconfig index 2244d370f24d..02ac96b679b8 100644 --- a/arch/powerpc/configs/maple_defconfig +++ b/arch/powerpc/configs/maple_defconfig @@ -1,5 +1,4 @@  CONFIG_PPC64=y -CONFIG_POWER4_ONLY=y  CONFIG_SMP=y  CONFIG_NR_CPUS=4  CONFIG_EXPERIMENTAL=y diff --git a/arch/powerpc/configs/pasemi_defconfig b/arch/powerpc/configs/pasemi_defconfig index f4deb0b78cf0..840a2c2d0430 100644 --- a/arch/powerpc/configs/pasemi_defconfig +++ b/arch/powerpc/configs/pasemi_defconfig @@ -1,5 +1,4 @@  CONFIG_PPC64=y -CONFIG_POWER4_ONLY=y  CONFIG_ALTIVEC=y  # CONFIG_VIRT_CPU_ACCOUNTING is not set  CONFIG_SMP=y diff --git a/arch/powerpc/configs/ps3_defconfig b/arch/powerpc/configs/ps3_defconfig index ded867871e97..c2f4b4a86ece 100644 --- a/arch/powerpc/configs/ps3_defconfig +++ b/arch/powerpc/configs/ps3_defconfig @@ -6,7 +6,6 @@ CONFIG_NR_CPUS=2  CONFIG_EXPERIMENTAL=y  CONFIG_SYSVIPC=y  CONFIG_POSIX_MQUEUE=y -CONFIG_SPARSE_IRQ=y  CONFIG_BLK_DEV_INITRD=y  CONFIG_CC_OPTIMIZE_FOR_SIZE=y  CONFIG_EMBEDDED=y @@ -25,7 +24,6 @@ CONFIG_PS3_DISK=y  CONFIG_PS3_ROM=y  CONFIG_PS3_FLASH=y  CONFIG_PS3_VRAM=m -CONFIG_PS3_LPM=m  # CONFIG_PPC_OF_BOOT_TRAMPOLINE is not set  CONFIG_HIGH_RES_TIMERS=y  # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set @@ -53,8 +51,6 @@ CONFIG_IP_PNP_DHCP=y  # CONFIG_INET_DIAG is not set  CONFIG_IPV6=y  CONFIG_BT=m -CONFIG_BT_L2CAP=y -CONFIG_BT_SCO=y  CONFIG_BT_RFCOMM=m  CONFIG_BT_RFCOMM_TTY=y  CONFIG_BT_BNEP=m @@ -63,7 +59,6 @@ CONFIG_BT_BNEP_PROTO_FILTER=y  CONFIG_BT_HIDP=m  CONFIG_BT_HCIBTUSB=m  CONFIG_CFG80211=m -# CONFIG_WIRELESS_EXT_SYSFS is not set  CONFIG_MAC80211=m  CONFIG_MAC80211_RC_PID=y  # CONFIG_MAC80211_RC_MINSTREL is not set @@ -181,7 +176,6 @@ CONFIG_DEBUG_INFO=y  CONFIG_DEBUG_WRITECOUNT=y  CONFIG_DEBUG_MEMORY_INIT=y  CONFIG_DEBUG_LIST=y -CONFIG_SYSCTL_SYSCALL_CHECK=y  # CONFIG_FTRACE is not set  CONFIG_DEBUG_STACKOVERFLOW=y  CONFIG_CRYPTO_CCM=m diff --git a/arch/powerpc/include/asm/asm-compat.h b/arch/powerpc/include/asm/asm-compat.h index decad950f11a..5d7fbe1950f9 100644 --- a/arch/powerpc/include/asm/asm-compat.h +++ b/arch/powerpc/include/asm/asm-compat.h @@ -29,18 +29,9 @@  #define PPC_LLARX(t, a, b, eh)	PPC_LDARX(t, a, b, eh)  #define PPC_STLCX	stringify_in_c(stdcx.)  #define PPC_CNTLZL	stringify_in_c(cntlzd) +#define PPC_MTOCRF(FXM, RS) MTOCRF((FXM), (RS))  #define PPC_LR_STKOFF	16  #define PPC_MIN_STKFRM	112 - -/* Move to CR, single-entry optimized version. Only available - * on POWER4 and later. - */ -#ifdef CONFIG_POWER4_ONLY -#define PPC_MTOCRF	stringify_in_c(mtocrf) -#else -#define PPC_MTOCRF	stringify_in_c(mtcrf) -#endif -  #else /* 32-bit */  /* operations for longs and pointers */ diff --git a/arch/powerpc/include/asm/cputhreads.h b/arch/powerpc/include/asm/cputhreads.h index ce516e5eb0d3..ac3eedb9b74a 100644 --- a/arch/powerpc/include/asm/cputhreads.h +++ b/arch/powerpc/include/asm/cputhreads.h @@ -9,7 +9,7 @@   * Note: This implementation is limited to a power of 2 number of   * threads per core and the same number for each core in the system   * (though it would work if some processors had less threads as long - * as the CPU numbers are still allocated, just not brought offline). + * as the CPU numbers are still allocated, just not brought online).   *   * However, the API allows for a different implementation in the future   * if needed, as long as you only use the functions and not the variables diff --git a/arch/powerpc/include/asm/hvcall.h b/arch/powerpc/include/asm/hvcall.h index 1c324ff55ea8..612252388190 100644 --- a/arch/powerpc/include/asm/hvcall.h +++ b/arch/powerpc/include/asm/hvcall.h @@ -77,8 +77,27 @@  #define H_MR_CONDITION  -43  #define H_NOT_ENOUGH_RESOURCES -44  #define H_R_STATE       -45 -#define H_RESCINDEND    -46 -#define H_MULTI_THREADS_ACTIVE -9005 +#define H_RESCINDED     -46 +#define H_P2		-55 +#define H_P3		-56 +#define H_P4		-57 +#define H_P5		-58 +#define H_P6		-59 +#define H_P7		-60 +#define H_P8		-61 +#define H_P9		-62 +#define H_TOO_BIG	-64 +#define H_OVERLAP	-68 +#define H_INTERRUPT	-69 +#define H_BAD_DATA	-70 +#define H_NOT_ACTIVE	-71 +#define H_SG_LIST	-72 +#define H_OP_MODE	-73 +#define H_COP_HW	-74 +#define H_UNSUPPORTED_FLAG_START	-256 +#define H_UNSUPPORTED_FLAG_END		-511 +#define H_MULTI_THREADS_ACTIVE	-9005 +#define H_OUTSTANDING_COP_OPS	-9006  /* Long Busy is a condition that can be returned by the firmware @@ -240,6 +259,8 @@  #define H_GET_MPP		0x2D4  #define H_HOME_NODE_ASSOCIATIVITY 0x2EC  #define H_BEST_ENERGY		0x2F4 +#define H_RANDOM		0x300 +#define H_COP			0x304  #define H_GET_MPP_X		0x314  #define MAX_HCALL_OPCODE	H_GET_MPP_X diff --git a/arch/powerpc/include/asm/lppaca.h b/arch/powerpc/include/asm/lppaca.h index a76254af0aaa..531fe0c3108f 100644 --- a/arch/powerpc/include/asm/lppaca.h +++ b/arch/powerpc/include/asm/lppaca.h @@ -20,18 +20,16 @@  #define _ASM_POWERPC_LPPACA_H  #ifdef __KERNEL__ -/* These definitions relate to hypervisors that only exist when using +/* + * These definitions relate to hypervisors that only exist when using   * a server type processor   */  #ifdef CONFIG_PPC_BOOK3S -//============================================================================= -// -//	This control block contains the data that is shared between the -//	hypervisor (PLIC) and the OS. -// -// -//---------------------------------------------------------------------------- +/* + * This control block contains the data that is shared between the + * hypervisor and the OS. + */  #include <linux/cache.h>  #include <linux/threads.h>  #include <asm/types.h> @@ -43,123 +41,65 @@   */  #define NR_LPPACAS	1 - -/* The Hypervisor barfs if the lppaca crosses a page boundary.  A 1k - * alignment is sufficient to prevent this */ +/* + * The Hypervisor barfs if the lppaca crosses a page boundary.  A 1k + * alignment is sufficient to prevent this + */  struct lppaca { -//============================================================================= -// CACHE_LINE_1 0x0000 - 0x007F Contains read-only data -// NOTE: The xDynXyz fields are fields that will be dynamically changed by -// PLIC when preparing to bring a processor online or when dispatching a -// virtual processor! -//============================================================================= -	u32	desc;			// Eye catcher 0xD397D781	x00-x03 -	u16	size;			// Size of this struct		x04-x05 -	u16	reserved1;		// Reserved			x06-x07 -	u16	reserved2:14;		// Reserved			x08-x09 -	u8	shared_proc:1;		// Shared processor indicator	... -	u8	secondary_thread:1;	// Secondary thread indicator	... -	volatile u8 dyn_proc_status:8;	// Dynamic Status of this proc	x0A-x0A -	u8	secondary_thread_count;	// Secondary thread count	x0B-x0B -	volatile u16 dyn_hv_phys_proc_index;// Dynamic HV Physical Proc Index0C-x0D -	volatile u16 dyn_hv_log_proc_index;// Dynamic HV Logical Proc Indexx0E-x0F -	u32	decr_val;   		// Value for Decr programming 	x10-x13 -	u32	pmc_val;       		// Value for PMC regs         	x14-x17 -	volatile u32 dyn_hw_node_id;	// Dynamic Hardware Node id	x18-x1B -	volatile u32 dyn_hw_proc_id;	// Dynamic Hardware Proc Id	x1C-x1F -	volatile u32 dyn_pir;		// Dynamic ProcIdReg value	x20-x23 -	u32	dsei_data;           	// DSEI data                  	x24-x27 -	u64	sprg3;               	// SPRG3 value                	x28-x2F -	u8	reserved3[40];		// Reserved			x30-x57 -	volatile u8 vphn_assoc_counts[8]; // Virtual processor home node -					// associativity change counters x58-x5F -	u8	reserved4[32];		// Reserved			x60-x7F - -//============================================================================= -// CACHE_LINE_2 0x0080 - 0x00FF Contains local read-write data -//============================================================================= -	// This Dword contains a byte for each type of interrupt that can occur. -	// The IPI is a count while the others are just a binary 1 or 0. -	union { -		u64	any_int; -		struct { -			u16	reserved;	// Reserved - cleared by #mpasmbl -			u8	xirr_int;	// Indicates xXirrValue is valid or Immed IO -			u8	ipi_cnt;	// IPI Count -			u8	decr_int;	// DECR interrupt occurred -			u8	pdc_int;	// PDC interrupt occurred -			u8	quantum_int;	// Interrupt quantum reached -			u8	old_plic_deferred_ext_int;	// Old PLIC has a deferred XIRR pending -		} fields; -	} int_dword; - -	// Whenever any fields in this Dword are set then PLIC will defer the -	// processing of external interrupts.  Note that PLIC will store the -	// XIRR directly into the xXirrValue field so that another XIRR will -	// not be presented until this one clears.  The layout of the low -	// 4-bytes of this Dword is up to SLIC - PLIC just checks whether the -	// entire Dword is zero or not.  A non-zero value in the low order -	// 2-bytes will result in SLIC being granted the highest thread -	// priority upon return.  A 0 will return to SLIC as medium priority. -	u64	plic_defer_ints_area;	// Entire Dword +	/* cacheline 1 contains read-only data */ -	// Used to pass the real SRR0/1 from PLIC to SLIC as well as to -	// pass the target SRR0/1 from SLIC to PLIC on a SetAsrAndRfid. -	u64	saved_srr0;		// Saved SRR0                   x10-x17 -	u64	saved_srr1;		// Saved SRR1                   x18-x1F +	u32	desc;			/* Eye catcher 0xD397D781 */ +	u16	size;			/* Size of this struct */ +	u16	reserved1; +	u16	reserved2:14; +	u8	shared_proc:1;		/* Shared processor indicator */ +	u8	secondary_thread:1;	/* Secondary thread indicator */ +	u8	reserved3[14]; +	volatile u32 dyn_hw_node_id;	/* Dynamic hardware node id */ +	volatile u32 dyn_hw_proc_id;	/* Dynamic hardware proc id */ +	u8	reserved4[56]; +	volatile u8 vphn_assoc_counts[8]; /* Virtual processor home node */ +					  /* associativity change counters */ +	u8	reserved5[32]; -	// Used to pass parms from the OS to PLIC for SetAsrAndRfid -	u64	saved_gpr3;		// Saved GPR3                   x20-x27 -	u64	saved_gpr4;		// Saved GPR4                   x28-x2F -	union { -		u64	saved_gpr5;	/* Saved GPR5               x30-x37 */ -		struct { -			u8	cede_latency_hint;  /*			x30 */ -			u8	reserved[7];        /*		    x31-x36 */ -		} fields; -	} gpr5_dword; +	/* cacheline 2 contains local read-write data */ +	u8	reserved6[48]; +	u8	cede_latency_hint; +	u8	reserved7[7]; +	u8	dtl_enable_mask;	/* Dispatch Trace Log mask */ +	u8	donate_dedicated_cpu;	/* Donate dedicated CPU cycles */ +	u8	fpregs_in_use; +	u8	pmcregs_in_use; +	u8	reserved8[28]; +	u64	wait_state_cycles;	/* Wait cycles for this proc */ +	u8	reserved9[28]; +	u16	slb_count;		/* # of SLBs to maintain */ +	u8	idle;			/* Indicate OS is idle */ +	u8	vmxregs_in_use; -	u8	dtl_enable_mask;	// Dispatch Trace Log mask	x38-x38 -	u8	donate_dedicated_cpu;	// Donate dedicated CPU cycles  x39-x39 -	u8	fpregs_in_use;		// FP regs in use               x3A-x3A -	u8	pmcregs_in_use;		// PMC regs in use              x3B-x3B -	volatile u32 saved_decr;	// Saved Decr Value             x3C-x3F -	volatile u64 emulated_time_base;// Emulated TB for this thread  x40-x47 -	volatile u64 cur_plic_latency;	// Unaccounted PLIC latency     x48-x4F -	u64	tot_plic_latency;	// Accumulated PLIC latency     x50-x57 -	u64	wait_state_cycles;	// Wait cycles for this proc    x58-x5F -	u64	end_of_quantum;		// TB at end of quantum         x60-x67 -	u64	pdc_saved_sprg1;	// Saved SPRG1 for PMC int      x68-x6F -	u64	pdc_saved_srr0;		// Saved SRR0 for PMC int       x70-x77 -	volatile u32 virtual_decr;	// Virtual DECR for shared procsx78-x7B -	u16	slb_count;		// # of SLBs to maintain        x7C-x7D -	u8	idle;			// Indicate OS is idle          x7E -	u8	vmxregs_in_use;		// VMX registers in use         x7F +	/* cacheline 3 is shared with other processors */ +	/* +	 * This is the yield_count.  An "odd" value (low bit on) means that +	 * the processor is yielded (either because of an OS yield or a +	 * hypervisor preempt).  An even value implies that the processor is +	 * currently executing. +	 * NOTE: This value will ALWAYS be zero for dedicated processors and +	 * will NEVER be zero for shared processors (ie, initialized to a 1). +	 */ +	volatile u32 yield_count; +	volatile u32 dispersion_count;	/* dispatch changed physical cpu */ +	volatile u64 cmo_faults;	/* CMO page fault count */ +	volatile u64 cmo_fault_time;	/* CMO page fault time */ +	u8	reserved10[104]; -//============================================================================= -// CACHE_LINE_3 0x0100 - 0x017F: This line is shared with other processors -//============================================================================= -	// This is the yield_count.  An "odd" value (low bit on) means that -	// the processor is yielded (either because of an OS yield or a PLIC -	// preempt).  An even value implies that the processor is currently -	// executing. -	// NOTE: This value will ALWAYS be zero for dedicated processors and -	// will NEVER be zero for shared processors (ie, initialized to a 1). -	volatile u32 yield_count;	// PLIC increments each dispatchx00-x03 -	volatile u32 dispersion_count;	// dispatch changed phys cpu    x04-x07 -	volatile u64 cmo_faults;	// CMO page fault count         x08-x0F -	volatile u64 cmo_fault_time;	// CMO page fault time          x10-x17 -	u8	reserved7[104];		// Reserved                     x18-x7F +	/* cacheline 4-5 */ -//============================================================================= -// CACHE_LINE_4-5 0x0180 - 0x027F Contains PMC interrupt data -//============================================================================= -	u32	page_ins;		// CMO Hint - # page ins by OS  x00-x03 -	u8	reserved8[148];		// Reserved                     x04-x97 -	volatile u64 dtl_idx;		// Dispatch Trace Log head idx	x98-x9F -	u8	reserved9[96];		// Reserved                     xA0-xFF +	u32	page_ins;		/* CMO Hint - # page ins by OS */ +	u8	reserved11[148]; +	volatile u64 dtl_idx;		/* Dispatch Trace Log head index */ +	u8	reserved12[96];  } __attribute__((__aligned__(0x400)));  extern struct lppaca lppaca[]; @@ -172,13 +112,13 @@ extern struct lppaca lppaca[];   * ESID is stored in the lower 64bits, then the VSID.   */  struct slb_shadow { -	u32	persistent;		// Number of persistent SLBs	x00-x03 -	u32	buffer_length;		// Total shadow buffer length	x04-x07 -	u64	reserved;		// Alignment			x08-x0f +	u32	persistent;		/* Number of persistent SLBs */ +	u32	buffer_length;		/* Total shadow buffer length */ +	u64	reserved;  	struct	{  		u64     esid;  		u64	vsid; -	} save_area[SLB_NUM_BOLTED];	//				x10-x40 +	} save_area[SLB_NUM_BOLTED];  } ____cacheline_aligned;  extern struct slb_shadow slb_shadow[]; diff --git a/arch/powerpc/include/asm/lv1call.h b/arch/powerpc/include/asm/lv1call.h index 233f9ecae761..f5117674bf92 100644 --- a/arch/powerpc/include/asm/lv1call.h +++ b/arch/powerpc/include/asm/lv1call.h @@ -265,8 +265,8 @@ LV1_CALL(get_spe_irq_outlet,                            2, 1,  78 )  LV1_CALL(set_spe_privilege_state_area_1_register,       3, 0,  79 )  LV1_CALL(create_repository_node,                        6, 0,  90 )  LV1_CALL(read_repository_node,                          5, 2,  91 ) -LV1_CALL(modify_repository_node_value,                  6, 0,  92 ) -LV1_CALL(remove_repository_node,                        4, 0,  93 ) +LV1_CALL(write_repository_node,                         6, 0,  92 ) +LV1_CALL(delete_repository_node,                        4, 0,  93 )  LV1_CALL(read_htab_entries,                             2, 5,  95 )  LV1_CALL(set_dabr,                                      2, 0,  96 )  LV1_CALL(get_total_execution_time,                      2, 1, 103 ) diff --git a/arch/powerpc/include/asm/pSeries_reconfig.h b/arch/powerpc/include/asm/pSeries_reconfig.h index 23cd6cc30bcf..c07edfe98b98 100644 --- a/arch/powerpc/include/asm/pSeries_reconfig.h +++ b/arch/powerpc/include/asm/pSeries_reconfig.h @@ -13,6 +13,18 @@  #define PSERIES_RECONFIG_REMOVE		0x0002  #define PSERIES_DRCONF_MEM_ADD		0x0003  #define PSERIES_DRCONF_MEM_REMOVE	0x0004 +#define PSERIES_UPDATE_PROPERTY		0x0005 + +/** + * pSeries_reconfig_notify - Notifier value structure for OFDT property updates + * + * @node: Device tree node which owns the property being updated + * @property: Updated property + */ +struct pSeries_reconfig_prop_update { +	struct device_node *node; +	struct property *property; +};  #ifdef CONFIG_PPC_PSERIES  extern int pSeries_reconfig_notifier_register(struct notifier_block *); diff --git a/arch/powerpc/include/asm/ppc_asm.h b/arch/powerpc/include/asm/ppc_asm.h index 50f73aa2ba21..15444204a3a1 100644 --- a/arch/powerpc/include/asm/ppc_asm.h +++ b/arch/powerpc/include/asm/ppc_asm.h @@ -369,7 +369,15 @@ BEGIN_FTR_SECTION			\  END_FTR_SECTION_IFCLR(CPU_FTR_601)  #endif -	 +#ifdef CONFIG_PPC64 +#define MTOCRF(FXM, RS)			\ +	BEGIN_FTR_SECTION_NESTED(848);	\ +	mtcrf	(FXM), (RS);		\ +	FTR_SECTION_ELSE_NESTED(848);	\ +	mtocrf (FXM), (RS);		\ +	ALT_FTR_SECTION_END_NESTED_IFCLR(CPU_FTR_NOEXECUTE, 848) +#endif +  /*   * This instruction is not implemented on the PPC 603 or 601; however, on   * the 403GCX and 405GP tlbia IS defined and tlbie is not. diff --git a/arch/powerpc/include/asm/ptrace.h b/arch/powerpc/include/asm/ptrace.h index 84cc7840cd18..9c21ed42aba6 100644 --- a/arch/powerpc/include/asm/ptrace.h +++ b/arch/powerpc/include/asm/ptrace.h @@ -354,12 +354,6 @@ static inline unsigned long regs_get_kernel_stack_nth(struct pt_regs *regs,  #define PTRACE_GETREGS64	  22  #define PTRACE_SETREGS64	  23 -/* (old) PTRACE requests with inverted arguments */ -#define PPC_PTRACE_GETREGS	0x99	/* Get GPRs 0 - 31 */ -#define PPC_PTRACE_SETREGS	0x98	/* Set GPRs 0 - 31 */ -#define PPC_PTRACE_GETFPREGS	0x97	/* Get FPRs 0 - 31 */ -#define PPC_PTRACE_SETFPREGS	0x96	/* Set FPRs 0 - 31 */ -  /* Calls to trace a 64bit program from a 32bit program */  #define PPC_PTRACE_PEEKTEXT_3264 0x95  #define PPC_PTRACE_PEEKDATA_3264 0x94 diff --git a/arch/powerpc/include/asm/switch_to.h b/arch/powerpc/include/asm/switch_to.h index caf82d0a00de..1a6320290d26 100644 --- a/arch/powerpc/include/asm/switch_to.h +++ b/arch/powerpc/include/asm/switch_to.h @@ -21,7 +21,6 @@ extern void disable_kernel_fp(void);  extern void enable_kernel_fp(void);  extern void flush_fp_to_thread(struct task_struct *);  extern void enable_kernel_altivec(void); -extern void giveup_altivec(struct task_struct *);  extern void load_up_altivec(struct task_struct *);  extern int emulate_altivec(struct pt_regs *);  extern void __giveup_vsx(struct task_struct *); @@ -40,10 +39,15 @@ static inline void discard_lazy_cpu_state(void)  #ifdef CONFIG_ALTIVEC  extern void flush_altivec_to_thread(struct task_struct *); +extern void giveup_altivec(struct task_struct *); +extern void giveup_altivec_notask(void);  #else  static inline void flush_altivec_to_thread(struct task_struct *t)  {  } +static inline void giveup_altivec(struct task_struct *t) +{ +}  #endif  #ifdef CONFIG_VSX diff --git a/arch/powerpc/include/asm/thread_info.h b/arch/powerpc/include/asm/thread_info.h index 1a1bb00f061a..a556ccc16b58 100644 --- a/arch/powerpc/include/asm/thread_info.h +++ b/arch/powerpc/include/asm/thread_info.h @@ -113,7 +113,6 @@ static inline struct thread_info *current_thread_info(void)  #define _TIF_NOERROR		(1<<TIF_NOERROR)  #define _TIF_NOTIFY_RESUME	(1<<TIF_NOTIFY_RESUME)  #define _TIF_SYSCALL_TRACEPOINT	(1<<TIF_SYSCALL_TRACEPOINT) -#define _TIF_RUNLATCH		(1<<TIF_RUNLATCH)  #define _TIF_SYSCALL_T_OR_A	(_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \  				 _TIF_SECCOMP | _TIF_SYSCALL_TRACEPOINT) diff --git a/arch/powerpc/include/asm/vio.h b/arch/powerpc/include/asm/vio.h index 6bfd5ffe1d4f..b19adf751dd9 100644 --- a/arch/powerpc/include/asm/vio.h +++ b/arch/powerpc/include/asm/vio.h @@ -46,6 +46,48 @@  struct iommu_table; +/* + * Platform Facilities Option (PFO)-specific data + */ + +/* Starting unit address for PFO devices on the VIO BUS */ +#define VIO_BASE_PFO_UA	0x50000000 + +/** + * vio_pfo_op - PFO operation parameters + * + * @flags: h_call subfunctions and modifiers + * @in: Input data block logical real address + * @inlen: If non-negative, the length of the input data block.  If negative, + *	the length of the input data descriptor list in bytes. + * @out: Output data block logical real address + * @outlen: If non-negative, the length of the input data block.  If negative, + *	the length of the input data descriptor list in bytes. + * @csbcpb: Logical real address of the 4k naturally-aligned storage block + *	containing the CSB & optional FC field specific CPB + * @timeout: # of milliseconds to retry h_call, 0 for no timeout. + * @hcall_err: pointer to return the h_call return value, else NULL + */ +struct vio_pfo_op { +	u64 flags; +	s64 in; +	s64 inlen; +	s64 out; +	s64 outlen; +	u64 csbcpb; +	void *done; +	unsigned long handle; +	unsigned int timeout; +	long hcall_err; +}; + +/* End PFO specific data */ + +enum vio_dev_family { +	VDEVICE,	/* The OF node is a child of /vdevice */ +	PFO,		/* The OF node is a child of /ibm,platform-facilities */ +}; +  /**   * vio_dev - This structure is used to describe virtual I/O devices.   * @@ -58,6 +100,7 @@ struct vio_dev {  	const char *name;  	const char *type;  	uint32_t unit_address; +	uint32_t resource_id;  	unsigned int irq;  	struct {  		size_t desired; @@ -65,6 +108,7 @@ struct vio_dev {  		size_t allocated;  		atomic_t allocs_failed;  	} cmo; +	enum vio_dev_family family;  	struct device dev;  }; @@ -95,6 +139,8 @@ extern void vio_cmo_set_dev_desired(struct vio_dev *viodev, size_t desired);  extern void __devinit vio_unregister_device(struct vio_dev *dev); +extern int vio_h_cop_sync(struct vio_dev *vdev, struct vio_pfo_op *op); +  struct device_node;  extern struct vio_dev *vio_register_device_node( diff --git a/arch/powerpc/kernel/asm-offsets.c b/arch/powerpc/kernel/asm-offsets.c index 34b8afe94a50..4554dc2fe857 100644 --- a/arch/powerpc/kernel/asm-offsets.c +++ b/arch/powerpc/kernel/asm-offsets.c @@ -188,10 +188,6 @@ int main(void)  	DEFINE(SLBSHADOW_STACKESID,  	       offsetof(struct slb_shadow, save_area[SLB_NUM_BOLTED - 1].esid));  	DEFINE(SLBSHADOW_SAVEAREA, offsetof(struct slb_shadow, save_area)); -	DEFINE(LPPACASRR0, offsetof(struct lppaca, saved_srr0)); -	DEFINE(LPPACASRR1, offsetof(struct lppaca, saved_srr1)); -	DEFINE(LPPACAANYINT, offsetof(struct lppaca, int_dword.any_int)); -	DEFINE(LPPACADECRINT, offsetof(struct lppaca, int_dword.fields.decr_int));  	DEFINE(LPPACA_PMCINUSE, offsetof(struct lppaca, pmcregs_in_use));  	DEFINE(LPPACA_DTLIDX, offsetof(struct lppaca, dtl_idx));  	DEFINE(LPPACA_YIELDCOUNT, offsetof(struct lppaca, yield_count)); diff --git a/arch/powerpc/kernel/entry_64.S b/arch/powerpc/kernel/entry_64.S index ef2074c3e906..ed1718feb9d9 100644 --- a/arch/powerpc/kernel/entry_64.S +++ b/arch/powerpc/kernel/entry_64.S @@ -63,15 +63,9 @@ system_call_common:  	std	r0,GPR0(r1)  	std	r10,GPR1(r1)  	ACCOUNT_CPU_USER_ENTRY(r10, r11) -	/* -	 * This "crclr so" clears CR0.SO, which is the error indication on -	 * return from this system call.  There must be no cmp instruction -	 * between it and the "mfcr r9" below, otherwise if XER.SO is set, -	 * CR0.SO will get set, causing all system calls to appear to fail. -	 */ -	crclr	so  	std	r2,GPR2(r1)  	std	r3,GPR3(r1) +	mfcr	r2  	std	r4,GPR4(r1)  	std	r5,GPR5(r1)  	std	r6,GPR6(r1) @@ -82,18 +76,20 @@ system_call_common:  	std	r11,GPR10(r1)  	std	r11,GPR11(r1)  	std	r11,GPR12(r1) +	std	r11,_XER(r1) +	std	r11,_CTR(r1)  	std	r9,GPR13(r1) -	mfcr	r9  	mflr	r10 +	/* +	 * This clears CR0.SO (bit 28), which is the error indication on +	 * return from this system call. +	 */ +	rldimi	r2,r11,28,(63-28)  	li	r11,0xc01 -	std	r9,_CCR(r1)  	std	r10,_LINK(r1)  	std	r11,_TRAP(r1) -	mfxer	r9 -	mfctr	r10 -	std	r9,_XER(r1) -	std	r10,_CTR(r1)  	std	r3,ORIG_GPR3(r1) +	std	r2,_CCR(r1)  	ld	r2,PACATOC(r13)  	addi	r9,r1,STACK_FRAME_OVERHEAD  	ld	r11,exception_marker@toc(r2) @@ -154,7 +150,7 @@ END_FW_FTR_SECTION_IFSET(FW_FEATURE_SPLPAR)  	ld	r10,TI_FLAGS(r11)  	andi.	r11,r10,_TIF_SYSCALL_T_OR_A  	bne-	syscall_dotrace -syscall_dotrace_cont: +.Lsyscall_dotrace_cont:  	cmpldi	0,r0,NR_syscalls  	bge-	syscall_enosys @@ -211,7 +207,7 @@ syscall_exit:  	cmpld	r3,r11  	ld	r5,_CCR(r1)  	bge-	syscall_error -syscall_error_cont: +.Lsyscall_error_cont:  	ld	r7,_NIP(r1)  BEGIN_FTR_SECTION  	stdcx.	r0,0,r1			/* to clear the reservation */ @@ -246,7 +242,7 @@ syscall_error:  	oris	r5,r5,0x1000	/* Set SO bit in CR */  	neg	r3,r3  	std	r5,_CCR(r1) -	b	syscall_error_cont +	b	.Lsyscall_error_cont  /* Traced system call support */  syscall_dotrace: @@ -268,7 +264,7 @@ syscall_dotrace:  	addi	r9,r1,STACK_FRAME_OVERHEAD  	clrrdi	r10,r1,THREAD_SHIFT  	ld	r10,TI_FLAGS(r10) -	b	syscall_dotrace_cont +	b	.Lsyscall_dotrace_cont  syscall_enosys:  	li	r3,-ENOSYS diff --git a/arch/powerpc/kernel/exceptions-64s.S b/arch/powerpc/kernel/exceptions-64s.S index 8f880bc77c56..f7bed44ee165 100644 --- a/arch/powerpc/kernel/exceptions-64s.S +++ b/arch/powerpc/kernel/exceptions-64s.S @@ -94,12 +94,10 @@ machine_check_pSeries_1:  data_access_pSeries:  	HMT_MEDIUM  	SET_SCRATCH0(r13) -#ifndef CONFIG_POWER4_ONLY  BEGIN_FTR_SECTION  	b	data_access_check_stab  data_access_not_stab:  END_MMU_FTR_SECTION_IFCLR(MMU_FTR_SLB) -#endif  	EXCEPTION_PROLOG_PSERIES(PACA_EXGEN, data_access_common, EXC_STD,  				 KVMTEST, 0x300) @@ -301,7 +299,6 @@ machine_check_fwnmi:  				 EXC_STD, KVMTEST, 0x200)  	KVM_HANDLER_SKIP(PACA_EXMC, EXC_STD, 0x200) -#ifndef CONFIG_POWER4_ONLY  	/* moved from 0x300 */  data_access_check_stab:  	GET_PACA(r13) @@ -328,7 +325,6 @@ do_stab_bolted_pSeries:  	GET_SCRATCH0(r10)  	std	r10,PACA_EXSLB+EX_R13(r13)  	EXCEPTION_PROLOG_PSERIES_1(.do_stab_bolted, EXC_STD) -#endif /* CONFIG_POWER4_ONLY */  	KVM_HANDLER_SKIP(PACA_EXGEN, EXC_STD, 0x300)  	KVM_HANDLER_SKIP(PACA_EXSLB, EXC_STD, 0x380) diff --git a/arch/powerpc/kernel/head_44x.S b/arch/powerpc/kernel/head_44x.S index 7dd2981bcc50..22d608e8bb7d 100644 --- a/arch/powerpc/kernel/head_44x.S +++ b/arch/powerpc/kernel/head_44x.S @@ -778,14 +778,6 @@ _GLOBAL(__fixup_440A_mcheck)  	blr  /* - * extern void giveup_altivec(struct task_struct *prev) - * - * The 44x core does not have an AltiVec unit. - */ -_GLOBAL(giveup_altivec) -	blr - -/*   * extern void giveup_fpu(struct task_struct *prev)   *   * The 44x core does not have an FPU. diff --git a/arch/powerpc/kernel/head_fsl_booke.S b/arch/powerpc/kernel/head_fsl_booke.S index 28e62598d0e8..de80e0f9a2bd 100644 --- a/arch/powerpc/kernel/head_fsl_booke.S +++ b/arch/powerpc/kernel/head_fsl_booke.S @@ -874,14 +874,6 @@ _GLOBAL(__setup_e500mc_ivors)  	sync  	blr -/* - * extern void giveup_altivec(struct task_struct *prev) - * - * The e500 core does not have an AltiVec unit. - */ -_GLOBAL(giveup_altivec) -	blr -  #ifdef CONFIG_SPE  /*   * extern void giveup_spe(struct task_struct *prev) diff --git a/arch/powerpc/kernel/irq.c b/arch/powerpc/kernel/irq.c index 641da9e868ce..7835a5e1ea5f 100644 --- a/arch/powerpc/kernel/irq.c +++ b/arch/powerpc/kernel/irq.c @@ -587,7 +587,7 @@ int irq_choose_cpu(const struct cpumask *mask)  {  	int cpuid; -	if (cpumask_equal(mask, cpu_all_mask)) { +	if (cpumask_equal(mask, cpu_online_mask)) {  		static int irq_rover;  		static DEFINE_RAW_SPINLOCK(irq_rover_lock);  		unsigned long flags; diff --git a/arch/powerpc/kernel/misc_32.S b/arch/powerpc/kernel/misc_32.S index 7cd07b42ca1a..386d57f66f28 100644 --- a/arch/powerpc/kernel/misc_32.S +++ b/arch/powerpc/kernel/misc_32.S @@ -738,8 +738,23 @@ relocate_new_kernel:  	mr      r5, r31  	li	r0, 0 -#elif defined(CONFIG_44x)  && !defined(CONFIG_PPC_47x) +#elif defined(CONFIG_44x) +	/* Save our parameters */ +	mr	r29, r3 +	mr	r30, r4 +	mr	r31, r5 + +#ifdef CONFIG_PPC_47x +	/* Check for 47x cores */ +	mfspr	r3,SPRN_PVR +	srwi	r3,r3,16 +	cmplwi	cr0,r3,PVR_476@h +	beq	setup_map_47x +	cmplwi	cr0,r3,PVR_476_ISS@h +	beq	setup_map_47x +#endif /* CONFIG_PPC_47x */ +	  /*   * Code for setting up 1:1 mapping for PPC440x for KEXEC   * @@ -753,16 +768,15 @@ relocate_new_kernel:   * 5) Invalidate the tmp mapping.   *   * - Based on the kexec support code for FSL BookE - * - Doesn't support 47x yet.   *   */ -	/* Save our parameters */ -	mr	r29, r3 -	mr	r30, r4 -	mr	r31, r5 -	/* Load our MSR_IS and TID to MMUCR for TLB search */ -	mfspr	r3,SPRN_PID +	/*  +	 * Load the PID with kernel PID (0). +	 * Also load our MSR_IS and TID to MMUCR for TLB search. +	 */ +	li	r3, 0 +	mtspr	SPRN_PID, r3  	mfmsr	r4  	andi.	r4,r4,MSR_IS@l  	beq	wmmucr @@ -900,6 +914,179 @@ next_tlb:  	li	r3, 0  	tlbwe	r3, r24, PPC44x_TLB_PAGEID  	sync +	b	ppc44x_map_done + +#ifdef CONFIG_PPC_47x + +	/* 1:1 mapping for 47x */ + +setup_map_47x: + +	/* +	 * Load the kernel pid (0) to PID and also to MMUCR[TID]. +	 * Also set the MSR IS->MMUCR STS +	 */ +	li	r3, 0 +	mtspr	SPRN_PID, r3			/* Set PID */ +	mfmsr	r4				/* Get MSR */ +	andi.	r4, r4, MSR_IS@l		/* TS=1? */ +	beq	1f				/* If not, leave STS=0 */ +	oris	r3, r3, PPC47x_MMUCR_STS@h	/* Set STS=1 */ +1:	mtspr	SPRN_MMUCR, r3			/* Put MMUCR */ +	sync + +	/* Find the entry we are running from */ +	bl	2f +2:	mflr	r23 +	tlbsx	r23, 0, r23 +	tlbre	r24, r23, 0			/* TLB Word 0 */ +	tlbre	r25, r23, 1			/* TLB Word 1 */ +	tlbre	r26, r23, 2			/* TLB Word 2 */ + + +	/* +	 * Invalidates all the tlb entries by writing to 256 RPNs(r4) +	 * of 4k page size in all  4 ways (0-3 in r3). +	 * This would invalidate the entire UTLB including the one we are +	 * running from. However the shadow TLB entries would help us  +	 * to continue the execution, until we flush them (rfi/isync). +	 */ +	addis	r3, 0, 0x8000			/* specify the way */ +	addi	r4, 0, 0			/* TLB Word0 = (EPN=0, VALID = 0) */ +	addi	r5, 0, 0 +	b	clear_utlb_entry + +	/* Align the loop to speed things up. from head_44x.S */ +	.align	6 + +clear_utlb_entry: + +	tlbwe	r4, r3, 0 +	tlbwe	r5, r3, 1 +	tlbwe	r5, r3, 2 +	addis	r3, r3, 0x2000			/* Increment the way */ +	cmpwi	r3, 0 +	bne	clear_utlb_entry +	addis	r3, 0, 0x8000 +	addis	r4, r4, 0x100			/* Increment the EPN */ +	cmpwi	r4, 0 +	bne	clear_utlb_entry + +	/* Create the entries in the other address space */ +	mfmsr	r5 +	rlwinm	r7, r5, 27, 31, 31		/* Get the TS (Bit 26) from MSR */ +	xori	r7, r7, 1			/* r7 = !TS */ + +	insrwi	r24, r7, 1, 21			/* Change the TS in the saved TLB word 0 */ + +	/*  +	 * write out the TLB entries for the tmp mapping +	 * Use way '0' so that we could easily invalidate it later. +	 */ +	lis	r3, 0x8000			/* Way '0' */  + +	tlbwe	r24, r3, 0 +	tlbwe	r25, r3, 1 +	tlbwe	r26, r3, 2 + +	/* Update the msr to the new TS */ +	insrwi	r5, r7, 1, 26 + +	bl	1f +1:	mflr	r6 +	addi	r6, r6, (2f-1b) + +	mtspr	SPRN_SRR0, r6 +	mtspr	SPRN_SRR1, r5 +	rfi + +	/*  +	 * Now we are in the tmp address space. +	 * Create a 1:1 mapping for 0-2GiB in the original TS. +	 */ +2: +	li	r3, 0 +	li	r4, 0				/* TLB Word 0 */ +	li	r5, 0				/* TLB Word 1 */ +	li	r6, 0 +	ori	r6, r6, PPC47x_TLB2_S_RWX	/* TLB word 2 */ + +	li	r8, 0				/* PageIndex */ + +	xori	r7, r7, 1			/* revert back to original TS */ + +write_utlb: +	rotlwi	r5, r8, 28			/* RPN = PageIndex * 256M */ +						/* ERPN = 0 as we don't use memory above 2G */ + +	mr	r4, r5				/* EPN = RPN */ +	ori	r4, r4, (PPC47x_TLB0_VALID | PPC47x_TLB0_256M) +	insrwi	r4, r7, 1, 21			/* Insert the TS to Word 0 */ + +	tlbwe	r4, r3, 0			/* Write out the entries */ +	tlbwe	r5, r3, 1 +	tlbwe	r6, r3, 2 +	addi	r8, r8, 1 +	cmpwi	r8, 8				/* Have we completed ? */ +	bne	write_utlb + +	/* make sure we complete the TLB write up */ +	isync + +	/*  +	 * Prepare to jump to the 1:1 mapping. +	 * 1) Extract page size of the tmp mapping +	 *    DSIZ = TLB_Word0[22:27] +	 * 2) Calculate the physical address of the address +	 *    to jump to. +	 */ +	rlwinm	r10, r24, 0, 22, 27 + +	cmpwi	r10, PPC47x_TLB0_4K +	bne	0f +	li	r10, 0x1000			/* r10 = 4k */ +	bl	1f + +0: +	/* Defaults to 256M */ +	lis	r10, 0x1000 +	 +	bl	1f +1:	mflr	r4 +	addi	r4, r4, (2f-1b)			/* virtual address  of 2f */ + +	subi	r11, r10, 1			/* offsetmask = Pagesize - 1 */ +	not	r10, r11			/* Pagemask = ~(offsetmask) */ + +	and	r5, r25, r10			/* Physical page */ +	and	r6, r4, r11			/* offset within the current page */ + +	or	r5, r5, r6			/* Physical address for 2f */ + +	/* Switch the TS in MSR to the original one */ +	mfmsr	r8 +	insrwi	r8, r7, 1, 26 + +	mtspr	SPRN_SRR1, r8 +	mtspr	SPRN_SRR0, r5 +	rfi + +2: +	/* Invalidate the tmp mapping */ +	lis	r3, 0x8000			/* Way '0' */ + +	clrrwi	r24, r24, 12			/* Clear the valid bit */ +	tlbwe	r24, r3, 0 +	tlbwe	r25, r3, 1 +	tlbwe	r26, r3, 2 + +	/* Make sure we complete the TLB write and flush the shadow TLB */ +	isync + +#endif + +ppc44x_map_done: +  	/* Restore the parameters */  	mr	r3, r29 diff --git a/arch/powerpc/kernel/paca.c b/arch/powerpc/kernel/paca.c index 0bb1f98613ba..fbe1a12dc7f1 100644 --- a/arch/powerpc/kernel/paca.c +++ b/arch/powerpc/kernel/paca.c @@ -36,10 +36,7 @@ struct lppaca lppaca[] = {  	[0 ... (NR_LPPACAS-1)] = {  		.desc = 0xd397d781,	/* "LpPa" */  		.size = sizeof(struct lppaca), -		.dyn_proc_status = 2, -		.decr_val = 0x00ff0000,  		.fpregs_in_use = 1, -		.end_of_quantum = 0xfffffffffffffffful,  		.slb_count = 64,  		.vmxregs_in_use = 0,  		.page_ins = 0, diff --git a/arch/powerpc/kernel/process.c b/arch/powerpc/kernel/process.c index aa05935b6947..7f8ec1de0ace 100644 --- a/arch/powerpc/kernel/process.c +++ b/arch/powerpc/kernel/process.c @@ -124,7 +124,7 @@ void enable_kernel_altivec(void)  	if (current->thread.regs && (current->thread.regs->msr & MSR_VEC))  		giveup_altivec(current);  	else -		giveup_altivec(NULL);	/* just enable AltiVec for kernel - force */ +		giveup_altivec_notask();  #else  	giveup_altivec(last_task_used_altivec);  #endif /* CONFIG_SMP */ diff --git a/arch/powerpc/kernel/prom_init.c b/arch/powerpc/kernel/prom_init.c index 99860273211b..1b488e5305c5 100644 --- a/arch/powerpc/kernel/prom_init.c +++ b/arch/powerpc/kernel/prom_init.c @@ -680,6 +680,9 @@ static void __init early_cmdline_parse(void)  #define OV3_VMX			0x40	/* VMX/Altivec */  #define OV3_DFP			0x20	/* decimal FP */ +/* Option vector 4: IBM PAPR implementation */ +#define OV4_MIN_ENT_CAP		0x01	/* minimum VP entitled capacity */ +  /* Option vector 5: PAPR/OF options supported */  #define OV5_LPAR		0x80	/* logical partitioning supported */  #define OV5_SPLPAR		0x40	/* shared-processor LPAR supported */ @@ -701,6 +704,8 @@ static void __init early_cmdline_parse(void)  #define OV5_XCMO			0x00  #endif  #define OV5_TYPE1_AFFINITY	0x80	/* Type 1 NUMA affinity */ +#define OV5_PFO_HW_RNG		0x80	/* PFO Random Number Generator */ +#define OV5_PFO_HW_ENCR		0x20	/* PFO Encryption Accelerator */  /* Option Vector 6: IBM PAPR hints */  #define OV6_LINUX		0x02	/* Linux is our OS */ @@ -744,11 +749,12 @@ static unsigned char ibm_architecture_vec[] = {  	OV3_FP | OV3_VMX | OV3_DFP,  	/* option vector 4: IBM PAPR implementation */ -	2 - 2,				/* length */ +	3 - 2,				/* length */  	0,				/* don't halt */ +	OV4_MIN_ENT_CAP,		/* minimum VP entitled capacity */  	/* option vector 5: PAPR/OF options */ -	13 - 2,				/* length */ +	18 - 2,				/* length */  	0,				/* don't ignore, don't halt */  	OV5_LPAR | OV5_SPLPAR | OV5_LARGE_PAGES | OV5_DRCONF_MEMORY |  	OV5_DONATE_DEDICATE_CPU | OV5_MSI, @@ -762,8 +768,13 @@ static unsigned char ibm_architecture_vec[] = {  	 * must match by the macro below. Update the definition if  	 * the structure layout changes.  	 */ -#define IBM_ARCH_VEC_NRCORES_OFFSET	100 +#define IBM_ARCH_VEC_NRCORES_OFFSET	101  	W(NR_CPUS),			/* number of cores supported */ +	0, +	0, +	0, +	0, +	OV5_PFO_HW_RNG | OV5_PFO_HW_ENCR,  	/* option vector 6: IBM PAPR hints */  	4 - 2,				/* length */ diff --git a/arch/powerpc/kernel/ptrace.c b/arch/powerpc/kernel/ptrace.c index dd5e214cdf21..c10fc28b9092 100644 --- a/arch/powerpc/kernel/ptrace.c +++ b/arch/powerpc/kernel/ptrace.c @@ -1432,40 +1432,6 @@ static long ppc_del_hwdebug(struct task_struct *child, long addr, long data)  #endif  } -/* - * Here are the old "legacy" powerpc specific getregs/setregs ptrace calls, - * we mark them as obsolete now, they will be removed in a future version - */ -static long arch_ptrace_old(struct task_struct *child, long request, -			    unsigned long addr, unsigned long data) -{ -	void __user *datavp = (void __user *) data; - -	switch (request) { -	case PPC_PTRACE_GETREGS:	/* Get GPRs 0 - 31. */ -		return copy_regset_to_user(child, &user_ppc_native_view, -					   REGSET_GPR, 0, 32 * sizeof(long), -					   datavp); - -	case PPC_PTRACE_SETREGS:	/* Set GPRs 0 - 31. */ -		return copy_regset_from_user(child, &user_ppc_native_view, -					     REGSET_GPR, 0, 32 * sizeof(long), -					     datavp); - -	case PPC_PTRACE_GETFPREGS:	/* Get FPRs 0 - 31. */ -		return copy_regset_to_user(child, &user_ppc_native_view, -					   REGSET_FPR, 0, 32 * sizeof(double), -					   datavp); - -	case PPC_PTRACE_SETFPREGS:	/* Set FPRs 0 - 31. */ -		return copy_regset_from_user(child, &user_ppc_native_view, -					     REGSET_FPR, 0, 32 * sizeof(double), -					     datavp); -	} - -	return -EPERM; -} -  long arch_ptrace(struct task_struct *child, long request,  		 unsigned long addr, unsigned long data)  { @@ -1687,14 +1653,6 @@ long arch_ptrace(struct task_struct *child, long request,  					     datavp);  #endif -	/* Old reverse args ptrace callss */ -	case PPC_PTRACE_GETREGS: /* Get GPRs 0 - 31. */ -	case PPC_PTRACE_SETREGS: /* Set GPRs 0 - 31. */ -	case PPC_PTRACE_GETFPREGS: /* Get FPRs 0 - 31. */ -	case PPC_PTRACE_SETFPREGS: /* Get FPRs 0 - 31. */ -		ret = arch_ptrace_old(child, request, addr, data); -		break; -  	default:  		ret = ptrace_request(child, request, addr, data);  		break; diff --git a/arch/powerpc/kernel/ptrace32.c b/arch/powerpc/kernel/ptrace32.c index 469349d14a97..8c21658719d9 100644 --- a/arch/powerpc/kernel/ptrace32.c +++ b/arch/powerpc/kernel/ptrace32.c @@ -39,30 +39,6 @@   * in exit.c or in signal.c.   */ -/* - * Here are the old "legacy" powerpc specific getregs/setregs ptrace calls, - * we mark them as obsolete now, they will be removed in a future version - */ -static long compat_ptrace_old(struct task_struct *child, long request, -			      long addr, long data) -{ -	switch (request) { -	case PPC_PTRACE_GETREGS:	/* Get GPRs 0 - 31. */ -		return copy_regset_to_user(child, -					   task_user_regset_view(current), 0, -					   0, 32 * sizeof(compat_long_t), -					   compat_ptr(data)); - -	case PPC_PTRACE_SETREGS:	/* Set GPRs 0 - 31. */ -		return copy_regset_from_user(child, -					     task_user_regset_view(current), 0, -					     0, 32 * sizeof(compat_long_t), -					     compat_ptr(data)); -	} - -	return -EPERM; -} -  /* Macros to workout the correct index for the FPR in the thread struct */  #define FPRNUMBER(i) (((i) - PT_FPR0) >> 1)  #define FPRHALF(i) (((i) - PT_FPR0) & 1) @@ -308,8 +284,6 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,  	case PTRACE_SETVSRREGS:  	case PTRACE_GETREGS64:  	case PTRACE_SETREGS64: -	case PPC_PTRACE_GETFPREGS: -	case PPC_PTRACE_SETFPREGS:  	case PTRACE_KILL:  	case PTRACE_SINGLESTEP:  	case PTRACE_DETACH: @@ -322,12 +296,6 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,  		ret = arch_ptrace(child, request, addr, data);  		break; -	/* Old reverse args ptrace callss */ -	case PPC_PTRACE_GETREGS: /* Get GPRs 0 - 31. */ -	case PPC_PTRACE_SETREGS: /* Set GPRs 0 - 31. */ -		ret = compat_ptrace_old(child, request, addr, data); -		break; -  	default:  		ret = compat_ptrace_request(child, request, addr, data);  		break; diff --git a/arch/powerpc/kernel/vector.S b/arch/powerpc/kernel/vector.S index 4d5a3edff49e..e830289d2e48 100644 --- a/arch/powerpc/kernel/vector.S +++ b/arch/powerpc/kernel/vector.S @@ -89,6 +89,16 @@ _GLOBAL(load_up_altivec)  	/* restore registers and return */  	blr +_GLOBAL(giveup_altivec_notask) +	mfmsr	r3 +	andis.	r4,r3,MSR_VEC@h +	bnelr				/* Already enabled? */ +	oris	r3,r3,MSR_VEC@h +	SYNC +	MTMSRD(r3)			/* enable use of VMX now */ +	isync +	blr +  /*   * giveup_altivec(tsk)   * Disable VMX for the task given as the argument, diff --git a/arch/powerpc/kernel/vio.c b/arch/powerpc/kernel/vio.c index a3a99901c8ec..cb87301ccd55 100644 --- a/arch/powerpc/kernel/vio.c +++ b/arch/powerpc/kernel/vio.c @@ -14,7 +14,9 @@   *      2 of the License, or (at your option) any later version.   */ +#include <linux/cpu.h>  #include <linux/types.h> +#include <linux/delay.h>  #include <linux/stat.h>  #include <linux/device.h>  #include <linux/init.h> @@ -709,13 +711,26 @@ static int vio_cmo_bus_probe(struct vio_dev *viodev)  	struct vio_driver *viodrv = to_vio_driver(dev->driver);  	unsigned long flags;  	size_t size; +	bool dma_capable = false; -	/* -	 * Check to see that device has a DMA window and configure -	 * entitlement for the device. -	 */ -	if (of_get_property(viodev->dev.of_node, -	                    "ibm,my-dma-window", NULL)) { +	/* A device requires entitlement if it has a DMA window property */ +	switch (viodev->family) { +	case VDEVICE: +		if (of_get_property(viodev->dev.of_node, +					"ibm,my-dma-window", NULL)) +			dma_capable = true; +		break; +	case PFO: +		dma_capable = false; +		break; +	default: +		dev_warn(dev, "unknown device family: %d\n", viodev->family); +		BUG(); +		break; +	} + +	/* Configure entitlement for the device. */ +	if (dma_capable) {  		/* Check that the driver is CMO enabled and get desired DMA */  		if (!viodrv->get_desired_dma) {  			dev_err(dev, "%s: device driver does not support CMO\n", @@ -1050,6 +1065,94 @@ static void vio_cmo_sysfs_init(void) { }  EXPORT_SYMBOL(vio_cmo_entitlement_update);  EXPORT_SYMBOL(vio_cmo_set_dev_desired); + +/* + * Platform Facilities Option (PFO) support + */ + +/** + * vio_h_cop_sync - Perform a synchronous PFO co-processor operation + * + * @vdev - Pointer to a struct vio_dev for device + * @op - Pointer to a struct vio_pfo_op for the operation parameters + * + * Calls the hypervisor to synchronously perform the PFO operation + * described in @op.  In the case of a busy response from the hypervisor, + * the operation will be re-submitted indefinitely unless a non-zero timeout + * is specified or an error occurs. The timeout places a limit on when to + * stop re-submitting a operation, the total time can be exceeded if an + * operation is in progress. + * + * If op->hcall_ret is not NULL, this will be set to the return from the + * last h_cop_op call or it will be 0 if an error not involving the h_call + * was encountered. + * + * Returns: + *	0 on success, + *	-EINVAL if the h_call fails due to an invalid parameter, + *	-E2BIG if the h_call can not be performed synchronously, + *	-EBUSY if a timeout is specified and has elapsed, + *	-EACCES if the memory area for data/status has been rescinded, or + *	-EPERM if a hardware fault has been indicated + */ +int vio_h_cop_sync(struct vio_dev *vdev, struct vio_pfo_op *op) +{ +	struct device *dev = &vdev->dev; +	unsigned long deadline = 0; +	long hret = 0; +	int ret = 0; + +	if (op->timeout) +		deadline = jiffies + msecs_to_jiffies(op->timeout); + +	while (true) { +		hret = plpar_hcall_norets(H_COP, op->flags, +				vdev->resource_id, +				op->in, op->inlen, op->out, +				op->outlen, op->csbcpb); + +		if (hret == H_SUCCESS || +		    (hret != H_NOT_ENOUGH_RESOURCES && +		     hret != H_BUSY && hret != H_RESOURCE) || +		    (op->timeout && time_after(deadline, jiffies))) +			break; + +		dev_dbg(dev, "%s: hcall ret(%ld), retrying.\n", __func__, hret); +	} + +	switch (hret) { +	case H_SUCCESS: +		ret = 0; +		break; +	case H_OP_MODE: +	case H_TOO_BIG: +		ret = -E2BIG; +		break; +	case H_RESCINDED: +		ret = -EACCES; +		break; +	case H_HARDWARE: +		ret = -EPERM; +		break; +	case H_NOT_ENOUGH_RESOURCES: +	case H_RESOURCE: +	case H_BUSY: +		ret = -EBUSY; +		break; +	default: +		ret = -EINVAL; +		break; +	} + +	if (ret) +		dev_dbg(dev, "%s: Sync h_cop_op failure (ret:%d) (hret:%ld)\n", +				__func__, ret, hret); + +	op->hcall_err = hret; +	return ret; +} +EXPORT_SYMBOL(vio_h_cop_sync); +  static struct iommu_table *vio_build_iommu_table(struct vio_dev *dev)  {  	const unsigned char *dma_window; @@ -1211,35 +1314,87 @@ static void __devinit vio_dev_release(struct device *dev)  struct vio_dev *vio_register_device_node(struct device_node *of_node)  {  	struct vio_dev *viodev; +	struct device_node *parent_node;  	const unsigned int *unit_address; +	const unsigned int *pfo_resid = NULL; +	enum vio_dev_family family; +	const char *of_node_name = of_node->name ? of_node->name : "<unknown>"; -	/* we need the 'device_type' property, in order to match with drivers */ -	if (of_node->type == NULL) { -		printk(KERN_WARNING "%s: node %s missing 'device_type'\n", -				__func__, -				of_node->name ? of_node->name : "<unknown>"); +	/* +	 * Determine if this node is a under the /vdevice node or under the +	 * /ibm,platform-facilities node.  This decides the device's family. +	 */ +	parent_node = of_get_parent(of_node); +	if (parent_node) { +		if (!strcmp(parent_node->full_name, "/ibm,platform-facilities")) +			family = PFO; +		else if (!strcmp(parent_node->full_name, "/vdevice")) +			family = VDEVICE; +		else { +			pr_warn("%s: parent(%s) of %s not recognized.\n", +					__func__, +					parent_node->full_name, +					of_node_name); +			of_node_put(parent_node); +			return NULL; +		} +		of_node_put(parent_node); +	} else { +		pr_warn("%s: could not determine the parent of node %s.\n", +				__func__, of_node_name);  		return NULL;  	} -	unit_address = of_get_property(of_node, "reg", NULL); -	if (unit_address == NULL) { -		printk(KERN_WARNING "%s: node %s missing 'reg'\n", -				__func__, -				of_node->name ? of_node->name : "<unknown>"); -		return NULL; +	if (family == PFO) { +		if (of_get_property(of_node, "interrupt-controller", NULL)) { +			pr_debug("%s: Skipping the interrupt controller %s.\n", +					__func__, of_node_name); +			return NULL; +		}  	}  	/* allocate a vio_dev for this node */  	viodev = kzalloc(sizeof(struct vio_dev), GFP_KERNEL); -	if (viodev == NULL) +	if (viodev == NULL) { +		pr_warn("%s: allocation failure for VIO device.\n", __func__);  		return NULL; +	} + +	/* we need the 'device_type' property, in order to match with drivers */ +	viodev->family = family; +	if (viodev->family == VDEVICE) { +		if (of_node->type != NULL) +			viodev->type = of_node->type; +		else { +			pr_warn("%s: node %s is missing the 'device_type' " +					"property.\n", __func__, of_node_name); +			goto out; +		} + +		unit_address = of_get_property(of_node, "reg", NULL); +		if (unit_address == NULL) { +			pr_warn("%s: node %s missing 'reg'\n", +					__func__, of_node_name); +			goto out; +		} +		dev_set_name(&viodev->dev, "%x", *unit_address); +		viodev->irq = irq_of_parse_and_map(of_node, 0); +		viodev->unit_address = *unit_address; +	} else { +		/* PFO devices need their resource_id for submitting COP_OPs +		 * This is an optional field for devices, but is required when +		 * performing synchronous ops */ +		pfo_resid = of_get_property(of_node, "ibm,resource-id", NULL); +		if (pfo_resid != NULL) +			viodev->resource_id = *pfo_resid; -	viodev->irq = irq_of_parse_and_map(of_node, 0); +		unit_address = NULL; +		dev_set_name(&viodev->dev, "%s", of_node_name); +		viodev->type = of_node_name; +		viodev->irq = 0; +	} -	dev_set_name(&viodev->dev, "%x", *unit_address);  	viodev->name = of_node->name; -	viodev->type = of_node->type; -	viodev->unit_address = *unit_address;  	viodev->dev.of_node = of_node_get(of_node);  	if (firmware_has_feature(FW_FEATURE_CMO)) @@ -1267,16 +1422,51 @@ struct vio_dev *vio_register_device_node(struct device_node *of_node)  	}  	return viodev; + +out:	/* Use this exit point for any return prior to device_register */ +	kfree(viodev); + +	return NULL;  }  EXPORT_SYMBOL(vio_register_device_node); +/* + * vio_bus_scan_for_devices - Scan OF and register each child device + * @root_name - OF node name for the root of the subtree to search. + *		This must be non-NULL + * + * Starting from the root node provide, register the device node for + * each child beneath the root. + */ +static void vio_bus_scan_register_devices(char *root_name) +{ +	struct device_node *node_root, *node_child; + +	if (!root_name) +		return; + +	node_root = of_find_node_by_name(NULL, root_name); +	if (node_root) { + +		/* +		 * Create struct vio_devices for each virtual device in +		 * the device tree. Drivers will associate with them later. +		 */ +		node_child = of_get_next_child(node_root, NULL); +		while (node_child) { +			vio_register_device_node(node_child); +			node_child = of_get_next_child(node_root, node_child); +		} +		of_node_put(node_root); +	} +} +  /**   * vio_bus_init: - Initialize the virtual IO bus   */  static int __init vio_bus_init(void)  {  	int err; -	struct device_node *node_vroot;  	if (firmware_has_feature(FW_FEATURE_CMO))  		vio_cmo_sysfs_init(); @@ -1301,19 +1491,8 @@ static int __init vio_bus_init(void)  	if (firmware_has_feature(FW_FEATURE_CMO))  		vio_cmo_bus_init(); -	node_vroot = of_find_node_by_name(NULL, "vdevice"); -	if (node_vroot) { -		struct device_node *of_node; - -		/* -		 * Create struct vio_devices for each virtual device in -		 * the device tree. Drivers will associate with them later. -		 */ -		for (of_node = node_vroot->child; of_node != NULL; -				of_node = of_node->sibling) -			vio_register_device_node(of_node); -		of_node_put(node_vroot); -	} +	vio_bus_scan_register_devices("vdevice"); +	vio_bus_scan_register_devices("ibm,platform-facilities");  	return 0;  } @@ -1436,12 +1615,28 @@ struct vio_dev *vio_find_node(struct device_node *vnode)  {  	const uint32_t *unit_address;  	char kobj_name[20]; +	struct device_node *vnode_parent; +	const char *dev_type; + +	vnode_parent = of_get_parent(vnode); +	if (!vnode_parent) +		return NULL; + +	dev_type = of_get_property(vnode_parent, "device_type", NULL); +	of_node_put(vnode_parent); +	if (!dev_type) +		return NULL;  	/* construct the kobject name from the device node */ -	unit_address = of_get_property(vnode, "reg", NULL); -	if (!unit_address) +	if (!strcmp(dev_type, "vdevice")) { +		unit_address = of_get_property(vnode, "reg", NULL); +		if (!unit_address) +			return NULL; +		snprintf(kobj_name, sizeof(kobj_name), "%x", *unit_address); +	} else if (!strcmp(dev_type, "ibm,platform-facilities")) +		snprintf(kobj_name, sizeof(kobj_name), "%s", vnode->name); +	else  		return NULL; -	snprintf(kobj_name, sizeof(kobj_name), "%x", *unit_address);  	return vio_find_name(kobj_name);  } diff --git a/arch/powerpc/lib/copyuser_64.S b/arch/powerpc/lib/copyuser_64.S index 773d38f90aaa..d73a59014900 100644 --- a/arch/powerpc/lib/copyuser_64.S +++ b/arch/powerpc/lib/copyuser_64.S @@ -30,7 +30,7 @@ _GLOBAL(__copy_tofrom_user_base)  	dcbt	0,r4  	beq	.Lcopy_page_4K  	andi.	r6,r6,7 -	PPC_MTOCRF	0x01,r5 +	PPC_MTOCRF(0x01,r5)  	blt	cr1,.Lshort_copy  /* Below we want to nop out the bne if we're on a CPU that has the   * CPU_FTR_UNALIGNED_LD_STD bit set and the CPU_FTR_CP_USE_DCBTZ bit @@ -186,7 +186,7 @@ END_FTR_SECTION_IFCLR(CPU_FTR_UNALIGNED_LD_STD)  	blr  .Ldst_unaligned: -	PPC_MTOCRF	0x01,r6		/* put #bytes to 8B bdry into cr7 */ +	PPC_MTOCRF(0x01,r6)		/* put #bytes to 8B bdry into cr7 */  	subf	r5,r6,r5  	li	r7,0  	cmpldi	cr1,r5,16 @@ -201,7 +201,7 @@ END_FTR_SECTION_IFCLR(CPU_FTR_UNALIGNED_LD_STD)  2:	bf	cr7*4+1,3f  37:	lwzx	r0,r7,r4  83:	stwx	r0,r7,r3 -3:	PPC_MTOCRF	0x01,r5 +3:	PPC_MTOCRF(0x01,r5)  	add	r4,r6,r4  	add	r3,r6,r3  	b	.Ldst_aligned diff --git a/arch/powerpc/lib/mem_64.S b/arch/powerpc/lib/mem_64.S index 11ce045e21fd..f4fcb0bc6563 100644 --- a/arch/powerpc/lib/mem_64.S +++ b/arch/powerpc/lib/mem_64.S @@ -19,7 +19,7 @@ _GLOBAL(memset)  	rlwimi	r4,r4,16,0,15  	cmplw	cr1,r5,r0		/* do we get that far? */  	rldimi	r4,r4,32,0 -	PPC_MTOCRF	1,r0 +	PPC_MTOCRF(1,r0)  	mr	r6,r3  	blt	cr1,8f  	beq+	3f			/* if already 8-byte aligned */ @@ -49,7 +49,7 @@ _GLOBAL(memset)  	bdnz	4b  5:	srwi.	r0,r5,3  	clrlwi	r5,r5,29 -	PPC_MTOCRF	1,r0 +	PPC_MTOCRF(1,r0)  	beq	8f  	bf	29,6f  	std	r4,0(r6) @@ -65,7 +65,7 @@ _GLOBAL(memset)  	std	r4,0(r6)  	addi	r6,r6,8  8:	cmpwi	r5,0 -	PPC_MTOCRF	1,r5 +	PPC_MTOCRF(1,r5)  	beqlr+  	bf	29,9f  	stw	r4,0(r6) diff --git a/arch/powerpc/lib/memcpy_64.S b/arch/powerpc/lib/memcpy_64.S index e178922b2c21..82fea3963e15 100644 --- a/arch/powerpc/lib/memcpy_64.S +++ b/arch/powerpc/lib/memcpy_64.S @@ -12,7 +12,7 @@  	.align	7  _GLOBAL(memcpy)  	std	r3,48(r1)	/* save destination pointer for return value */ -	PPC_MTOCRF	0x01,r5 +	PPC_MTOCRF(0x01,r5)  	cmpldi	cr1,r5,16  	neg	r6,r3		# LS 3 bits = # bytes to 8-byte dest bdry  	andi.	r6,r6,7 @@ -154,7 +154,7 @@ END_FTR_SECTION_IFCLR(CPU_FTR_UNALIGNED_LD_STD)  	blr  .Ldst_unaligned: -	PPC_MTOCRF	0x01,r6		# put #bytes to 8B bdry into cr7 +	PPC_MTOCRF(0x01,r6)		# put #bytes to 8B bdry into cr7  	subf	r5,r6,r5  	li	r7,0  	cmpldi	cr1,r5,16 @@ -169,7 +169,7 @@ END_FTR_SECTION_IFCLR(CPU_FTR_UNALIGNED_LD_STD)  2:	bf	cr7*4+1,3f  	lwzx	r0,r7,r4  	stwx	r0,r7,r3 -3:	PPC_MTOCRF	0x01,r5 +3:	PPC_MTOCRF(0x01,r5)  	add	r4,r6,r4  	add	r3,r6,r3  	b	.Ldst_aligned diff --git a/arch/powerpc/platforms/44x/Kconfig b/arch/powerpc/platforms/44x/Kconfig index 2e4e64abfab4..8abf6fb8f410 100644 --- a/arch/powerpc/platforms/44x/Kconfig +++ b/arch/powerpc/platforms/44x/Kconfig @@ -23,6 +23,8 @@ config BLUESTONE  	default n  	select PPC44x_SIMPLE  	select APM821xx +	select PCI_MSI +	select PPC4xx_MSI  	select PPC4xx_PCI_EXPRESS  	select IBM_EMAC_RGMII  	help diff --git a/arch/powerpc/platforms/Kconfig.cputype b/arch/powerpc/platforms/Kconfig.cputype index 9c80fc07384a..61c9550819a2 100644 --- a/arch/powerpc/platforms/Kconfig.cputype +++ b/arch/powerpc/platforms/Kconfig.cputype @@ -78,6 +78,36 @@ config PPC_BOOK3E_64  endchoice +choice +	prompt "CPU selection" +	depends on PPC64 +	default GENERIC_CPU +	help +	  This will create a kernel which is optimised for a particular CPU. +	  The resulting kernel may not run on other CPUs, so use this with care. + +	  If unsure, select Generic. + +config GENERIC_CPU +	bool "Generic" + +config CELL_CPU +	bool "Cell Broadband Engine" + +config POWER4_CPU +	bool "POWER4" + +config POWER5_CPU +	bool "POWER5" + +config POWER6_CPU +	bool "POWER6" + +config POWER7_CPU +	bool "POWER7" + +endchoice +  config PPC_BOOK3S  	def_bool y  	depends on PPC_BOOK3S_32 || PPC_BOOK3S_64 @@ -86,15 +116,6 @@ config PPC_BOOK3E  	def_bool y  	depends on PPC_BOOK3E_64 -config POWER4_ONLY -	bool "Optimize for POWER4" -	depends on PPC64 && PPC_BOOK3S -	default n -	---help--- -	  Cause the compiler to optimize for POWER4/POWER5/PPC970 processors. -	  The resulting binary will not work on POWER3 or RS64 processors -	  when compiled with binutils 2.15 or later. -  config 6xx  	def_bool y  	depends on PPC32 && PPC_BOOK3S diff --git a/arch/powerpc/platforms/powermac/low_i2c.c b/arch/powerpc/platforms/powermac/low_i2c.c index 03685a329d7d..fc536f2971c0 100644 --- a/arch/powerpc/platforms/powermac/low_i2c.c +++ b/arch/powerpc/platforms/powermac/low_i2c.c @@ -1503,6 +1503,7 @@ static int __init pmac_i2c_create_platform_devices(void)  		if (bus->platform_dev == NULL)  			return -ENOMEM;  		bus->platform_dev->dev.platform_data = bus; +		bus->platform_dev->dev.of_node = bus->busnode;  		platform_device_add(bus->platform_dev);  	} diff --git a/arch/powerpc/platforms/ps3/Kconfig b/arch/powerpc/platforms/ps3/Kconfig index 476d9d9b2405..46b7f0232523 100644 --- a/arch/powerpc/platforms/ps3/Kconfig +++ b/arch/powerpc/platforms/ps3/Kconfig @@ -7,7 +7,6 @@ config PPC_PS3  	select USB_OHCI_BIG_ENDIAN_MMIO  	select USB_ARCH_HAS_EHCI  	select USB_EHCI_BIG_ENDIAN_MMIO -	select MEMORY_HOTPLUG  	select PPC_PCI_CHOICE  	help  	  This option enables support for the Sony PS3 game console @@ -74,7 +73,7 @@ config PS3_PS3AV  	help  	  Include support for the PS3 AV Settings driver. -	  This support is required for graphics and sound. In +	  This support is required for PS3 graphics and sound. In  	  general, all users will say Y or M.  config PS3_SYS_MANAGER @@ -85,9 +84,22 @@ config PS3_SYS_MANAGER  	help  	  Include support for the PS3 System Manager. -	  This support is required for system control.  In +	  This support is required for PS3 system control.  In  	  general, all users will say Y or M. +config PS3_REPOSITORY_WRITE +	bool "PS3 Repository write support" if PS3_ADVANCED +	depends on PPC_PS3 +	default n +	help +	  Enables support for writing to the PS3 System Repository. + +	  This support is intended for bootloaders that need to store data +	  in the repository for later boot stages. + +	  If in doubt, say N here and reduce the size of the kernel by a +	  small amount. +  config PS3_STORAGE  	depends on PPC_PS3  	tristate @@ -122,7 +134,7 @@ config PS3_FLASH  	  This support is required to access the PS3 FLASH ROM, which  	  contains the boot loader and some boot options. -	  In general, all users will say Y or M. +	  In general, PS3 OtherOS users will say Y or M.  	  As this driver needs a fixed buffer of 256 KiB of memory, it can  	  be disabled on the kernel command line using "ps3flash=off", to @@ -156,7 +168,7 @@ config PS3GELIC_UDBG  	  via the Ethernet port (UDP port number 18194).  	  This driver uses a trivial implementation and is independent -	  from the main network driver. +	  from the main PS3 gelic network driver.  	  If in doubt, say N here. diff --git a/arch/powerpc/platforms/ps3/mm.c b/arch/powerpc/platforms/ps3/mm.c index de2aea421707..0c9f643d9e2a 100644 --- a/arch/powerpc/platforms/ps3/mm.c +++ b/arch/powerpc/platforms/ps3/mm.c @@ -20,7 +20,6 @@  #include <linux/kernel.h>  #include <linux/export.h> -#include <linux/memory_hotplug.h>  #include <linux/memblock.h>  #include <linux/slab.h> @@ -79,12 +78,14 @@ enum {   * @base: base address   * @size: size in bytes   * @offset: difference between base and rm.size + * @destroy: flag if region should be destroyed upon shutdown   */  struct mem_region {  	u64 base;  	u64 size;  	unsigned long offset; +	int destroy;  };  /** @@ -96,7 +97,7 @@ struct mem_region {   * The HV virtual address space (vas) allows for hotplug memory regions.   * Memory regions can be created and destroyed in the vas at runtime.   * @rm: real mode (bootmem) region - * @r1: hotplug memory region(s) + * @r1: highmem region(s)   *   * ps3 addresses   * virt_addr: a cpu 'translated' effective address @@ -222,10 +223,6 @@ void ps3_mm_vas_destroy(void)  	}  } -/*============================================================================*/ -/* memory hotplug routines                                                    */ -/*============================================================================*/ -  /**   * ps3_mm_region_create - create a memory region in the vas   * @r: pointer to a struct mem_region to accept initialized values @@ -262,6 +259,7 @@ static int ps3_mm_region_create(struct mem_region *r, unsigned long size)  		goto zero_region;  	} +	r->destroy = 1;  	r->offset = r->base - map.rm.size;  	return result; @@ -279,7 +277,14 @@ static void ps3_mm_region_destroy(struct mem_region *r)  {  	int result; +	if (!r->destroy) { +		pr_info("%s:%d: Not destroying high region: %llxh %llxh\n", +			__func__, __LINE__, r->base, r->size); +		return; +	} +  	DBG("%s:%d: r->base = %llxh\n", __func__, __LINE__, r->base); +  	if (r->base) {  		result = lv1_release_memory(r->base);  		BUG_ON(result); @@ -288,50 +293,36 @@ static void ps3_mm_region_destroy(struct mem_region *r)  	}  } -/** - * ps3_mm_add_memory - hot add memory - */ - -static int __init ps3_mm_add_memory(void) +static int ps3_mm_get_repository_highmem(struct mem_region *r)  {  	int result; -	unsigned long start_addr; -	unsigned long start_pfn; -	unsigned long nr_pages; - -	if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) -		return -ENODEV; -	BUG_ON(!mem_init_done); +	/* Assume a single highmem region. */ -	start_addr = map.rm.size; -	start_pfn = start_addr >> PAGE_SHIFT; -	nr_pages = (map.r1.size + PAGE_SIZE - 1) >> PAGE_SHIFT; +	result = ps3_repository_read_highmem_info(0, &r->base, &r->size); -	DBG("%s:%d: start_addr %lxh, start_pfn %lxh, nr_pages %lxh\n", -		__func__, __LINE__, start_addr, start_pfn, nr_pages); - -	result = add_memory(0, start_addr, map.r1.size); +	if (result) +		goto zero_region; -	if (result) { -		pr_err("%s:%d: add_memory failed: (%d)\n", -			__func__, __LINE__, result); -		return result; +	if (!r->base || !r->size) { +		result = -1; +		goto zero_region;  	} -	memblock_add(start_addr, map.r1.size); +	r->offset = r->base - map.rm.size; -	result = online_pages(start_pfn, nr_pages); +	DBG("%s:%d: Found high region in repository: %llxh %llxh\n", +	    __func__, __LINE__, r->base, r->size); -	if (result) -		pr_err("%s:%d: online_pages failed: (%d)\n", -			__func__, __LINE__, result); +	return 0; +zero_region: +	DBG("%s:%d: No high region in repository.\n", __func__, __LINE__); + +	r->size = r->base = r->offset = 0;  	return result;  } -device_initcall(ps3_mm_add_memory); -  /*============================================================================*/  /* dma routines                                                               */  /*============================================================================*/ @@ -1217,13 +1208,23 @@ void __init ps3_mm_init(void)  	BUG_ON(map.rm.base);  	BUG_ON(!map.rm.size); +	/* Check if we got the highmem region from an earlier boot step */ -	/* arrange to do this in ps3_mm_add_memory */ -	ps3_mm_region_create(&map.r1, map.total - map.rm.size); +	if (ps3_mm_get_repository_highmem(&map.r1)) +		ps3_mm_region_create(&map.r1, map.total - map.rm.size);  	/* correct map.total for the real total amount of memory we use */  	map.total = map.rm.size + map.r1.size; +	if (!map.r1.size) { +		DBG("%s:%d: No highmem region found\n", __func__, __LINE__); +	} else { +		DBG("%s:%d: Adding highmem region: %llxh %llxh\n", +			__func__, __LINE__, map.rm.size, +			map.total - map.rm.size); +		memblock_add(map.rm.size, map.total - map.rm.size); +	} +  	DBG(" <- %s:%d\n", __func__, __LINE__);  } diff --git a/arch/powerpc/platforms/ps3/platform.h b/arch/powerpc/platforms/ps3/platform.h index 1a633ed0fe98..d71329a8e325 100644 --- a/arch/powerpc/platforms/ps3/platform.h +++ b/arch/powerpc/platforms/ps3/platform.h @@ -188,6 +188,22 @@ int ps3_repository_read_rm_size(unsigned int ppe_id, u64 *rm_size);  int ps3_repository_read_region_total(u64 *region_total);  int ps3_repository_read_mm_info(u64 *rm_base, u64 *rm_size,  	u64 *region_total); +int ps3_repository_read_highmem_region_count(unsigned int *region_count); +int ps3_repository_read_highmem_base(unsigned int region_index, +	u64 *highmem_base); +int ps3_repository_read_highmem_size(unsigned int region_index, +	u64 *highmem_size); +int ps3_repository_read_highmem_info(unsigned int region_index, +	u64 *highmem_base, u64 *highmem_size); + +int ps3_repository_write_highmem_region_count(unsigned int region_count); +int ps3_repository_write_highmem_base(unsigned int region_index, +	u64 highmem_base); +int ps3_repository_write_highmem_size(unsigned int region_index, +	u64 highmem_size); +int ps3_repository_write_highmem_info(unsigned int region_index, +	u64 highmem_base, u64 highmem_size); +int ps3_repository_delete_highmem_info(unsigned int region_index);  /* repository pme info */ diff --git a/arch/powerpc/platforms/ps3/repository.c b/arch/powerpc/platforms/ps3/repository.c index 7bdfea336f5e..9b47ba7a5de7 100644 --- a/arch/powerpc/platforms/ps3/repository.c +++ b/arch/powerpc/platforms/ps3/repository.c @@ -779,6 +779,72 @@ int ps3_repository_read_mm_info(u64 *rm_base, u64 *rm_size, u64 *region_total)  }  /** + * ps3_repository_read_highmem_region_count - Read the number of highmem regions + * + * Bootloaders must arrange the repository nodes such that regions are indexed + * with a region_index from 0 to region_count-1. + */ + +int ps3_repository_read_highmem_region_count(unsigned int *region_count) +{ +	int result; +	u64 v1 = 0; + +	result = read_node(PS3_LPAR_ID_CURRENT, +		make_first_field("highmem", 0), +		make_field("region", 0), +		make_field("count", 0), +		0, +		&v1, NULL); +	*region_count = v1; +	return result; +} + + +int ps3_repository_read_highmem_base(unsigned int region_index, +	u64 *highmem_base) +{ +	return read_node(PS3_LPAR_ID_CURRENT, +		make_first_field("highmem", 0), +		make_field("region", region_index), +		make_field("base", 0), +		0, +		highmem_base, NULL); +} + +int ps3_repository_read_highmem_size(unsigned int region_index, +	u64 *highmem_size) +{ +	return read_node(PS3_LPAR_ID_CURRENT, +		make_first_field("highmem", 0), +		make_field("region", region_index), +		make_field("size", 0), +		0, +		highmem_size, NULL); +} + +/** + * ps3_repository_read_highmem_info - Read high memory region info + * @region_index: Region index, {0,..,region_count-1}. + * @highmem_base: High memory base address. + * @highmem_size: High memory size. + * + * Bootloaders that preallocate highmem regions must place the + * region info into the repository at these well known nodes. + */ + +int ps3_repository_read_highmem_info(unsigned int region_index, +	u64 *highmem_base, u64 *highmem_size) +{ +	int result; + +	*highmem_base = 0; +	result = ps3_repository_read_highmem_base(region_index, highmem_base); +	return result ? result +		: ps3_repository_read_highmem_size(region_index, highmem_size); +} + +/**   * ps3_repository_read_num_spu_reserved - Number of physical spus reserved.   * @num_spu: Number of physical spus.   */ @@ -1002,6 +1068,138 @@ int ps3_repository_read_lpm_privileges(unsigned int be_index, u64 *lpar,  			    lpar, rights);  } +#if defined(CONFIG_PS3_REPOSITORY_WRITE) + +static int create_node(u64 n1, u64 n2, u64 n3, u64 n4, u64 v1, u64 v2) +{ +	int result; + +	dump_node(0, n1, n2, n3, n4, v1, v2); + +	result = lv1_create_repository_node(n1, n2, n3, n4, v1, v2); + +	if (result) { +		pr_devel("%s:%d: lv1_create_repository_node failed: %s\n", +			__func__, __LINE__, ps3_result(result)); +		return -ENOENT; +	} + +	return 0; +} + +static int delete_node(u64 n1, u64 n2, u64 n3, u64 n4) +{ +	int result; + +	dump_node(0, n1, n2, n3, n4, 0, 0); + +	result = lv1_delete_repository_node(n1, n2, n3, n4); + +	if (result) { +		pr_devel("%s:%d: lv1_delete_repository_node failed: %s\n", +			__func__, __LINE__, ps3_result(result)); +		return -ENOENT; +	} + +	return 0; +} + +static int write_node(u64 n1, u64 n2, u64 n3, u64 n4, u64 v1, u64 v2) +{ +	int result; + +	result = create_node(n1, n2, n3, n4, v1, v2); + +	if (!result) +		return 0; + +	result = lv1_write_repository_node(n1, n2, n3, n4, v1, v2); + +	if (result) { +		pr_devel("%s:%d: lv1_write_repository_node failed: %s\n", +			__func__, __LINE__, ps3_result(result)); +		return -ENOENT; +	} + +	return 0; +} + +int ps3_repository_write_highmem_region_count(unsigned int region_count) +{ +	int result; +	u64 v1 = (u64)region_count; + +	result = write_node( +		make_first_field("highmem", 0), +		make_field("region", 0), +		make_field("count", 0), +		0, +		v1, 0); +	return result; +} + +int ps3_repository_write_highmem_base(unsigned int region_index, +	u64 highmem_base) +{ +	return write_node( +		make_first_field("highmem", 0), +		make_field("region", region_index), +		make_field("base", 0), +		0, +		highmem_base, 0); +} + +int ps3_repository_write_highmem_size(unsigned int region_index, +	u64 highmem_size) +{ +	return write_node( +		make_first_field("highmem", 0), +		make_field("region", region_index), +		make_field("size", 0), +		0, +		highmem_size, 0); +} + +int ps3_repository_write_highmem_info(unsigned int region_index, +	u64 highmem_base, u64 highmem_size) +{ +	int result; + +	result = ps3_repository_write_highmem_base(region_index, highmem_base); +	return result ? result +		: ps3_repository_write_highmem_size(region_index, highmem_size); +} + +static int ps3_repository_delete_highmem_base(unsigned int region_index) +{ +	return delete_node( +		make_first_field("highmem", 0), +		make_field("region", region_index), +		make_field("base", 0), +		0); +} + +static int ps3_repository_delete_highmem_size(unsigned int region_index) +{ +	return delete_node( +		make_first_field("highmem", 0), +		make_field("region", region_index), +		make_field("size", 0), +		0); +} + +int ps3_repository_delete_highmem_info(unsigned int region_index) +{ +	int result; + +	result = ps3_repository_delete_highmem_base(region_index); +	result += ps3_repository_delete_highmem_size(region_index); + +	return result ? -1 : 0; +} + +#endif /* defined(CONFIG_PS3_WRITE_REPOSITORY) */ +  #if defined(DEBUG)  int ps3_repository_dump_resource_info(const struct ps3_repository_device *repo) diff --git a/arch/powerpc/platforms/pseries/eeh.c b/arch/powerpc/platforms/pseries/eeh.c index a75e37dc41aa..ecd394cf34e6 100644 --- a/arch/powerpc/platforms/pseries/eeh.c +++ b/arch/powerpc/platforms/pseries/eeh.c @@ -489,7 +489,7 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)  	 * a stack trace will help the device-driver authors figure  	 * out what happened.  So print that out.  	 */ -	dump_stack(); +	WARN(1, "EEH: failure detected\n");  	return 1;  dn_unlock: diff --git a/arch/powerpc/platforms/pseries/plpar_wrappers.h b/arch/powerpc/platforms/pseries/plpar_wrappers.h index 342797fc0f9c..13e8cc43adf7 100644 --- a/arch/powerpc/platforms/pseries/plpar_wrappers.h +++ b/arch/powerpc/platforms/pseries/plpar_wrappers.h @@ -22,12 +22,12 @@ static inline long poll_pending(void)  static inline u8 get_cede_latency_hint(void)  { -	return get_lppaca()->gpr5_dword.fields.cede_latency_hint; +	return get_lppaca()->cede_latency_hint;  }  static inline void set_cede_latency_hint(u8 latency_hint)  { -	get_lppaca()->gpr5_dword.fields.cede_latency_hint = latency_hint; +	get_lppaca()->cede_latency_hint = latency_hint;  }  static inline long cede_processor(void) diff --git a/arch/powerpc/platforms/pseries/reconfig.c b/arch/powerpc/platforms/pseries/reconfig.c index 168651acdd83..7b3bf76ef834 100644 --- a/arch/powerpc/platforms/pseries/reconfig.c +++ b/arch/powerpc/platforms/pseries/reconfig.c @@ -103,11 +103,13 @@ int pSeries_reconfig_notifier_register(struct notifier_block *nb)  {  	return blocking_notifier_chain_register(&pSeries_reconfig_chain, nb);  } +EXPORT_SYMBOL_GPL(pSeries_reconfig_notifier_register);  void pSeries_reconfig_notifier_unregister(struct notifier_block *nb)  {  	blocking_notifier_chain_unregister(&pSeries_reconfig_chain, nb);  } +EXPORT_SYMBOL_GPL(pSeries_reconfig_notifier_unregister);  int pSeries_reconfig_notify(unsigned long action, void *p)  { @@ -426,6 +428,7 @@ static int do_remove_property(char *buf, size_t bufsize)  static int do_update_property(char *buf, size_t bufsize)  {  	struct device_node *np; +	struct pSeries_reconfig_prop_update upd_value;  	unsigned char *value;  	char *name, *end, *next_prop;  	int rc, length; @@ -454,6 +457,10 @@ static int do_update_property(char *buf, size_t bufsize)  		return -ENODEV;  	} +	upd_value.node = np; +	upd_value.property = newprop; +	pSeries_reconfig_notify(PSERIES_UPDATE_PROPERTY, &upd_value); +  	rc = prom_update_property(np, newprop, oldprop);  	if (rc)  		return rc; diff --git a/arch/powerpc/sysdev/ppc4xx_msi.c b/arch/powerpc/sysdev/ppc4xx_msi.c index 1c2d7af17bbe..82c6702dcbab 100644 --- a/arch/powerpc/sysdev/ppc4xx_msi.c +++ b/arch/powerpc/sysdev/ppc4xx_msi.c @@ -28,10 +28,11 @@  #include <linux/of_platform.h>  #include <linux/interrupt.h>  #include <linux/export.h> +#include <linux/kernel.h>  #include <asm/prom.h>  #include <asm/hw_irq.h>  #include <asm/ppc-pci.h> -#include <boot/dcr.h> +#include <asm/dcr.h>  #include <asm/dcr-regs.h>  #include <asm/msi_bitmap.h> @@ -43,13 +44,14 @@  #define PEIH_FLUSH0	0x30  #define PEIH_FLUSH1	0x38  #define PEIH_CNTRST	0x48 -#define NR_MSI_IRQS	4 + +static int msi_irqs;  struct ppc4xx_msi {  	u32 msi_addr_lo;  	u32 msi_addr_hi;  	void __iomem *msi_regs; -	int msi_virqs[NR_MSI_IRQS]; +	int *msi_virqs;  	struct msi_bitmap bitmap;  	struct device_node *msi_dev;  }; @@ -61,7 +63,7 @@ static int ppc4xx_msi_init_allocator(struct platform_device *dev,  {  	int err; -	err = msi_bitmap_alloc(&msi_data->bitmap, NR_MSI_IRQS, +	err = msi_bitmap_alloc(&msi_data->bitmap, msi_irqs,  			      dev->dev.of_node);  	if (err)  		return err; @@ -83,6 +85,11 @@ static int ppc4xx_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)  	struct msi_desc *entry;  	struct ppc4xx_msi *msi_data = &ppc4xx_msi; +	msi_data->msi_virqs = kmalloc((msi_irqs) * sizeof(int), +					    GFP_KERNEL); +	if (!msi_data->msi_virqs) +		return -ENOMEM; +  	list_for_each_entry(entry, &dev->msi_list, list) {  		int_no = msi_bitmap_alloc_hwirqs(&msi_data->bitmap, 1);  		if (int_no >= 0) @@ -150,12 +157,11 @@ static int ppc4xx_setup_pcieh_hw(struct platform_device *dev,  	if (!sdr_addr)  		return -1; -	SDR0_WRITE(sdr_addr, (u64)res.start >> 32);	 /*HIGH addr */ -	SDR0_WRITE(sdr_addr + 1, res.start & 0xFFFFFFFF); /* Low addr */ - +	mtdcri(SDR0, *sdr_addr, upper_32_bits(res.start));	/*HIGH addr */ +	mtdcri(SDR0, *sdr_addr + 1, lower_32_bits(res.start));	/* Low addr */  	msi->msi_dev = of_find_node_by_name(NULL, "ppc4xx-msi"); -	if (msi->msi_dev) +	if (!msi->msi_dev)  		return -ENODEV;  	msi->msi_regs = of_iomap(msi->msi_dev, 0); @@ -167,9 +173,12 @@ static int ppc4xx_setup_pcieh_hw(struct platform_device *dev,  		(u32) (msi->msi_regs + PEIH_TERMADH), (u32) (msi->msi_regs));  	msi_virt = dma_alloc_coherent(&dev->dev, 64, &msi_phys, GFP_KERNEL); -	msi->msi_addr_hi = 0x0; -	msi->msi_addr_lo = (u32) msi_phys; -	dev_dbg(&dev->dev, "PCIE-MSI: msi address 0x%x\n", msi->msi_addr_lo); +	if (!msi_virt) +		return -ENOMEM; +	msi->msi_addr_hi = upper_32_bits(msi_phys); +	msi->msi_addr_lo = lower_32_bits(msi_phys & 0xffffffff); +	dev_dbg(&dev->dev, "PCIE-MSI: msi address high 0x%x, low 0x%x\n", +		msi->msi_addr_hi, msi->msi_addr_lo);  	/* Progam the Interrupt handler Termination addr registers */  	out_be32(msi->msi_regs + PEIH_TERMADH, msi->msi_addr_hi); @@ -185,6 +194,8 @@ static int ppc4xx_setup_pcieh_hw(struct platform_device *dev,  	out_be32(msi->msi_regs + PEIH_MSIED, *msi_data);  	out_be32(msi->msi_regs + PEIH_MSIMK, *msi_mask); +	dma_free_coherent(&dev->dev, 64, msi_virt, msi_phys); +  	return 0;  } @@ -194,7 +205,7 @@ static int ppc4xx_of_msi_remove(struct platform_device *dev)  	int i;  	int virq; -	for (i = 0; i < NR_MSI_IRQS; i++) { +	for (i = 0; i < msi_irqs; i++) {  		virq = msi->msi_virqs[i];  		if (virq != NO_IRQ)  			irq_dispose_mapping(virq); @@ -215,8 +226,6 @@ static int __devinit ppc4xx_msi_probe(struct platform_device *dev)  	struct resource res;  	int err = 0; -	msi = &ppc4xx_msi;/*keep the msi data for further use*/ -  	dev_dbg(&dev->dev, "PCIE-MSI: Setting up MSI support...\n");  	msi = kzalloc(sizeof(struct ppc4xx_msi), GFP_KERNEL); @@ -234,6 +243,10 @@ static int __devinit ppc4xx_msi_probe(struct platform_device *dev)  		goto error_out;  	} +	msi_irqs = of_irq_count(dev->dev.of_node); +	if (!msi_irqs) +		return -ENODEV; +  	if (ppc4xx_setup_pcieh_hw(dev, res, msi))  		goto error_out; @@ -242,6 +255,7 @@ static int __devinit ppc4xx_msi_probe(struct platform_device *dev)  		dev_err(&dev->dev, "Error allocating MSI bitmap\n");  		goto error_out;  	} +	ppc4xx_msi = *msi;  	ppc_md.setup_msi_irqs = ppc4xx_setup_msi_irqs;  	ppc_md.teardown_msi_irqs = ppc4xx_teardown_msi_irqs; diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index b2402eb076c7..c225314468ee 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -250,3 +250,16 @@ config UML_RANDOM  	  (check your distro, or download from  	  http://sourceforge.net/projects/gkernel/).  rngd periodically reads  	  /dev/hwrng and injects the entropy into /dev/random. + +config HW_RANDOM_PSERIES +	tristate "pSeries HW Random Number Generator support" +	depends on HW_RANDOM && PPC64 && IBMVIO +	default HW_RANDOM +	---help--- +	  This driver provides kernel-side support for the Random Number +	  Generator hardware found on POWER7+ machines and above + +	  To compile this driver as a module, choose M here: the +	  module will be called pseries-rng. + +	  If unsure, say Y. diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index b2ff5265a996..d901dfa30321 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o  obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o  obj-$(CONFIG_HW_RANDOM_PICOXCELL) += picoxcell-rng.o  obj-$(CONFIG_HW_RANDOM_PPC4XX) += ppc4xx-rng.o +obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o diff --git a/drivers/char/hw_random/pseries-rng.c b/drivers/char/hw_random/pseries-rng.c new file mode 100644 index 000000000000..5f1197929f0c --- /dev/null +++ b/drivers/char/hw_random/pseries-rng.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010 Michael Neuling IBM Corporation + * + * Driver for the pseries hardware RNG for POWER7+ and above + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/hw_random.h> +#include <asm/vio.h> + +#define MODULE_NAME "pseries-rng" + +static int pseries_rng_data_read(struct hwrng *rng, u32 *data) +{ +	if (plpar_hcall(H_RANDOM, (unsigned long *)data) != H_SUCCESS) { +		printk(KERN_ERR "pseries rng hcall error\n"); +		return 0; +	} +	return 8; +} + +/** + * pseries_rng_get_desired_dma - Return desired DMA allocate for CMO operations + * + * This is a required function for a driver to operate in a CMO environment + * but this device does not make use of DMA allocations, return 0. + * + * Return value: + *	Number of bytes of IO data the driver will need to perform well -> 0 + */ +static unsigned long pseries_rng_get_desired_dma(struct vio_dev *vdev) +{ +	return 0; +}; + +static struct hwrng pseries_rng = { +	.name		= MODULE_NAME, +	.data_read	= pseries_rng_data_read, +}; + +static int __init pseries_rng_probe(struct vio_dev *dev, +		const struct vio_device_id *id) +{ +	return hwrng_register(&pseries_rng); +} + +static int __exit pseries_rng_remove(struct vio_dev *dev) +{ +	hwrng_unregister(&pseries_rng); +	return 0; +} + +static struct vio_device_id pseries_rng_driver_ids[] = { +	{ "ibm,random-v1", "ibm,random"}, +	{ "", "" } +}; +MODULE_DEVICE_TABLE(vio, pseries_rng_driver_ids); + +static struct vio_driver pseries_rng_driver = { +	.name = MODULE_NAME, +	.probe = pseries_rng_probe, +	.remove = pseries_rng_remove, +	.get_desired_dma = pseries_rng_get_desired_dma, +	.id_table = pseries_rng_driver_ids +}; + +static int __init rng_init(void) +{ +	printk(KERN_INFO "Registering IBM pSeries RNG driver\n"); +	return vio_register_driver(&pseries_rng_driver); +} + +module_init(rng_init); + +static void __exit rng_exit(void) +{ +	vio_unregister_driver(&pseries_rng_driver); +} +module_exit(rng_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Neuling <mikey@neuling.org>"); +MODULE_DESCRIPTION("H/W RNG driver for IBM pSeries processors"); diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index 371f13cc38eb..6373fa0ddb65 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -297,4 +297,21 @@ config CRYPTO_DEV_TEGRA_AES  	  To compile this driver as a module, choose M here: the module  	  will be called tegra-aes. +config CRYPTO_DEV_NX +	tristate "Support for Power7+ in-Nest cryptographic accleration" +	depends on PPC64 && IBMVIO +	select CRYPTO_AES +	select CRYPTO_CBC +	select CRYPTO_ECB +	select CRYPTO_CCM +	select CRYPTO_GCM +	select CRYPTO_AUTHENC +	select CRYPTO_XCBC +	select CRYPTO_SHA256 +	select CRYPTO_SHA512 +	help +	  Support for Power7+ in-Nest cryptographic acceleration. This +	  module supports acceleration for AES and SHA2 algorithms. If you +	  choose 'M' here, this module will be called nx_crypto. +  endif # CRYPTO_HW diff --git a/drivers/crypto/nx/Makefile b/drivers/crypto/nx/Makefile new file mode 100644 index 000000000000..411ce59c80d1 --- /dev/null +++ b/drivers/crypto/nx/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_CRYPTO_DEV_NX) += nx-crypto.o +nx-crypto-objs := nx.o \ +		  nx_debugfs.o \ +		  nx-aes-cbc.o \ +		  nx-aes-ecb.o \ +		  nx-aes-gcm.o \ +		  nx-aes-ccm.o \ +		  nx-aes-ctr.o \ +		  nx-aes-xcbc.o \ +		  nx-sha256.o \ +		  nx-sha512.o diff --git a/drivers/crypto/nx/nx-aes-cbc.c b/drivers/crypto/nx/nx-aes-cbc.c new file mode 100644 index 000000000000..69ed796ee327 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-cbc.c @@ -0,0 +1,141 @@ +/** + * AES CBC routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int cbc_aes_nx_set_key(struct crypto_tfm *tfm, +			      const u8          *in_key, +			      unsigned int       key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + +	nx_ctx_init(nx_ctx, HCOP_FC_AES); + +	switch (key_len) { +	case AES_KEYSIZE_128: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; +		break; +	case AES_KEYSIZE_192: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; +		break; +	case AES_KEYSIZE_256: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; +		break; +	default: +		return -EINVAL; +	} + +	csbcpb->cpb.hdr.mode = NX_MODE_AES_CBC; +	memcpy(csbcpb->cpb.aes_cbc.key, in_key, key_len); + +	return 0; +} + +static int cbc_aes_nx_crypt(struct blkcipher_desc *desc, +			    struct scatterlist    *dst, +			    struct scatterlist    *src, +			    unsigned int           nbytes, +			    int                    enc) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	int rc; + +	if (nbytes > nx_ctx->ap->databytelen) +		return -EINVAL; + +	if (enc) +		NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; +	else +		NX_CPB_FDM(csbcpb) &= ~NX_FDM_ENDE_ENCRYPT; + +	rc = nx_build_sg_lists(nx_ctx, desc, dst, src, nbytes, +			       csbcpb->cpb.aes_cbc.iv); +	if (rc) +		goto out; + +	if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); +	atomic64_add(csbcpb->csb.processed_byte_count, +		     &(nx_ctx->stats->aes_bytes)); +out: +	return rc; +} + +static int cbc_aes_nx_encrypt(struct blkcipher_desc *desc, +			      struct scatterlist    *dst, +			      struct scatterlist    *src, +			      unsigned int           nbytes) +{ +	return cbc_aes_nx_crypt(desc, dst, src, nbytes, 1); +} + +static int cbc_aes_nx_decrypt(struct blkcipher_desc *desc, +			      struct scatterlist    *dst, +			      struct scatterlist    *src, +			      unsigned int           nbytes) +{ +	return cbc_aes_nx_crypt(desc, dst, src, nbytes, 0); +} + +struct crypto_alg nx_cbc_aes_alg = { +	.cra_name        = "cbc(aes)", +	.cra_driver_name = "cbc-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_BLKCIPHER, +	.cra_blocksize   = AES_BLOCK_SIZE, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_blkcipher_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_cbc_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_cbc_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_blkcipher = { +		.min_keysize = AES_MIN_KEY_SIZE, +		.max_keysize = AES_MAX_KEY_SIZE, +		.ivsize      = AES_BLOCK_SIZE, +		.setkey      = cbc_aes_nx_set_key, +		.encrypt     = cbc_aes_nx_encrypt, +		.decrypt     = cbc_aes_nx_decrypt, +	} +}; diff --git a/drivers/crypto/nx/nx-aes-ccm.c b/drivers/crypto/nx/nx-aes-ccm.c new file mode 100644 index 000000000000..7aeac678b9c0 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-ccm.c @@ -0,0 +1,468 @@ +/** + * AES CCM routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/internal/aead.h> +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <crypto/scatterwalk.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int ccm_aes_nx_set_key(struct crypto_aead *tfm, +			      const u8           *in_key, +			      unsigned int        key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	struct nx_csbcpb *csbcpb_aead = nx_ctx->csbcpb_aead; + +	nx_ctx_init(nx_ctx, HCOP_FC_AES); + +	switch (key_len) { +	case AES_KEYSIZE_128: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); +		NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_128); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; +		break; +	default: +		return -EINVAL; +	} + +	csbcpb->cpb.hdr.mode = NX_MODE_AES_CCM; +	memcpy(csbcpb->cpb.aes_ccm.key, in_key, key_len); + +	csbcpb_aead->cpb.hdr.mode = NX_MODE_AES_CCA; +	memcpy(csbcpb_aead->cpb.aes_cca.key, in_key, key_len); + +	return 0; + +} + +static int ccm4309_aes_nx_set_key(struct crypto_aead *tfm, +				  const u8           *in_key, +				  unsigned int        key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); + +	if (key_len < 3) +		return -EINVAL; + +	key_len -= 3; + +	memcpy(nx_ctx->priv.ccm.nonce, in_key + key_len, 3); + +	return ccm_aes_nx_set_key(tfm, in_key, key_len); +} + +static int ccm_aes_nx_setauthsize(struct crypto_aead *tfm, +				  unsigned int authsize) +{ +	switch (authsize) { +	case 4: +	case 6: +	case 8: +	case 10: +	case 12: +	case 14: +	case 16: +		break; +	default: +		return -EINVAL; +	} + +	crypto_aead_crt(tfm)->authsize = authsize; + +	return 0; +} + +static int ccm4309_aes_nx_setauthsize(struct crypto_aead *tfm, +				      unsigned int authsize) +{ +	switch (authsize) { +	case 8: +	case 12: +	case 16: +		break; +	default: +		return -EINVAL; +	} + +	crypto_aead_crt(tfm)->authsize = authsize; + +	return 0; +} + +/* taken from crypto/ccm.c */ +static int set_msg_len(u8 *block, unsigned int msglen, int csize) +{ +	__be32 data; + +	memset(block, 0, csize); +	block += csize; + +	if (csize >= 4) +		csize = 4; +	else if (msglen > (unsigned int)(1 << (8 * csize))) +		return -EOVERFLOW; + +	data = cpu_to_be32(msglen); +	memcpy(block - csize, (u8 *)&data + 4 - csize, csize); + +	return 0; +} + +/* taken from crypto/ccm.c */ +static inline int crypto_ccm_check_iv(const u8 *iv) +{ +	/* 2 <= L <= 8, so 1 <= L' <= 7. */ +	if (1 > iv[0] || iv[0] > 7) +		return -EINVAL; + +	return 0; +} + +/* based on code from crypto/ccm.c */ +static int generate_b0(u8 *iv, unsigned int assoclen, unsigned int authsize, +		       unsigned int cryptlen, u8 *b0) +{ +	unsigned int l, lp, m = authsize; +	int rc; + +	memcpy(b0, iv, 16); + +	lp = b0[0]; +	l = lp + 1; + +	/* set m, bits 3-5 */ +	*b0 |= (8 * ((m - 2) / 2)); + +	/* set adata, bit 6, if associated data is used */ +	if (assoclen) +		*b0 |= 64; + +	rc = set_msg_len(b0 + 16 - l, cryptlen, l); + +	return rc; +} + +static int generate_pat(u8                   *iv, +			struct aead_request  *req, +			struct nx_crypto_ctx *nx_ctx, +			unsigned int          authsize, +			unsigned int          nbytes, +			u8                   *out) +{ +	struct nx_sg *nx_insg = nx_ctx->in_sg; +	struct nx_sg *nx_outsg = nx_ctx->out_sg; +	unsigned int iauth_len = 0; +	struct vio_pfo_op *op = NULL; +	u8 tmp[16], *b1 = NULL, *b0 = NULL, *result = NULL; +	int rc; + +	/* zero the ctr value */ +	memset(iv + 15 - iv[0], 0, iv[0] + 1); + +	if (!req->assoclen) { +		b0 = nx_ctx->csbcpb->cpb.aes_ccm.in_pat_or_b0; +	} else if (req->assoclen <= 14) { +		/* if associated data is 14 bytes or less, we do 1 GCM +		 * operation on 2 AES blocks, B0 (stored in the csbcpb) and B1, +		 * which is fed in through the source buffers here */ +		b0 = nx_ctx->csbcpb->cpb.aes_ccm.in_pat_or_b0; +		b1 = nx_ctx->priv.ccm.iauth_tag; +		iauth_len = req->assoclen; + +		nx_insg = nx_build_sg_list(nx_insg, b1, 16, nx_ctx->ap->sglen); +		nx_outsg = nx_build_sg_list(nx_outsg, tmp, 16, +					    nx_ctx->ap->sglen); + +		/* inlen should be negative, indicating to phyp that its a +		 * pointer to an sg list */ +		nx_ctx->op.inlen = (nx_ctx->in_sg - nx_insg) * +					sizeof(struct nx_sg); +		nx_ctx->op.outlen = (nx_ctx->out_sg - nx_outsg) * +					sizeof(struct nx_sg); + +		NX_CPB_FDM(nx_ctx->csbcpb) |= NX_FDM_ENDE_ENCRYPT; +		NX_CPB_FDM(nx_ctx->csbcpb) |= NX_FDM_INTERMEDIATE; + +		op = &nx_ctx->op; +		result = nx_ctx->csbcpb->cpb.aes_ccm.out_pat_or_mac; +	} else if (req->assoclen <= 65280) { +		/* if associated data is less than (2^16 - 2^8), we construct +		 * B1 differently and feed in the associated data to a CCA +		 * operation */ +		b0 = nx_ctx->csbcpb_aead->cpb.aes_cca.b0; +		b1 = nx_ctx->csbcpb_aead->cpb.aes_cca.b1; +		iauth_len = 14; + +		/* remaining assoc data must have scatterlist built for it */ +		nx_insg = nx_walk_and_build(nx_insg, nx_ctx->ap->sglen, +					    req->assoc, iauth_len, +					    req->assoclen - iauth_len); +		nx_ctx->op_aead.inlen = (nx_ctx->in_sg - nx_insg) * +						sizeof(struct nx_sg); + +		op = &nx_ctx->op_aead; +		result = nx_ctx->csbcpb_aead->cpb.aes_cca.out_pat_or_b0; +	} else { +		/* if associated data is less than (2^32), we construct B1 +		 * differently yet again and feed in the associated data to a +		 * CCA operation */ +		pr_err("associated data len is %u bytes (returning -EINVAL)\n", +		       req->assoclen); +		rc = -EINVAL; +	} + +	rc = generate_b0(iv, req->assoclen, authsize, nbytes, b0); +	if (rc) +		goto done; + +	if (b1) { +		memset(b1, 0, 16); +		*(u16 *)b1 = (u16)req->assoclen; + +		scatterwalk_map_and_copy(b1 + 2, req->assoc, 0, +					 iauth_len, SCATTERWALK_FROM_SG); + +		rc = nx_hcall_sync(nx_ctx, op, +				   req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); +		if (rc) +			goto done; + +		atomic_inc(&(nx_ctx->stats->aes_ops)); +		atomic64_add(req->assoclen, &(nx_ctx->stats->aes_bytes)); + +		memcpy(out, result, AES_BLOCK_SIZE); +	} +done: +	return rc; +} + +static int ccm_nx_decrypt(struct aead_request   *req, +			  struct blkcipher_desc *desc) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	unsigned int nbytes = req->cryptlen; +	unsigned int authsize = crypto_aead_authsize(crypto_aead_reqtfm(req)); +	struct nx_ccm_priv *priv = &nx_ctx->priv.ccm; +	int rc = -1; + +	if (nbytes > nx_ctx->ap->databytelen) +		return -EINVAL; + +	nbytes -= authsize; + +	/* copy out the auth tag to compare with later */ +	scatterwalk_map_and_copy(priv->oauth_tag, +				 req->src, nbytes, authsize, +				 SCATTERWALK_FROM_SG); + +	rc = generate_pat(desc->info, req, nx_ctx, authsize, nbytes, +			  csbcpb->cpb.aes_ccm.in_pat_or_b0); +	if (rc) +		goto out; + +	rc = nx_build_sg_lists(nx_ctx, desc, req->dst, req->src, nbytes, +			       csbcpb->cpb.aes_ccm.iv_or_ctr); +	if (rc) +		goto out; + +	NX_CPB_FDM(nx_ctx->csbcpb) &= ~NX_FDM_ENDE_ENCRYPT; +	NX_CPB_FDM(nx_ctx->csbcpb) &= ~NX_FDM_INTERMEDIATE; + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); +	atomic64_add(csbcpb->csb.processed_byte_count, +		     &(nx_ctx->stats->aes_bytes)); + +	rc = memcmp(csbcpb->cpb.aes_ccm.out_pat_or_mac, priv->oauth_tag, +		    authsize) ? -EBADMSG : 0; +out: +	return rc; +} + +static int ccm_nx_encrypt(struct aead_request   *req, +			  struct blkcipher_desc *desc) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	unsigned int nbytes = req->cryptlen; +	unsigned int authsize = crypto_aead_authsize(crypto_aead_reqtfm(req)); +	int rc = -1; + +	if (nbytes > nx_ctx->ap->databytelen) +		return -EINVAL; + +	rc = generate_pat(desc->info, req, nx_ctx, authsize, nbytes, +			  csbcpb->cpb.aes_ccm.in_pat_or_b0); +	if (rc) +		goto out; + +	rc = nx_build_sg_lists(nx_ctx, desc, req->dst, req->src, nbytes, +			       csbcpb->cpb.aes_ccm.iv_or_ctr); +	if (rc) +		goto out; + +	NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; +	NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); +	atomic64_add(csbcpb->csb.processed_byte_count, +		     &(nx_ctx->stats->aes_bytes)); + +	/* copy out the auth tag */ +	scatterwalk_map_and_copy(csbcpb->cpb.aes_ccm.out_pat_or_mac, +				 req->dst, nbytes, authsize, +				 SCATTERWALK_TO_SG); +out: +	return rc; +} + +static int ccm4309_aes_nx_encrypt(struct aead_request *req) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	struct blkcipher_desc desc; +	u8 *iv = nx_ctx->priv.ccm.iv; + +	iv[0] = 3; +	memcpy(iv + 1, nx_ctx->priv.ccm.nonce, 3); +	memcpy(iv + 4, req->iv, 8); + +	desc.info = iv; +	desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + +	return ccm_nx_encrypt(req, &desc); +} + +static int ccm_aes_nx_encrypt(struct aead_request *req) +{ +	struct blkcipher_desc desc; +	int rc; + +	desc.info = req->iv; +	desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + +	rc = crypto_ccm_check_iv(desc.info); +	if (rc) +		return rc; + +	return ccm_nx_encrypt(req, &desc); +} + +static int ccm4309_aes_nx_decrypt(struct aead_request *req) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	struct blkcipher_desc desc; +	u8 *iv = nx_ctx->priv.ccm.iv; + +	iv[0] = 3; +	memcpy(iv + 1, nx_ctx->priv.ccm.nonce, 3); +	memcpy(iv + 4, req->iv, 8); + +	desc.info = iv; +	desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + +	return ccm_nx_decrypt(req, &desc); +} + +static int ccm_aes_nx_decrypt(struct aead_request *req) +{ +	struct blkcipher_desc desc; +	int rc; + +	desc.info = req->iv; +	desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + +	rc = crypto_ccm_check_iv(desc.info); +	if (rc) +		return rc; + +	return ccm_nx_decrypt(req, &desc); +} + +/* tell the block cipher walk routines that this is a stream cipher by + * setting cra_blocksize to 1. Even using blkcipher_walk_virt_block + * during encrypt/decrypt doesn't solve this problem, because it calls + * blkcipher_walk_done under the covers, which doesn't use walk->blocksize, + * but instead uses this tfm->blocksize. */ +struct crypto_alg nx_ccm_aes_alg = { +	.cra_name        = "ccm(aes)", +	.cra_driver_name = "ccm-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_AEAD | +			   CRYPTO_ALG_NEED_FALLBACK, +	.cra_blocksize   = 1, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_aead_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_ccm_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_ccm_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_aead = { +		.ivsize      = AES_BLOCK_SIZE, +		.maxauthsize = AES_BLOCK_SIZE, +		.setkey      = ccm_aes_nx_set_key, +		.setauthsize = ccm_aes_nx_setauthsize, +		.encrypt     = ccm_aes_nx_encrypt, +		.decrypt     = ccm_aes_nx_decrypt, +	} +}; + +struct crypto_alg nx_ccm4309_aes_alg = { +	.cra_name        = "rfc4309(ccm(aes))", +	.cra_driver_name = "rfc4309-ccm-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_AEAD | +			   CRYPTO_ALG_NEED_FALLBACK, +	.cra_blocksize   = 1, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_nivaead_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_ccm4309_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_ccm_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_aead = { +		.ivsize      = 8, +		.maxauthsize = AES_BLOCK_SIZE, +		.setkey      = ccm4309_aes_nx_set_key, +		.setauthsize = ccm4309_aes_nx_setauthsize, +		.encrypt     = ccm4309_aes_nx_encrypt, +		.decrypt     = ccm4309_aes_nx_decrypt, +		.geniv       = "seqiv", +	} +}; diff --git a/drivers/crypto/nx/nx-aes-ctr.c b/drivers/crypto/nx/nx-aes-ctr.c new file mode 100644 index 000000000000..52d4eb05e8f7 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-ctr.c @@ -0,0 +1,178 @@ +/** + * AES CTR routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/aes.h> +#include <crypto/ctr.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int ctr_aes_nx_set_key(struct crypto_tfm *tfm, +			      const u8          *in_key, +			      unsigned int       key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + +	nx_ctx_init(nx_ctx, HCOP_FC_AES); + +	switch (key_len) { +	case AES_KEYSIZE_128: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; +		break; +	case AES_KEYSIZE_192: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; +		break; +	case AES_KEYSIZE_256: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; +		break; +	default: +		return -EINVAL; +	} + +	csbcpb->cpb.hdr.mode = NX_MODE_AES_CTR; +	memcpy(csbcpb->cpb.aes_ctr.key, in_key, key_len); + +	return 0; +} + +static int ctr3686_aes_nx_set_key(struct crypto_tfm *tfm, +				  const u8          *in_key, +				  unsigned int       key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); + +	if (key_len < CTR_RFC3686_NONCE_SIZE) +		return -EINVAL; + +	memcpy(nx_ctx->priv.ctr.iv, +	       in_key + key_len - CTR_RFC3686_NONCE_SIZE, +	       CTR_RFC3686_NONCE_SIZE); + +	key_len -= CTR_RFC3686_NONCE_SIZE; + +	return ctr_aes_nx_set_key(tfm, in_key, key_len); +} + +static int ctr_aes_nx_crypt(struct blkcipher_desc *desc, +			    struct scatterlist    *dst, +			    struct scatterlist    *src, +			    unsigned int           nbytes) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	int rc; + +	if (nbytes > nx_ctx->ap->databytelen) +		return -EINVAL; + +	rc = nx_build_sg_lists(nx_ctx, desc, dst, src, nbytes, +			       csbcpb->cpb.aes_ctr.iv); +	if (rc) +		goto out; + +	if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); +	atomic64_add(csbcpb->csb.processed_byte_count, +		     &(nx_ctx->stats->aes_bytes)); +out: +	return rc; +} + +static int ctr3686_aes_nx_crypt(struct blkcipher_desc *desc, +				struct scatterlist    *dst, +				struct scatterlist    *src, +				unsigned int           nbytes) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); +	u8 *iv = nx_ctx->priv.ctr.iv; + +	memcpy(iv + CTR_RFC3686_NONCE_SIZE, +	       desc->info, CTR_RFC3686_IV_SIZE); +	iv[15] = 1; + +	desc->info = nx_ctx->priv.ctr.iv; + +	return ctr_aes_nx_crypt(desc, dst, src, nbytes); +} + +struct crypto_alg nx_ctr_aes_alg = { +	.cra_name        = "ctr(aes)", +	.cra_driver_name = "ctr-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_BLKCIPHER, +	.cra_blocksize   = 1, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_blkcipher_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_ctr_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_ctr_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_blkcipher = { +		.min_keysize = AES_MIN_KEY_SIZE, +		.max_keysize = AES_MAX_KEY_SIZE, +		.ivsize      = AES_BLOCK_SIZE, +		.setkey      = ctr_aes_nx_set_key, +		.encrypt     = ctr_aes_nx_crypt, +		.decrypt     = ctr_aes_nx_crypt, +	} +}; + +struct crypto_alg nx_ctr3686_aes_alg = { +	.cra_name        = "rfc3686(ctr(aes))", +	.cra_driver_name = "rfc3686-ctr-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_BLKCIPHER, +	.cra_blocksize   = 1, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_blkcipher_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_ctr3686_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_ctr_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_blkcipher = { +		.min_keysize = AES_MIN_KEY_SIZE + CTR_RFC3686_NONCE_SIZE, +		.max_keysize = AES_MAX_KEY_SIZE + CTR_RFC3686_NONCE_SIZE, +		.ivsize      = CTR_RFC3686_IV_SIZE, +		.geniv       = "seqiv", +		.setkey      = ctr3686_aes_nx_set_key, +		.encrypt     = ctr3686_aes_nx_crypt, +		.decrypt     = ctr3686_aes_nx_crypt, +	} +}; diff --git a/drivers/crypto/nx/nx-aes-ecb.c b/drivers/crypto/nx/nx-aes-ecb.c new file mode 100644 index 000000000000..7b77bc2d1df4 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-ecb.c @@ -0,0 +1,139 @@ +/** + * AES ECB routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int ecb_aes_nx_set_key(struct crypto_tfm *tfm, +			      const u8          *in_key, +			      unsigned int       key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + +	nx_ctx_init(nx_ctx, HCOP_FC_AES); + +	switch (key_len) { +	case AES_KEYSIZE_128: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; +		break; +	case AES_KEYSIZE_192: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; +		break; +	case AES_KEYSIZE_256: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; +		break; +	default: +		return -EINVAL; +	} + +	csbcpb->cpb.hdr.mode = NX_MODE_AES_ECB; +	memcpy(csbcpb->cpb.aes_ecb.key, in_key, key_len); + +	return 0; +} + +static int ecb_aes_nx_crypt(struct blkcipher_desc *desc, +			    struct scatterlist    *dst, +			    struct scatterlist    *src, +			    unsigned int           nbytes, +			    int                    enc) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	int rc; + +	if (nbytes > nx_ctx->ap->databytelen) +		return -EINVAL; + +	if (enc) +		NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; +	else +		NX_CPB_FDM(csbcpb) &= ~NX_FDM_ENDE_ENCRYPT; + +	rc = nx_build_sg_lists(nx_ctx, desc, dst, src, nbytes, NULL); +	if (rc) +		goto out; + +	if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); +	atomic64_add(csbcpb->csb.processed_byte_count, +		     &(nx_ctx->stats->aes_bytes)); +out: +	return rc; +} + +static int ecb_aes_nx_encrypt(struct blkcipher_desc *desc, +			      struct scatterlist    *dst, +			      struct scatterlist    *src, +			      unsigned int           nbytes) +{ +	return ecb_aes_nx_crypt(desc, dst, src, nbytes, 1); +} + +static int ecb_aes_nx_decrypt(struct blkcipher_desc *desc, +			      struct scatterlist    *dst, +			      struct scatterlist    *src, +			      unsigned int           nbytes) +{ +	return ecb_aes_nx_crypt(desc, dst, src, nbytes, 0); +} + +struct crypto_alg nx_ecb_aes_alg = { +	.cra_name        = "ecb(aes)", +	.cra_driver_name = "ecb-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_BLKCIPHER, +	.cra_blocksize   = AES_BLOCK_SIZE, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_blkcipher_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_ecb_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_ecb_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_blkcipher = { +		.min_keysize = AES_MIN_KEY_SIZE, +		.max_keysize = AES_MAX_KEY_SIZE, +		.setkey      = ecb_aes_nx_set_key, +		.encrypt     = ecb_aes_nx_encrypt, +		.decrypt     = ecb_aes_nx_decrypt, +	} +}; diff --git a/drivers/crypto/nx/nx-aes-gcm.c b/drivers/crypto/nx/nx-aes-gcm.c new file mode 100644 index 000000000000..9ab1c7341dac --- /dev/null +++ b/drivers/crypto/nx/nx-aes-gcm.c @@ -0,0 +1,353 @@ +/** + * AES GCM routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/internal/aead.h> +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <crypto/scatterwalk.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int gcm_aes_nx_set_key(struct crypto_aead *tfm, +			      const u8           *in_key, +			      unsigned int        key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	struct nx_csbcpb *csbcpb_aead = nx_ctx->csbcpb_aead; + +	nx_ctx_init(nx_ctx, HCOP_FC_AES); + +	switch (key_len) { +	case AES_KEYSIZE_128: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); +		NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_128); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; +		break; +	case AES_KEYSIZE_192: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); +		NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_192); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; +		break; +	case AES_KEYSIZE_256: +		NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); +		NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_256); +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; +		break; +	default: +		return -EINVAL; +	} + +	csbcpb->cpb.hdr.mode = NX_MODE_AES_GCM; +	memcpy(csbcpb->cpb.aes_gcm.key, in_key, key_len); + +	csbcpb_aead->cpb.hdr.mode = NX_MODE_AES_GCA; +	memcpy(csbcpb_aead->cpb.aes_gca.key, in_key, key_len); + +	return 0; +} + +static int gcm4106_aes_nx_set_key(struct crypto_aead *tfm, +				  const u8           *in_key, +				  unsigned int        key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); +	char *nonce = nx_ctx->priv.gcm.nonce; +	int rc; + +	if (key_len < 4) +		return -EINVAL; + +	key_len -= 4; + +	rc = gcm_aes_nx_set_key(tfm, in_key, key_len); +	if (rc) +		goto out; + +	memcpy(nonce, in_key + key_len, 4); +out: +	return rc; +} + +static int gcm_aes_nx_setauthsize(struct crypto_aead *tfm, +				  unsigned int authsize) +{ +	if (authsize > crypto_aead_alg(tfm)->maxauthsize) +		return -EINVAL; + +	crypto_aead_crt(tfm)->authsize = authsize; + +	return 0; +} + +static int gcm4106_aes_nx_setauthsize(struct crypto_aead *tfm, +				      unsigned int authsize) +{ +	switch (authsize) { +	case 8: +	case 12: +	case 16: +		break; +	default: +		return -EINVAL; +	} + +	crypto_aead_crt(tfm)->authsize = authsize; + +	return 0; +} + +static int nx_gca(struct nx_crypto_ctx  *nx_ctx, +		  struct aead_request   *req, +		  u8                    *out) +{ +	struct nx_csbcpb *csbcpb_aead = nx_ctx->csbcpb_aead; +	int rc = -EINVAL; +	struct scatter_walk walk; +	struct nx_sg *nx_sg = nx_ctx->in_sg; + +	if (req->assoclen > nx_ctx->ap->databytelen) +		goto out; + +	if (req->assoclen <= AES_BLOCK_SIZE) { +		scatterwalk_start(&walk, req->assoc); +		scatterwalk_copychunks(out, &walk, req->assoclen, +				       SCATTERWALK_FROM_SG); +		scatterwalk_done(&walk, SCATTERWALK_FROM_SG, 0); + +		rc = 0; +		goto out; +	} + +	nx_sg = nx_walk_and_build(nx_sg, nx_ctx->ap->sglen, req->assoc, 0, +				  req->assoclen); +	nx_ctx->op_aead.inlen = (nx_ctx->in_sg - nx_sg) * sizeof(struct nx_sg); + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op_aead, +			   req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); +	atomic64_add(req->assoclen, &(nx_ctx->stats->aes_bytes)); + +	memcpy(out, csbcpb_aead->cpb.aes_gca.out_pat, AES_BLOCK_SIZE); +out: +	return rc; +} + +static int gcm_aes_nx_crypt(struct aead_request *req, int enc) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	struct blkcipher_desc desc; +	unsigned int nbytes = req->cryptlen; +	int rc = -EINVAL; + +	if (nbytes > nx_ctx->ap->databytelen) +		goto out; + +	desc.info = nx_ctx->priv.gcm.iv; +	/* initialize the counter */ +	*(u32 *)(desc.info + NX_GCM_CTR_OFFSET) = 1; + +	/* For scenarios where the input message is zero length, AES CTR mode +	 * may be used. Set the source data to be a single block (16B) of all +	 * zeros, and set the input IV value to be the same as the GMAC IV +	 * value. - nx_wb 4.8.1.3 */ +	if (nbytes == 0) { +		char src[AES_BLOCK_SIZE] = {}; +		struct scatterlist sg; + +		desc.tfm = crypto_alloc_blkcipher("ctr(aes)", 0, 0); +		if (IS_ERR(desc.tfm)) { +			rc = -ENOMEM; +			goto out; +		} + +		crypto_blkcipher_setkey(desc.tfm, csbcpb->cpb.aes_gcm.key, +			NX_CPB_KEY_SIZE(csbcpb) == NX_KS_AES_128 ? 16 : +			NX_CPB_KEY_SIZE(csbcpb) == NX_KS_AES_192 ? 24 : 32); + +		sg_init_one(&sg, src, AES_BLOCK_SIZE); +		if (enc) +			crypto_blkcipher_encrypt_iv(&desc, req->dst, &sg, +						    AES_BLOCK_SIZE); +		else +			crypto_blkcipher_decrypt_iv(&desc, req->dst, &sg, +						    AES_BLOCK_SIZE); +		crypto_free_blkcipher(desc.tfm); + +		rc = 0; +		goto out; +	} + +	desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + +	csbcpb->cpb.aes_gcm.bit_length_aad = req->assoclen * 8; + +	if (req->assoclen) { +		rc = nx_gca(nx_ctx, req, csbcpb->cpb.aes_gcm.in_pat_or_aad); +		if (rc) +			goto out; +	} + +	if (enc) +		NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; +	else +		nbytes -= AES_BLOCK_SIZE; + +	csbcpb->cpb.aes_gcm.bit_length_data = nbytes * 8; + +	rc = nx_build_sg_lists(nx_ctx, &desc, req->dst, req->src, nbytes, +			       csbcpb->cpb.aes_gcm.iv_or_cnt); +	if (rc) +		goto out; + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); +	atomic64_add(csbcpb->csb.processed_byte_count, +		     &(nx_ctx->stats->aes_bytes)); + +	if (enc) { +		/* copy out the auth tag */ +		scatterwalk_map_and_copy(csbcpb->cpb.aes_gcm.out_pat_or_mac, +				 req->dst, nbytes, +				 crypto_aead_authsize(crypto_aead_reqtfm(req)), +				 SCATTERWALK_TO_SG); +	} else if (req->assoclen) { +		u8 *itag = nx_ctx->priv.gcm.iauth_tag; +		u8 *otag = csbcpb->cpb.aes_gcm.out_pat_or_mac; + +		scatterwalk_map_and_copy(itag, req->dst, nbytes, +				 crypto_aead_authsize(crypto_aead_reqtfm(req)), +				 SCATTERWALK_FROM_SG); +		rc = memcmp(itag, otag, +			    crypto_aead_authsize(crypto_aead_reqtfm(req))) ? +		     -EBADMSG : 0; +	} +out: +	return rc; +} + +static int gcm_aes_nx_encrypt(struct aead_request *req) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	char *iv = nx_ctx->priv.gcm.iv; + +	memcpy(iv, req->iv, 12); + +	return gcm_aes_nx_crypt(req, 1); +} + +static int gcm_aes_nx_decrypt(struct aead_request *req) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	char *iv = nx_ctx->priv.gcm.iv; + +	memcpy(iv, req->iv, 12); + +	return gcm_aes_nx_crypt(req, 0); +} + +static int gcm4106_aes_nx_encrypt(struct aead_request *req) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	char *iv = nx_ctx->priv.gcm.iv; +	char *nonce = nx_ctx->priv.gcm.nonce; + +	memcpy(iv, nonce, NX_GCM4106_NONCE_LEN); +	memcpy(iv + NX_GCM4106_NONCE_LEN, req->iv, 8); + +	return gcm_aes_nx_crypt(req, 1); +} + +static int gcm4106_aes_nx_decrypt(struct aead_request *req) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); +	char *iv = nx_ctx->priv.gcm.iv; +	char *nonce = nx_ctx->priv.gcm.nonce; + +	memcpy(iv, nonce, NX_GCM4106_NONCE_LEN); +	memcpy(iv + NX_GCM4106_NONCE_LEN, req->iv, 8); + +	return gcm_aes_nx_crypt(req, 0); +} + +/* tell the block cipher walk routines that this is a stream cipher by + * setting cra_blocksize to 1. Even using blkcipher_walk_virt_block + * during encrypt/decrypt doesn't solve this problem, because it calls + * blkcipher_walk_done under the covers, which doesn't use walk->blocksize, + * but instead uses this tfm->blocksize. */ +struct crypto_alg nx_gcm_aes_alg = { +	.cra_name        = "gcm(aes)", +	.cra_driver_name = "gcm-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_AEAD, +	.cra_blocksize   = 1, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_aead_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_gcm_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_gcm_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_aead = { +		.ivsize      = AES_BLOCK_SIZE, +		.maxauthsize = AES_BLOCK_SIZE, +		.setkey      = gcm_aes_nx_set_key, +		.setauthsize = gcm_aes_nx_setauthsize, +		.encrypt     = gcm_aes_nx_encrypt, +		.decrypt     = gcm_aes_nx_decrypt, +	} +}; + +struct crypto_alg nx_gcm4106_aes_alg = { +	.cra_name        = "rfc4106(gcm(aes))", +	.cra_driver_name = "rfc4106-gcm-aes-nx", +	.cra_priority    = 300, +	.cra_flags       = CRYPTO_ALG_TYPE_AEAD, +	.cra_blocksize   = 1, +	.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +	.cra_type        = &crypto_nivaead_type, +	.cra_module      = THIS_MODULE, +	.cra_list        = LIST_HEAD_INIT(nx_gcm4106_aes_alg.cra_list), +	.cra_init        = nx_crypto_ctx_aes_gcm_init, +	.cra_exit        = nx_crypto_ctx_exit, +	.cra_aead = { +		.ivsize      = 8, +		.maxauthsize = AES_BLOCK_SIZE, +		.geniv       = "seqiv", +		.setkey      = gcm4106_aes_nx_set_key, +		.setauthsize = gcm4106_aes_nx_setauthsize, +		.encrypt     = gcm4106_aes_nx_encrypt, +		.decrypt     = gcm4106_aes_nx_decrypt, +	} +}; diff --git a/drivers/crypto/nx/nx-aes-xcbc.c b/drivers/crypto/nx/nx-aes-xcbc.c new file mode 100644 index 000000000000..93923e4628c0 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-xcbc.c @@ -0,0 +1,236 @@ +/** + * AES XCBC routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/internal/hash.h> +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +struct xcbc_state { +	u8 state[AES_BLOCK_SIZE]; +	unsigned int count; +	u8 buffer[AES_BLOCK_SIZE]; +}; + +static int nx_xcbc_set_key(struct crypto_shash *desc, +			   const u8            *in_key, +			   unsigned int         key_len) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_shash_ctx(desc); + +	switch (key_len) { +	case AES_KEYSIZE_128: +		nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; +		break; +	default: +		return -EINVAL; +	} + +	memcpy(nx_ctx->priv.xcbc.key, in_key, key_len); + +	return 0; +} + +static int nx_xcbc_init(struct shash_desc *desc) +{ +	struct xcbc_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	struct nx_sg *out_sg; + +	nx_ctx_init(nx_ctx, HCOP_FC_AES); + +	memset(sctx, 0, sizeof *sctx); + +	NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); +	csbcpb->cpb.hdr.mode = NX_MODE_AES_XCBC_MAC; + +	memcpy(csbcpb->cpb.aes_xcbc.key, nx_ctx->priv.xcbc.key, AES_BLOCK_SIZE); +	memset(nx_ctx->priv.xcbc.key, 0, sizeof *nx_ctx->priv.xcbc.key); + +	out_sg = nx_build_sg_list(nx_ctx->out_sg, (u8 *)sctx->state, +				  AES_BLOCK_SIZE, nx_ctx->ap->sglen); +	nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + +	return 0; +} + +static int nx_xcbc_update(struct shash_desc *desc, +			  const u8          *data, +			  unsigned int       len) +{ +	struct xcbc_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	struct nx_sg *in_sg; +	u32 to_process, leftover; +	int rc = 0; + +	if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { +		/* we've hit the nx chip previously and we're updating again, +		 * so copy over the partial digest */ +		memcpy(csbcpb->cpb.aes_xcbc.cv, +		       csbcpb->cpb.aes_xcbc.out_cv_mac, AES_BLOCK_SIZE); +	} + +	/* 2 cases for total data len: +	 *  1: <= AES_BLOCK_SIZE: copy into state, return 0 +	 *  2: > AES_BLOCK_SIZE: process X blocks, copy in leftover +	 */ +	if (len + sctx->count <= AES_BLOCK_SIZE) { +		memcpy(sctx->buffer + sctx->count, data, len); +		sctx->count += len; +		goto out; +	} + +	/* to_process: the AES_BLOCK_SIZE data chunk to process in this +	 * update */ +	to_process = (sctx->count + len) & ~(AES_BLOCK_SIZE - 1); +	leftover = (sctx->count + len) & (AES_BLOCK_SIZE - 1); + +	/* the hardware will not accept a 0 byte operation for this algorithm +	 * and the operation MUST be finalized to be correct. So if we happen +	 * to get an update that falls on a block sized boundary, we must +	 * save off the last block to finalize with later. */ +	if (!leftover) { +		to_process -= AES_BLOCK_SIZE; +		leftover = AES_BLOCK_SIZE; +	} + +	if (sctx->count) { +		in_sg = nx_build_sg_list(nx_ctx->in_sg, sctx->buffer, +					 sctx->count, nx_ctx->ap->sglen); +		in_sg = nx_build_sg_list(in_sg, (u8 *)data, +					 to_process - sctx->count, +					 nx_ctx->ap->sglen); +		nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * +					sizeof(struct nx_sg); +	} else { +		in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)data, to_process, +					 nx_ctx->ap->sglen); +		nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * +					sizeof(struct nx_sg); +	} + +	NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + +	if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); + +	/* copy the leftover back into the state struct */ +	memcpy(sctx->buffer, data + len - leftover, leftover); +	sctx->count = leftover; + +	/* everything after the first update is continuation */ +	NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +out: +	return rc; +} + +static int nx_xcbc_final(struct shash_desc *desc, u8 *out) +{ +	struct xcbc_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; +	struct nx_sg *in_sg, *out_sg; +	int rc = 0; + +	if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { +		/* we've hit the nx chip previously, now we're finalizing, +		 * so copy over the partial digest */ +		memcpy(csbcpb->cpb.aes_xcbc.cv, +		       csbcpb->cpb.aes_xcbc.out_cv_mac, AES_BLOCK_SIZE); +	} else if (sctx->count == 0) { +		/* we've never seen an update, so this is a 0 byte op. The +		 * hardware cannot handle a 0 byte op, so just copy out the +		 * known 0 byte result. This is cheaper than allocating a +		 * software context to do a 0 byte op */ +		u8 data[] = { 0x75, 0xf0, 0x25, 0x1d, 0x52, 0x8a, 0xc0, 0x1c, +			      0x45, 0x73, 0xdf, 0xd5, 0x84, 0xd7, 0x9f, 0x29 }; +		memcpy(out, data, sizeof(data)); +		goto out; +	} + +	/* final is represented by continuing the operation and indicating that +	 * this is not an intermediate operation */ +	NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + +	in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buffer, +				 sctx->count, nx_ctx->ap->sglen); +	out_sg = nx_build_sg_list(nx_ctx->out_sg, out, AES_BLOCK_SIZE, +				  nx_ctx->ap->sglen); + +	nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * sizeof(struct nx_sg); +	nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + +	if (!nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->aes_ops)); + +	memcpy(out, csbcpb->cpb.aes_xcbc.out_cv_mac, AES_BLOCK_SIZE); +out: +	return rc; +} + +struct shash_alg nx_shash_aes_xcbc_alg = { +	.digestsize = AES_BLOCK_SIZE, +	.init       = nx_xcbc_init, +	.update     = nx_xcbc_update, +	.final      = nx_xcbc_final, +	.setkey     = nx_xcbc_set_key, +	.descsize   = sizeof(struct xcbc_state), +	.statesize  = sizeof(struct xcbc_state), +	.base       = { +		.cra_name        = "xcbc(aes)", +		.cra_driver_name = "xcbc-aes-nx", +		.cra_priority    = 300, +		.cra_flags       = CRYPTO_ALG_TYPE_SHASH, +		.cra_blocksize   = AES_BLOCK_SIZE, +		.cra_module      = THIS_MODULE, +		.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +		.cra_init        = nx_crypto_ctx_aes_xcbc_init, +		.cra_exit        = nx_crypto_ctx_exit, +	} +}; diff --git a/drivers/crypto/nx/nx-sha256.c b/drivers/crypto/nx/nx-sha256.c new file mode 100644 index 000000000000..9767315f8c0b --- /dev/null +++ b/drivers/crypto/nx/nx-sha256.c @@ -0,0 +1,246 @@ +/** + * SHA-256 routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/internal/hash.h> +#include <crypto/sha.h> +#include <linux/module.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int nx_sha256_init(struct shash_desc *desc) +{ +	struct sha256_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_sg *out_sg; + +	nx_ctx_init(nx_ctx, HCOP_FC_SHA); + +	memset(sctx, 0, sizeof *sctx); + +	nx_ctx->ap = &nx_ctx->props[NX_PROPS_SHA256]; + +	NX_CPB_SET_DIGEST_SIZE(nx_ctx->csbcpb, NX_DS_SHA256); +	out_sg = nx_build_sg_list(nx_ctx->out_sg, (u8 *)sctx->state, +				  SHA256_DIGEST_SIZE, nx_ctx->ap->sglen); +	nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + +	return 0; +} + +static int nx_sha256_update(struct shash_desc *desc, const u8 *data, +			    unsigned int len) +{ +	struct sha256_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	struct nx_sg *in_sg; +	u64 to_process, leftover; +	int rc = 0; + +	if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { +		/* we've hit the nx chip previously and we're updating again, +		 * so copy over the partial digest */ +		memcpy(csbcpb->cpb.sha256.input_partial_digest, +		       csbcpb->cpb.sha256.message_digest, SHA256_DIGEST_SIZE); +	} + +	/* 2 cases for total data len: +	 *  1: <= SHA256_BLOCK_SIZE: copy into state, return 0 +	 *  2: > SHA256_BLOCK_SIZE: process X blocks, copy in leftover +	 */ +	if (len + sctx->count <= SHA256_BLOCK_SIZE) { +		memcpy(sctx->buf + sctx->count, data, len); +		sctx->count += len; +		goto out; +	} + +	/* to_process: the SHA256_BLOCK_SIZE data chunk to process in this +	 * update */ +	to_process = (sctx->count + len) & ~(SHA256_BLOCK_SIZE - 1); +	leftover = (sctx->count + len) & (SHA256_BLOCK_SIZE - 1); + +	if (sctx->count) { +		in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buf, +					 sctx->count, nx_ctx->ap->sglen); +		in_sg = nx_build_sg_list(in_sg, (u8 *)data, +					 to_process - sctx->count, +					 nx_ctx->ap->sglen); +		nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * +					sizeof(struct nx_sg); +	} else { +		in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)data, +					 to_process, nx_ctx->ap->sglen); +		nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * +					sizeof(struct nx_sg); +	} + +	NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + +	if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->sha256_ops)); + +	/* copy the leftover back into the state struct */ +	memcpy(sctx->buf, data + len - leftover, leftover); +	sctx->count = leftover; + +	csbcpb->cpb.sha256.message_bit_length += (u64) +		(csbcpb->cpb.sha256.spbc * 8); + +	/* everything after the first update is continuation */ +	NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +out: +	return rc; +} + +static int nx_sha256_final(struct shash_desc *desc, u8 *out) +{ +	struct sha256_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	struct nx_sg *in_sg, *out_sg; +	int rc; + +	if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { +		/* we've hit the nx chip previously, now we're finalizing, +		 * so copy over the partial digest */ +		memcpy(csbcpb->cpb.sha256.input_partial_digest, +		       csbcpb->cpb.sha256.message_digest, SHA256_DIGEST_SIZE); +	} + +	/* final is represented by continuing the operation and indicating that +	 * this is not an intermediate operation */ +	NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + +	csbcpb->cpb.sha256.message_bit_length += (u64)(sctx->count * 8); + +	in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buf, +				 sctx->count, nx_ctx->ap->sglen); +	out_sg = nx_build_sg_list(nx_ctx->out_sg, out, SHA256_DIGEST_SIZE, +				  nx_ctx->ap->sglen); +	nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * sizeof(struct nx_sg); +	nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + +	if (!nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->sha256_ops)); + +	atomic64_add(csbcpb->cpb.sha256.message_bit_length, +		     &(nx_ctx->stats->sha256_bytes)); +	memcpy(out, csbcpb->cpb.sha256.message_digest, SHA256_DIGEST_SIZE); +out: +	return rc; +} + +static int nx_sha256_export(struct shash_desc *desc, void *out) +{ +	struct sha256_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	struct sha256_state *octx = out; + +	octx->count = sctx->count + +		      (csbcpb->cpb.sha256.message_bit_length / 8); +	memcpy(octx->buf, sctx->buf, sizeof(octx->buf)); + +	/* if no data has been processed yet, we need to export SHA256's +	 * initial data, in case this context gets imported into a software +	 * context */ +	if (csbcpb->cpb.sha256.message_bit_length) +		memcpy(octx->state, csbcpb->cpb.sha256.message_digest, +		       SHA256_DIGEST_SIZE); +	else { +		octx->state[0] = SHA256_H0; +		octx->state[1] = SHA256_H1; +		octx->state[2] = SHA256_H2; +		octx->state[3] = SHA256_H3; +		octx->state[4] = SHA256_H4; +		octx->state[5] = SHA256_H5; +		octx->state[6] = SHA256_H6; +		octx->state[7] = SHA256_H7; +	} + +	return 0; +} + +static int nx_sha256_import(struct shash_desc *desc, const void *in) +{ +	struct sha256_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	const struct sha256_state *ictx = in; + +	memcpy(sctx->buf, ictx->buf, sizeof(ictx->buf)); + +	sctx->count = ictx->count & 0x3f; +	csbcpb->cpb.sha256.message_bit_length = (ictx->count & ~0x3f) * 8; + +	if (csbcpb->cpb.sha256.message_bit_length) { +		memcpy(csbcpb->cpb.sha256.message_digest, ictx->state, +		       SHA256_DIGEST_SIZE); + +		NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +		NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; +	} + +	return 0; +} + +struct shash_alg nx_shash_sha256_alg = { +	.digestsize = SHA256_DIGEST_SIZE, +	.init       = nx_sha256_init, +	.update     = nx_sha256_update, +	.final      = nx_sha256_final, +	.export     = nx_sha256_export, +	.import     = nx_sha256_import, +	.descsize   = sizeof(struct sha256_state), +	.statesize  = sizeof(struct sha256_state), +	.base       = { +		.cra_name        = "sha256", +		.cra_driver_name = "sha256-nx", +		.cra_priority    = 300, +		.cra_flags       = CRYPTO_ALG_TYPE_SHASH, +		.cra_blocksize   = SHA256_BLOCK_SIZE, +		.cra_module      = THIS_MODULE, +		.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +		.cra_init        = nx_crypto_ctx_sha_init, +		.cra_exit        = nx_crypto_ctx_exit, +	} +}; diff --git a/drivers/crypto/nx/nx-sha512.c b/drivers/crypto/nx/nx-sha512.c new file mode 100644 index 000000000000..3177b8c3d5f1 --- /dev/null +++ b/drivers/crypto/nx/nx-sha512.c @@ -0,0 +1,265 @@ +/** + * SHA-512 routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/internal/hash.h> +#include <crypto/sha.h> +#include <linux/module.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int nx_sha512_init(struct shash_desc *desc) +{ +	struct sha512_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_sg *out_sg; + +	nx_ctx_init(nx_ctx, HCOP_FC_SHA); + +	memset(sctx, 0, sizeof *sctx); + +	nx_ctx->ap = &nx_ctx->props[NX_PROPS_SHA512]; + +	NX_CPB_SET_DIGEST_SIZE(nx_ctx->csbcpb, NX_DS_SHA512); +	out_sg = nx_build_sg_list(nx_ctx->out_sg, (u8 *)sctx->state, +				  SHA512_DIGEST_SIZE, nx_ctx->ap->sglen); +	nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + +	return 0; +} + +static int nx_sha512_update(struct shash_desc *desc, const u8 *data, +			    unsigned int len) +{ +	struct sha512_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	struct nx_sg *in_sg; +	u64 to_process, leftover, spbc_bits; +	int rc = 0; + +	if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { +		/* we've hit the nx chip previously and we're updating again, +		 * so copy over the partial digest */ +		memcpy(csbcpb->cpb.sha512.input_partial_digest, +		       csbcpb->cpb.sha512.message_digest, SHA512_DIGEST_SIZE); +	} + +	/* 2 cases for total data len: +	 *  1: <= SHA512_BLOCK_SIZE: copy into state, return 0 +	 *  2: > SHA512_BLOCK_SIZE: process X blocks, copy in leftover +	 */ +	if ((u64)len + sctx->count[0] <= SHA512_BLOCK_SIZE) { +		memcpy(sctx->buf + sctx->count[0], data, len); +		sctx->count[0] += len; +		goto out; +	} + +	/* to_process: the SHA512_BLOCK_SIZE data chunk to process in this +	 * update */ +	to_process = (sctx->count[0] + len) & ~(SHA512_BLOCK_SIZE - 1); +	leftover = (sctx->count[0] + len) & (SHA512_BLOCK_SIZE - 1); + +	if (sctx->count[0]) { +		in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buf, +					 sctx->count[0], nx_ctx->ap->sglen); +		in_sg = nx_build_sg_list(in_sg, (u8 *)data, +					 to_process - sctx->count[0], +					 nx_ctx->ap->sglen); +		nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * +					sizeof(struct nx_sg); +	} else { +		in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)data, +					 to_process, nx_ctx->ap->sglen); +		nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * +					sizeof(struct nx_sg); +	} + +	NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + +	if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->sha512_ops)); + +	/* copy the leftover back into the state struct */ +	memcpy(sctx->buf, data + len - leftover, leftover); +	sctx->count[0] = leftover; + +	spbc_bits = csbcpb->cpb.sha512.spbc * 8; +	csbcpb->cpb.sha512.message_bit_length_lo += spbc_bits; +	if (csbcpb->cpb.sha512.message_bit_length_lo < spbc_bits) +		csbcpb->cpb.sha512.message_bit_length_hi++; + +	/* everything after the first update is continuation */ +	NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +out: +	return rc; +} + +static int nx_sha512_final(struct shash_desc *desc, u8 *out) +{ +	struct sha512_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	struct nx_sg *in_sg, *out_sg; +	u64 count0; +	int rc; + +	if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { +		/* we've hit the nx chip previously, now we're finalizing, +		 * so copy over the partial digest */ +		memcpy(csbcpb->cpb.sha512.input_partial_digest, +		       csbcpb->cpb.sha512.message_digest, SHA512_DIGEST_SIZE); +	} + +	/* final is represented by continuing the operation and indicating that +	 * this is not an intermediate operation */ +	NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + +	count0 = sctx->count[0] * 8; + +	csbcpb->cpb.sha512.message_bit_length_lo += count0; +	if (csbcpb->cpb.sha512.message_bit_length_lo < count0) +		csbcpb->cpb.sha512.message_bit_length_hi++; + +	in_sg = nx_build_sg_list(nx_ctx->in_sg, sctx->buf, sctx->count[0], +				 nx_ctx->ap->sglen); +	out_sg = nx_build_sg_list(nx_ctx->out_sg, out, SHA512_DIGEST_SIZE, +				  nx_ctx->ap->sglen); +	nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * sizeof(struct nx_sg); +	nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + +	if (!nx_ctx->op.outlen) { +		rc = -EINVAL; +		goto out; +	} + +	rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, +			   desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); +	if (rc) +		goto out; + +	atomic_inc(&(nx_ctx->stats->sha512_ops)); +	atomic64_add(csbcpb->cpb.sha512.message_bit_length_lo, +		     &(nx_ctx->stats->sha512_bytes)); + +	memcpy(out, csbcpb->cpb.sha512.message_digest, SHA512_DIGEST_SIZE); +out: +	return rc; +} + +static int nx_sha512_export(struct shash_desc *desc, void *out) +{ +	struct sha512_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	struct sha512_state *octx = out; + +	/* move message_bit_length (128 bits) into count and convert its value +	 * to bytes */ +	octx->count[0] = csbcpb->cpb.sha512.message_bit_length_lo >> 3 | +			 ((csbcpb->cpb.sha512.message_bit_length_hi & 7) << 61); +	octx->count[1] = csbcpb->cpb.sha512.message_bit_length_hi >> 3; + +	octx->count[0] += sctx->count[0]; +	if (octx->count[0] < sctx->count[0]) +		octx->count[1]++; + +	memcpy(octx->buf, sctx->buf, sizeof(octx->buf)); + +	/* if no data has been processed yet, we need to export SHA512's +	 * initial data, in case this context gets imported into a software +	 * context */ +	if (csbcpb->cpb.sha512.message_bit_length_hi || +	    csbcpb->cpb.sha512.message_bit_length_lo) +		memcpy(octx->state, csbcpb->cpb.sha512.message_digest, +		       SHA512_DIGEST_SIZE); +	else { +		octx->state[0] = SHA512_H0; +		octx->state[1] = SHA512_H1; +		octx->state[2] = SHA512_H2; +		octx->state[3] = SHA512_H3; +		octx->state[4] = SHA512_H4; +		octx->state[5] = SHA512_H5; +		octx->state[6] = SHA512_H6; +		octx->state[7] = SHA512_H7; +	} + +	return 0; +} + +static int nx_sha512_import(struct shash_desc *desc, const void *in) +{ +	struct sha512_state *sctx = shash_desc_ctx(desc); +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); +	struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; +	const struct sha512_state *ictx = in; + +	memcpy(sctx->buf, ictx->buf, sizeof(ictx->buf)); +	sctx->count[0] = ictx->count[0] & 0x3f; +	csbcpb->cpb.sha512.message_bit_length_lo = (ictx->count[0] & ~0x3f) +							<< 3; +	csbcpb->cpb.sha512.message_bit_length_hi = ictx->count[1] << 3 | +						   ictx->count[0] >> 61; + +	if (csbcpb->cpb.sha512.message_bit_length_hi || +	    csbcpb->cpb.sha512.message_bit_length_lo) { +		memcpy(csbcpb->cpb.sha512.message_digest, ictx->state, +		       SHA512_DIGEST_SIZE); + +		NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +		NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; +	} + +	return 0; +} + +struct shash_alg nx_shash_sha512_alg = { +	.digestsize = SHA512_DIGEST_SIZE, +	.init       = nx_sha512_init, +	.update     = nx_sha512_update, +	.final      = nx_sha512_final, +	.export     = nx_sha512_export, +	.import     = nx_sha512_import, +	.descsize   = sizeof(struct sha512_state), +	.statesize  = sizeof(struct sha512_state), +	.base       = { +		.cra_name        = "sha512", +		.cra_driver_name = "sha512-nx", +		.cra_priority    = 300, +		.cra_flags       = CRYPTO_ALG_TYPE_SHASH, +		.cra_blocksize   = SHA512_BLOCK_SIZE, +		.cra_module      = THIS_MODULE, +		.cra_ctxsize     = sizeof(struct nx_crypto_ctx), +		.cra_init        = nx_crypto_ctx_sha_init, +		.cra_exit        = nx_crypto_ctx_exit, +	} +}; diff --git a/drivers/crypto/nx/nx.c b/drivers/crypto/nx/nx.c new file mode 100644 index 000000000000..d7f179cc2e98 --- /dev/null +++ b/drivers/crypto/nx/nx.c @@ -0,0 +1,716 @@ +/** + * Routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <crypto/internal/hash.h> +#include <crypto/hash.h> +#include <crypto/aes.h> +#include <crypto/sha.h> +#include <crypto/algapi.h> +#include <crypto/scatterwalk.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <linux/device.h> +#include <linux/of.h> +#include <asm/pSeries_reconfig.h> +#include <asm/abs_addr.h> +#include <asm/hvcall.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +/** + * nx_hcall_sync - make an H_COP_OP hcall for the passed in op structure + * + * @nx_ctx: the crypto context handle + * @op: PFO operation struct to pass in + * @may_sleep: flag indicating the request can sleep + * + * Make the hcall, retrying while the hardware is busy. If we cannot yield + * the thread, limit the number of retries to 10 here. + */ +int nx_hcall_sync(struct nx_crypto_ctx *nx_ctx, +		  struct vio_pfo_op    *op, +		  u32                   may_sleep) +{ +	int rc, retries = 10; +	struct vio_dev *viodev = nx_driver.viodev; + +	atomic_inc(&(nx_ctx->stats->sync_ops)); + +	do { +		rc = vio_h_cop_sync(viodev, op); +	} while ((rc == -EBUSY && !may_sleep && retries--) || +	         (rc == -EBUSY && may_sleep && cond_resched())); + +	if (rc) { +		dev_dbg(&viodev->dev, "vio_h_cop_sync failed: rc: %d " +			"hcall rc: %ld\n", rc, op->hcall_err); +		atomic_inc(&(nx_ctx->stats->errors)); +		atomic_set(&(nx_ctx->stats->last_error), op->hcall_err); +		atomic_set(&(nx_ctx->stats->last_error_pid), current->pid); +	} + +	return rc; +} + +/** + * nx_build_sg_list - build an NX scatter list describing a single  buffer + * + * @sg_head: pointer to the first scatter list element to build + * @start_addr: pointer to the linear buffer + * @len: length of the data at @start_addr + * @sgmax: the largest number of scatter list elements we're allowed to create + * + * This function will start writing nx_sg elements at @sg_head and keep + * writing them until all of the data from @start_addr is described or + * until sgmax elements have been written. Scatter list elements will be + * created such that none of the elements describes a buffer that crosses a 4K + * boundary. + */ +struct nx_sg *nx_build_sg_list(struct nx_sg *sg_head, +			       u8           *start_addr, +			       unsigned int  len, +			       u32           sgmax) +{ +	unsigned int sg_len = 0; +	struct nx_sg *sg; +	u64 sg_addr = (u64)start_addr; +	u64 end_addr; + +	/* determine the start and end for this address range - slightly +	 * different if this is in VMALLOC_REGION */ +	if (is_vmalloc_addr(start_addr)) +		sg_addr = phys_to_abs(page_to_phys(vmalloc_to_page(start_addr))) +			  + offset_in_page(sg_addr); +	else +		sg_addr = virt_to_abs(sg_addr); + +	end_addr = sg_addr + len; + +	/* each iteration will write one struct nx_sg element and add the +	 * length of data described by that element to sg_len. Once @len bytes +	 * have been described (or @sgmax elements have been written), the +	 * loop ends. min_t is used to ensure @end_addr falls on the same page +	 * as sg_addr, if not, we need to create another nx_sg element for the +	 * data on the next page */ +	for (sg = sg_head; sg_len < len; sg++) { +		sg->addr = sg_addr; +		sg_addr = min_t(u64, NX_PAGE_NUM(sg_addr + NX_PAGE_SIZE), end_addr); +		sg->len = sg_addr - sg->addr; +		sg_len += sg->len; + +		if ((sg - sg_head) == sgmax) { +			pr_err("nx: scatter/gather list overflow, pid: %d\n", +			       current->pid); +			return NULL; +		} +	} + +	/* return the moved sg_head pointer */ +	return sg; +} + +/** + * nx_walk_and_build - walk a linux scatterlist and build an nx scatterlist + * + * @nx_dst: pointer to the first nx_sg element to write + * @sglen: max number of nx_sg entries we're allowed to write + * @sg_src: pointer to the source linux scatterlist to walk + * @start: number of bytes to fast-forward past at the beginning of @sg_src + * @src_len: number of bytes to walk in @sg_src + */ +struct nx_sg *nx_walk_and_build(struct nx_sg       *nx_dst, +				unsigned int        sglen, +				struct scatterlist *sg_src, +				unsigned int        start, +				unsigned int        src_len) +{ +	struct scatter_walk walk; +	struct nx_sg *nx_sg = nx_dst; +	unsigned int n, offset = 0, len = src_len; +	char *dst; + +	/* we need to fast forward through @start bytes first */ +	for (;;) { +		scatterwalk_start(&walk, sg_src); + +		if (start < offset + sg_src->length) +			break; + +		offset += sg_src->length; +		sg_src = scatterwalk_sg_next(sg_src); +	} + +	/* start - offset is the number of bytes to advance in the scatterlist +	 * element we're currently looking at */ +	scatterwalk_advance(&walk, start - offset); + +	while (len && nx_sg) { +		n = scatterwalk_clamp(&walk, len); +		if (!n) { +			scatterwalk_start(&walk, sg_next(walk.sg)); +			n = scatterwalk_clamp(&walk, len); +		} +		dst = scatterwalk_map(&walk); + +		nx_sg = nx_build_sg_list(nx_sg, dst, n, sglen); +		len -= n; + +		scatterwalk_unmap(dst); +		scatterwalk_advance(&walk, n); +		scatterwalk_done(&walk, SCATTERWALK_FROM_SG, len); +	} + +	/* return the moved destination pointer */ +	return nx_sg; +} + +/** + * nx_build_sg_lists - walk the input scatterlists and build arrays of NX + *                     scatterlists based on them. + * + * @nx_ctx: NX crypto context for the lists we're building + * @desc: the block cipher descriptor for the operation + * @dst: destination scatterlist + * @src: source scatterlist + * @nbytes: length of data described in the scatterlists + * @iv: destination for the iv data, if the algorithm requires it + * + * This is common code shared by all the AES algorithms. It uses the block + * cipher walk routines to traverse input and output scatterlists, building + * corresponding NX scatterlists + */ +int nx_build_sg_lists(struct nx_crypto_ctx  *nx_ctx, +		      struct blkcipher_desc *desc, +		      struct scatterlist    *dst, +		      struct scatterlist    *src, +		      unsigned int           nbytes, +		      u8                    *iv) +{ +	struct nx_sg *nx_insg = nx_ctx->in_sg; +	struct nx_sg *nx_outsg = nx_ctx->out_sg; +	struct blkcipher_walk walk; +	int rc; + +	blkcipher_walk_init(&walk, dst, src, nbytes); +	rc = blkcipher_walk_virt_block(desc, &walk, AES_BLOCK_SIZE); +	if (rc) +		goto out; + +	if (iv) +		memcpy(iv, walk.iv, AES_BLOCK_SIZE); + +	while (walk.nbytes) { +		nx_insg = nx_build_sg_list(nx_insg, walk.src.virt.addr, +					   walk.nbytes, nx_ctx->ap->sglen); +		nx_outsg = nx_build_sg_list(nx_outsg, walk.dst.virt.addr, +					    walk.nbytes, nx_ctx->ap->sglen); + +		rc = blkcipher_walk_done(desc, &walk, 0); +		if (rc) +			break; +	} + +	if (walk.nbytes) { +		nx_insg = nx_build_sg_list(nx_insg, walk.src.virt.addr, +					   walk.nbytes, nx_ctx->ap->sglen); +		nx_outsg = nx_build_sg_list(nx_outsg, walk.dst.virt.addr, +					    walk.nbytes, nx_ctx->ap->sglen); + +		rc = 0; +	} + +	/* these lengths should be negative, which will indicate to phyp that +	 * the input and output parameters are scatterlists, not linear +	 * buffers */ +	nx_ctx->op.inlen = (nx_ctx->in_sg - nx_insg) * sizeof(struct nx_sg); +	nx_ctx->op.outlen = (nx_ctx->out_sg - nx_outsg) * sizeof(struct nx_sg); +out: +	return rc; +} + +/** + * nx_ctx_init - initialize an nx_ctx's vio_pfo_op struct + * + * @nx_ctx: the nx context to initialize + * @function: the function code for the op + */ +void nx_ctx_init(struct nx_crypto_ctx *nx_ctx, unsigned int function) +{ +	memset(nx_ctx->kmem, 0, nx_ctx->kmem_len); +	nx_ctx->csbcpb->csb.valid |= NX_CSB_VALID_BIT; + +	nx_ctx->op.flags = function; +	nx_ctx->op.csbcpb = virt_to_abs(nx_ctx->csbcpb); +	nx_ctx->op.in = virt_to_abs(nx_ctx->in_sg); +	nx_ctx->op.out = virt_to_abs(nx_ctx->out_sg); + +	if (nx_ctx->csbcpb_aead) { +		nx_ctx->csbcpb_aead->csb.valid |= NX_CSB_VALID_BIT; + +		nx_ctx->op_aead.flags = function; +		nx_ctx->op_aead.csbcpb = virt_to_abs(nx_ctx->csbcpb_aead); +		nx_ctx->op_aead.in = virt_to_abs(nx_ctx->in_sg); +		nx_ctx->op_aead.out = virt_to_abs(nx_ctx->out_sg); +	} +} + +static void nx_of_update_status(struct device   *dev, +			       struct property *p, +			       struct nx_of    *props) +{ +	if (!strncmp(p->value, "okay", p->length)) { +		props->status = NX_WAITING; +		props->flags |= NX_OF_FLAG_STATUS_SET; +	} else { +		dev_info(dev, "%s: status '%s' is not 'okay'\n", __func__, +			 (char *)p->value); +	} +} + +static void nx_of_update_sglen(struct device   *dev, +			       struct property *p, +			       struct nx_of    *props) +{ +	if (p->length != sizeof(props->max_sg_len)) { +		dev_err(dev, "%s: unexpected format for " +			"ibm,max-sg-len property\n", __func__); +		dev_dbg(dev, "%s: ibm,max-sg-len is %d bytes " +			"long, expected %zd bytes\n", __func__, +			p->length, sizeof(props->max_sg_len)); +		return; +	} + +	props->max_sg_len = *(u32 *)p->value; +	props->flags |= NX_OF_FLAG_MAXSGLEN_SET; +} + +static void nx_of_update_msc(struct device   *dev, +			     struct property *p, +			     struct nx_of    *props) +{ +	struct msc_triplet *trip; +	struct max_sync_cop *msc; +	unsigned int bytes_so_far, i, lenp; + +	msc = (struct max_sync_cop *)p->value; +	lenp = p->length; + +	/* You can't tell if the data read in for this property is sane by its +	 * size alone. This is because there are sizes embedded in the data +	 * structure. The best we can do is check lengths as we parse and bail +	 * as soon as a length error is detected. */ +	bytes_so_far = 0; + +	while ((bytes_so_far + sizeof(struct max_sync_cop)) <= lenp) { +		bytes_so_far += sizeof(struct max_sync_cop); + +		trip = msc->trip; + +		for (i = 0; +		     ((bytes_so_far + sizeof(struct msc_triplet)) <= lenp) && +		     i < msc->triplets; +		     i++) { +			if (msc->fc > NX_MAX_FC || msc->mode > NX_MAX_MODE) { +				dev_err(dev, "unknown function code/mode " +					"combo: %d/%d (ignored)\n", msc->fc, +					msc->mode); +				goto next_loop; +			} + +			switch (trip->keybitlen) { +			case 128: +			case 160: +				props->ap[msc->fc][msc->mode][0].databytelen = +					trip->databytelen; +				props->ap[msc->fc][msc->mode][0].sglen = +					trip->sglen; +				break; +			case 192: +				props->ap[msc->fc][msc->mode][1].databytelen = +					trip->databytelen; +				props->ap[msc->fc][msc->mode][1].sglen = +					trip->sglen; +				break; +			case 256: +				if (msc->fc == NX_FC_AES) { +					props->ap[msc->fc][msc->mode][2]. +						databytelen = trip->databytelen; +					props->ap[msc->fc][msc->mode][2].sglen = +						trip->sglen; +				} else if (msc->fc == NX_FC_AES_HMAC || +					   msc->fc == NX_FC_SHA) { +					props->ap[msc->fc][msc->mode][1]. +						databytelen = trip->databytelen; +					props->ap[msc->fc][msc->mode][1].sglen = +						trip->sglen; +				} else { +					dev_warn(dev, "unknown function " +						"code/key bit len combo" +						": (%u/256)\n", msc->fc); +				} +				break; +			case 512: +				props->ap[msc->fc][msc->mode][2].databytelen = +					trip->databytelen; +				props->ap[msc->fc][msc->mode][2].sglen = +					trip->sglen; +				break; +			default: +				dev_warn(dev, "unknown function code/key bit " +					 "len combo: (%u/%u)\n", msc->fc, +					 trip->keybitlen); +				break; +			} +next_loop: +			bytes_so_far += sizeof(struct msc_triplet); +			trip++; +		} + +		msc = (struct max_sync_cop *)trip; +	} + +	props->flags |= NX_OF_FLAG_MAXSYNCCOP_SET; +} + +/** + * nx_of_init - read openFirmware values from the device tree + * + * @dev: device handle + * @props: pointer to struct to hold the properties values + * + * Called once at driver probe time, this function will read out the + * openFirmware properties we use at runtime. If all the OF properties are + * acceptable, when we exit this function props->flags will indicate that + * we're ready to register our crypto algorithms. + */ +static void nx_of_init(struct device *dev, struct nx_of *props) +{ +	struct device_node *base_node = dev->of_node; +	struct property *p; + +	p = of_find_property(base_node, "status", NULL); +	if (!p) +		dev_info(dev, "%s: property 'status' not found\n", __func__); +	else +		nx_of_update_status(dev, p, props); + +	p = of_find_property(base_node, "ibm,max-sg-len", NULL); +	if (!p) +		dev_info(dev, "%s: property 'ibm,max-sg-len' not found\n", +			 __func__); +	else +		nx_of_update_sglen(dev, p, props); + +	p = of_find_property(base_node, "ibm,max-sync-cop", NULL); +	if (!p) +		dev_info(dev, "%s: property 'ibm,max-sync-cop' not found\n", +			 __func__); +	else +		nx_of_update_msc(dev, p, props); +} + +/** + * nx_register_algs - register algorithms with the crypto API + * + * Called from nx_probe() + * + * If all OF properties are in an acceptable state, the driver flags will + * indicate that we're ready and we'll create our debugfs files and register + * out crypto algorithms. + */ +static int nx_register_algs(void) +{ +	int rc = -1; + +	if (nx_driver.of.flags != NX_OF_FLAG_MASK_READY) +		goto out; + +	memset(&nx_driver.stats, 0, sizeof(struct nx_stats)); + +	rc = NX_DEBUGFS_INIT(&nx_driver); +	if (rc) +		goto out; + +	rc = crypto_register_alg(&nx_ecb_aes_alg); +	if (rc) +		goto out; + +	rc = crypto_register_alg(&nx_cbc_aes_alg); +	if (rc) +		goto out_unreg_ecb; + +	rc = crypto_register_alg(&nx_ctr_aes_alg); +	if (rc) +		goto out_unreg_cbc; + +	rc = crypto_register_alg(&nx_ctr3686_aes_alg); +	if (rc) +		goto out_unreg_ctr; + +	rc = crypto_register_alg(&nx_gcm_aes_alg); +	if (rc) +		goto out_unreg_ctr3686; + +	rc = crypto_register_alg(&nx_gcm4106_aes_alg); +	if (rc) +		goto out_unreg_gcm; + +	rc = crypto_register_alg(&nx_ccm_aes_alg); +	if (rc) +		goto out_unreg_gcm4106; + +	rc = crypto_register_alg(&nx_ccm4309_aes_alg); +	if (rc) +		goto out_unreg_ccm; + +	rc = crypto_register_shash(&nx_shash_sha256_alg); +	if (rc) +		goto out_unreg_ccm4309; + +	rc = crypto_register_shash(&nx_shash_sha512_alg); +	if (rc) +		goto out_unreg_s256; + +	rc = crypto_register_shash(&nx_shash_aes_xcbc_alg); +	if (rc) +		goto out_unreg_s512; + +	nx_driver.of.status = NX_OKAY; + +	goto out; + +out_unreg_s512: +	crypto_unregister_shash(&nx_shash_sha512_alg); +out_unreg_s256: +	crypto_unregister_shash(&nx_shash_sha256_alg); +out_unreg_ccm4309: +	crypto_unregister_alg(&nx_ccm4309_aes_alg); +out_unreg_ccm: +	crypto_unregister_alg(&nx_ccm_aes_alg); +out_unreg_gcm4106: +	crypto_unregister_alg(&nx_gcm4106_aes_alg); +out_unreg_gcm: +	crypto_unregister_alg(&nx_gcm_aes_alg); +out_unreg_ctr3686: +	crypto_unregister_alg(&nx_ctr3686_aes_alg); +out_unreg_ctr: +	crypto_unregister_alg(&nx_ctr_aes_alg); +out_unreg_cbc: +	crypto_unregister_alg(&nx_cbc_aes_alg); +out_unreg_ecb: +	crypto_unregister_alg(&nx_ecb_aes_alg); +out: +	return rc; +} + +/** + * nx_crypto_ctx_init - create and initialize a crypto api context + * + * @nx_ctx: the crypto api context + * @fc: function code for the context + * @mode: the function code specific mode for this context + */ +static int nx_crypto_ctx_init(struct nx_crypto_ctx *nx_ctx, u32 fc, u32 mode) +{ +	if (nx_driver.of.status != NX_OKAY) { +		pr_err("Attempt to initialize NX crypto context while device " +		       "is not available!\n"); +		return -ENODEV; +	} + +	/* we need an extra page for csbcpb_aead for these modes */ +	if (mode == NX_MODE_AES_GCM || mode == NX_MODE_AES_CCM) +		nx_ctx->kmem_len = (4 * NX_PAGE_SIZE) + +				   sizeof(struct nx_csbcpb); +	else +		nx_ctx->kmem_len = (3 * NX_PAGE_SIZE) + +				   sizeof(struct nx_csbcpb); + +	nx_ctx->kmem = kmalloc(nx_ctx->kmem_len, GFP_KERNEL); +	if (!nx_ctx->kmem) +		return -ENOMEM; + +	/* the csbcpb and scatterlists must be 4K aligned pages */ +	nx_ctx->csbcpb = (struct nx_csbcpb *)(round_up((u64)nx_ctx->kmem, +						       (u64)NX_PAGE_SIZE)); +	nx_ctx->in_sg = (struct nx_sg *)((u8 *)nx_ctx->csbcpb + NX_PAGE_SIZE); +	nx_ctx->out_sg = (struct nx_sg *)((u8 *)nx_ctx->in_sg + NX_PAGE_SIZE); + +	if (mode == NX_MODE_AES_GCM || mode == NX_MODE_AES_CCM) +		nx_ctx->csbcpb_aead = +			(struct nx_csbcpb *)((u8 *)nx_ctx->out_sg + +					     NX_PAGE_SIZE); + +	/* give each context a pointer to global stats and their OF +	 * properties */ +	nx_ctx->stats = &nx_driver.stats; +	memcpy(nx_ctx->props, nx_driver.of.ap[fc][mode], +	       sizeof(struct alg_props) * 3); + +	return 0; +} + +/* entry points from the crypto tfm initializers */ +int nx_crypto_ctx_aes_ccm_init(struct crypto_tfm *tfm) +{ +	return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, +				  NX_MODE_AES_CCM); +} + +int nx_crypto_ctx_aes_gcm_init(struct crypto_tfm *tfm) +{ +	return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, +				  NX_MODE_AES_GCM); +} + +int nx_crypto_ctx_aes_ctr_init(struct crypto_tfm *tfm) +{ +	return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, +				  NX_MODE_AES_CTR); +} + +int nx_crypto_ctx_aes_cbc_init(struct crypto_tfm *tfm) +{ +	return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, +				  NX_MODE_AES_CBC); +} + +int nx_crypto_ctx_aes_ecb_init(struct crypto_tfm *tfm) +{ +	return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, +				  NX_MODE_AES_ECB); +} + +int nx_crypto_ctx_sha_init(struct crypto_tfm *tfm) +{ +	return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_SHA, NX_MODE_SHA); +} + +int nx_crypto_ctx_aes_xcbc_init(struct crypto_tfm *tfm) +{ +	return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, +				  NX_MODE_AES_XCBC_MAC); +} + +/** + * nx_crypto_ctx_exit - destroy a crypto api context + * + * @tfm: the crypto transform pointer for the context + * + * As crypto API contexts are destroyed, this exit hook is called to free the + * memory associated with it. + */ +void nx_crypto_ctx_exit(struct crypto_tfm *tfm) +{ +	struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); + +	kzfree(nx_ctx->kmem); +	nx_ctx->csbcpb = NULL; +	nx_ctx->csbcpb_aead = NULL; +	nx_ctx->in_sg = NULL; +	nx_ctx->out_sg = NULL; +} + +static int __devinit nx_probe(struct vio_dev *viodev, +			      const struct vio_device_id *id) +{ +	dev_dbg(&viodev->dev, "driver probed: %s resource id: 0x%x\n", +		viodev->name, viodev->resource_id); + +	if (nx_driver.viodev) { +		dev_err(&viodev->dev, "%s: Attempt to register more than one " +			"instance of the hardware\n", __func__); +		return -EINVAL; +	} + +	nx_driver.viodev = viodev; + +	nx_of_init(&viodev->dev, &nx_driver.of); + +	return nx_register_algs(); +} + +static int __devexit nx_remove(struct vio_dev *viodev) +{ +	dev_dbg(&viodev->dev, "entering nx_remove for UA 0x%x\n", +		viodev->unit_address); + +	if (nx_driver.of.status == NX_OKAY) { +		NX_DEBUGFS_FINI(&nx_driver); + +		crypto_unregister_alg(&nx_ccm_aes_alg); +		crypto_unregister_alg(&nx_ccm4309_aes_alg); +		crypto_unregister_alg(&nx_gcm_aes_alg); +		crypto_unregister_alg(&nx_gcm4106_aes_alg); +		crypto_unregister_alg(&nx_ctr_aes_alg); +		crypto_unregister_alg(&nx_ctr3686_aes_alg); +		crypto_unregister_alg(&nx_cbc_aes_alg); +		crypto_unregister_alg(&nx_ecb_aes_alg); +		crypto_unregister_shash(&nx_shash_sha256_alg); +		crypto_unregister_shash(&nx_shash_sha512_alg); +		crypto_unregister_shash(&nx_shash_aes_xcbc_alg); +	} + +	return 0; +} + + +/* module wide initialization/cleanup */ +static int __init nx_init(void) +{ +	return vio_register_driver(&nx_driver.viodriver); +} + +static void __exit nx_fini(void) +{ +	vio_unregister_driver(&nx_driver.viodriver); +} + +static struct vio_device_id nx_crypto_driver_ids[] __devinitdata = { +	{ "ibm,sym-encryption-v1", "ibm,sym-encryption" }, +	{ "", "" } +}; +MODULE_DEVICE_TABLE(vio, nx_crypto_driver_ids); + +/* driver state structure */ +struct nx_crypto_driver nx_driver = { +	.viodriver = { +		.id_table = nx_crypto_driver_ids, +		.probe = nx_probe, +		.remove = nx_remove, +		.name  = NX_NAME, +	}, +}; + +module_init(nx_init); +module_exit(nx_fini); + +MODULE_AUTHOR("Kent Yoder <yoder1@us.ibm.com>"); +MODULE_DESCRIPTION(NX_STRING); +MODULE_LICENSE("GPL"); +MODULE_VERSION(NX_VERSION); diff --git a/drivers/crypto/nx/nx.h b/drivers/crypto/nx/nx.h new file mode 100644 index 000000000000..3232b182dd28 --- /dev/null +++ b/drivers/crypto/nx/nx.h @@ -0,0 +1,193 @@ + +#ifndef __NX_H__ +#define __NX_H__ + +#define NX_NAME		"nx-crypto" +#define NX_STRING	"IBM Power7+ Nest Accelerator Crypto Driver" +#define NX_VERSION	"1.0" + +static const char nx_driver_string[] = NX_STRING; +static const char nx_driver_version[] = NX_VERSION; + +/* a scatterlist in the format PHYP is expecting */ +struct nx_sg { +	u64 addr; +	u32 rsvd; +	u32 len; +} __attribute((packed)); + +#define NX_PAGE_SIZE		(4096) +#define NX_MAX_SG_ENTRIES	(NX_PAGE_SIZE/(sizeof(struct nx_sg))) + +enum nx_status { +	NX_DISABLED, +	NX_WAITING, +	NX_OKAY +}; + +/* msc_triplet and max_sync_cop are used only to assist in parsing the + * openFirmware property */ +struct msc_triplet { +	u32 keybitlen; +	u32 databytelen; +	u32 sglen; +} __packed; + +struct max_sync_cop { +	u32 fc; +	u32 mode; +	u32 triplets; +	struct msc_triplet trip[0]; +} __packed; + +struct alg_props { +	u32 databytelen; +	u32 sglen; +}; + +#define NX_OF_FLAG_MAXSGLEN_SET		(1) +#define NX_OF_FLAG_STATUS_SET		(2) +#define NX_OF_FLAG_MAXSYNCCOP_SET	(4) +#define NX_OF_FLAG_MASK_READY		(NX_OF_FLAG_MAXSGLEN_SET | \ +					 NX_OF_FLAG_STATUS_SET |   \ +					 NX_OF_FLAG_MAXSYNCCOP_SET) +struct nx_of { +	u32 flags; +	u32 max_sg_len; +	enum nx_status status; +	struct alg_props ap[NX_MAX_FC][NX_MAX_MODE][3]; +}; + +struct nx_stats { +	atomic_t aes_ops; +	atomic64_t aes_bytes; +	atomic_t sha256_ops; +	atomic64_t sha256_bytes; +	atomic_t sha512_ops; +	atomic64_t sha512_bytes; + +	atomic_t sync_ops; + +	atomic_t errors; +	atomic_t last_error; +	atomic_t last_error_pid; +}; + +struct nx_debugfs { +	struct dentry *dfs_root; +	struct dentry *dfs_aes_ops, *dfs_aes_bytes; +	struct dentry *dfs_sha256_ops, *dfs_sha256_bytes; +	struct dentry *dfs_sha512_ops, *dfs_sha512_bytes; +	struct dentry *dfs_errors, *dfs_last_error, *dfs_last_error_pid; +}; + +struct nx_crypto_driver { +	struct nx_stats    stats; +	struct nx_of       of; +	struct vio_dev    *viodev; +	struct vio_driver  viodriver; +	struct nx_debugfs  dfs; +}; + +#define NX_GCM4106_NONCE_LEN		(4) +#define NX_GCM_CTR_OFFSET		(12) +struct nx_gcm_priv { +	u8 iv[16]; +	u8 iauth_tag[16]; +	u8 nonce[NX_GCM4106_NONCE_LEN]; +}; + +#define NX_CCM_AES_KEY_LEN		(16) +#define NX_CCM4309_AES_KEY_LEN		(19) +#define NX_CCM4309_NONCE_LEN		(3) +struct nx_ccm_priv { +	u8 iv[16]; +	u8 b0[16]; +	u8 iauth_tag[16]; +	u8 oauth_tag[16]; +	u8 nonce[NX_CCM4309_NONCE_LEN]; +}; + +struct nx_xcbc_priv { +	u8 key[16]; +}; + +struct nx_ctr_priv { +	u8 iv[16]; +}; + +struct nx_crypto_ctx { +	void *kmem;		  /* unaligned, kmalloc'd buffer */ +	size_t kmem_len;	  /* length of kmem */ +	struct nx_csbcpb *csbcpb; /* aligned page given to phyp @ hcall time */ +	struct vio_pfo_op op;     /* operation struct with hcall parameters */ +	struct nx_csbcpb *csbcpb_aead; /* secondary csbcpb used by AEAD algs */ +	struct vio_pfo_op op_aead;/* operation struct for csbcpb_aead */ + +	struct nx_sg *in_sg;      /* aligned pointer into kmem to an sg list */ +	struct nx_sg *out_sg;     /* aligned pointer into kmem to an sg list */ + +	struct alg_props *ap;	  /* pointer into props based on our key size */ +	struct alg_props props[3];/* openFirmware properties for requests */ +	struct nx_stats *stats;   /* pointer into an nx_crypto_driver for stats +				     reporting */ + +	union { +		struct nx_gcm_priv gcm; +		struct nx_ccm_priv ccm; +		struct nx_xcbc_priv xcbc; +		struct nx_ctr_priv ctr; +	} priv; +}; + +/* prototypes */ +int nx_crypto_ctx_aes_ccm_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_gcm_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_xcbc_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_ctr_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_cbc_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_ecb_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_sha_init(struct crypto_tfm *tfm); +void nx_crypto_ctx_exit(struct crypto_tfm *tfm); +void nx_ctx_init(struct nx_crypto_ctx *nx_ctx, unsigned int function); +int nx_hcall_sync(struct nx_crypto_ctx *ctx, struct vio_pfo_op *op, +		  u32 may_sleep); +struct nx_sg *nx_build_sg_list(struct nx_sg *, u8 *, unsigned int, u32); +int nx_build_sg_lists(struct nx_crypto_ctx *, struct blkcipher_desc *, +		      struct scatterlist *, struct scatterlist *, unsigned int, +		      u8 *); +struct nx_sg *nx_walk_and_build(struct nx_sg *, unsigned int, +				struct scatterlist *, unsigned int, +				unsigned int); + +#ifdef CONFIG_DEBUG_FS +#define NX_DEBUGFS_INIT(drv)	nx_debugfs_init(drv) +#define NX_DEBUGFS_FINI(drv)	nx_debugfs_fini(drv) + +int nx_debugfs_init(struct nx_crypto_driver *); +void nx_debugfs_fini(struct nx_crypto_driver *); +#else +#define NX_DEBUGFS_INIT(drv)	(0) +#define NX_DEBUGFS_FINI(drv)	(0) +#endif + +#define NX_PAGE_NUM(x)		((u64)(x) & 0xfffffffffffff000ULL) + +extern struct crypto_alg nx_cbc_aes_alg; +extern struct crypto_alg nx_ecb_aes_alg; +extern struct crypto_alg nx_gcm_aes_alg; +extern struct crypto_alg nx_gcm4106_aes_alg; +extern struct crypto_alg nx_ctr_aes_alg; +extern struct crypto_alg nx_ctr3686_aes_alg; +extern struct crypto_alg nx_ccm_aes_alg; +extern struct crypto_alg nx_ccm4309_aes_alg; +extern struct shash_alg nx_shash_aes_xcbc_alg; +extern struct shash_alg nx_shash_sha512_alg; +extern struct shash_alg nx_shash_sha256_alg; + +extern struct nx_crypto_driver nx_driver; + +#define SCATTERWALK_TO_SG	1 +#define SCATTERWALK_FROM_SG	0 + +#endif diff --git a/drivers/crypto/nx/nx_csbcpb.h b/drivers/crypto/nx/nx_csbcpb.h new file mode 100644 index 000000000000..a304f956d6f8 --- /dev/null +++ b/drivers/crypto/nx/nx_csbcpb.h @@ -0,0 +1,205 @@ + +#ifndef __NX_CSBCPB_H__ +#define __NX_CSBCPB_H__ + +struct cop_symcpb_aes_ecb { +	u8 key[32]; +	u8 __rsvd[80]; +} __packed; + +struct cop_symcpb_aes_cbc { +	u8 iv[16]; +	u8 key[32]; +	u8 cv[16]; +	u32 spbc; +	u8 __rsvd[44]; +} __packed; + +struct cop_symcpb_aes_gca { +	u8 in_pat[16]; +	u8 key[32]; +	u8 out_pat[16]; +	u32 spbc; +	u8 __rsvd[44]; +} __packed; + +struct cop_symcpb_aes_gcm { +	u8 in_pat_or_aad[16]; +	u8 iv_or_cnt[16]; +	u64 bit_length_aad; +	u64 bit_length_data; +	u8 in_s0[16]; +	u8 key[32]; +	u8 __rsvd1[16]; +	u8 out_pat_or_mac[16]; +	u8 out_s0[16]; +	u8 out_cnt[16]; +	u32 spbc; +	u8 __rsvd2[12]; +} __packed; + +struct cop_symcpb_aes_ctr { +	u8 iv[16]; +	u8 key[32]; +	u8 cv[16]; +	u32 spbc; +	u8 __rsvd2[44]; +} __packed; + +struct cop_symcpb_aes_cca { +	u8 b0[16]; +	u8 b1[16]; +	u8 key[16]; +	u8 out_pat_or_b0[16]; +	u32 spbc; +	u8 __rsvd[44]; +} __packed; + +struct cop_symcpb_aes_ccm { +	u8 in_pat_or_b0[16]; +	u8 iv_or_ctr[16]; +	u8 in_s0[16]; +	u8 key[16]; +	u8 __rsvd1[48]; +	u8 out_pat_or_mac[16]; +	u8 out_s0[16]; +	u8 out_ctr[16]; +	u32 spbc; +	u8 __rsvd2[12]; +} __packed; + +struct cop_symcpb_aes_xcbc { +	u8 cv[16]; +	u8 key[16]; +	u8 __rsvd1[16]; +	u8 out_cv_mac[16]; +	u32 spbc; +	u8 __rsvd2[44]; +} __packed; + +struct cop_symcpb_sha256 { +	u64 message_bit_length; +	u64 __rsvd1; +	u8 input_partial_digest[32]; +	u8 message_digest[32]; +	u32 spbc; +	u8 __rsvd2[44]; +} __packed; + +struct cop_symcpb_sha512 { +	u64 message_bit_length_hi; +	u64 message_bit_length_lo; +	u8 input_partial_digest[64]; +	u8 __rsvd1[32]; +	u8 message_digest[64]; +	u32 spbc; +	u8 __rsvd2[76]; +} __packed; + +#define NX_FDM_INTERMEDIATE		0x01 +#define NX_FDM_CONTINUATION		0x02 +#define NX_FDM_ENDE_ENCRYPT		0x80 + +#define NX_CPB_FDM(c)			((c)->cpb.hdr.fdm) +#define NX_CPB_KS_DS(c)			((c)->cpb.hdr.ks_ds) + +#define NX_CPB_KEY_SIZE(c)		(NX_CPB_KS_DS(c) >> 4) +#define NX_CPB_SET_KEY_SIZE(c, x)	NX_CPB_KS_DS(c) |= ((x) << 4) +#define NX_CPB_SET_DIGEST_SIZE(c, x)	NX_CPB_KS_DS(c) |= (x) + +struct cop_symcpb_header { +	u8 mode; +	u8 fdm; +	u8 ks_ds; +	u8 pad_byte; +	u8 __rsvd[12]; +} __packed; + +struct cop_parameter_block { +	struct cop_symcpb_header hdr; +	union { +		struct cop_symcpb_aes_ecb  aes_ecb; +		struct cop_symcpb_aes_cbc  aes_cbc; +		struct cop_symcpb_aes_gca  aes_gca; +		struct cop_symcpb_aes_gcm  aes_gcm; +		struct cop_symcpb_aes_cca  aes_cca; +		struct cop_symcpb_aes_ccm  aes_ccm; +		struct cop_symcpb_aes_ctr  aes_ctr; +		struct cop_symcpb_aes_xcbc aes_xcbc; +		struct cop_symcpb_sha256   sha256; +		struct cop_symcpb_sha512   sha512; +	}; +} __packed; + +#define NX_CSB_VALID_BIT	0x80 + +/* co-processor status block */ +struct cop_status_block { +	u8 valid; +	u8 crb_seq_number; +	u8 completion_code; +	u8 completion_extension; +	u32 processed_byte_count; +	u64 address; +} __packed; + +/* Nest accelerator workbook section 4.4 */ +struct nx_csbcpb { +	unsigned char __rsvd[112]; +	struct cop_status_block csb; +	struct cop_parameter_block cpb; +} __packed; + +/* nx_csbcpb related definitions */ +#define NX_MODE_AES_ECB			0 +#define NX_MODE_AES_CBC			1 +#define NX_MODE_AES_GMAC		2 +#define NX_MODE_AES_GCA			3 +#define NX_MODE_AES_GCM			4 +#define NX_MODE_AES_CCA			5 +#define NX_MODE_AES_CCM			6 +#define NX_MODE_AES_CTR			7 +#define NX_MODE_AES_XCBC_MAC		20 +#define NX_MODE_SHA			0 +#define NX_MODE_SHA_HMAC		1 +#define NX_MODE_AES_CBC_HMAC_ETA	8 +#define NX_MODE_AES_CBC_HMAC_ATE	9 +#define NX_MODE_AES_CBC_HMAC_EAA	10 +#define NX_MODE_AES_CTR_HMAC_ETA	12 +#define NX_MODE_AES_CTR_HMAC_ATE	13 +#define NX_MODE_AES_CTR_HMAC_EAA	14 + +#define NX_FDM_CI_FULL		0 +#define NX_FDM_CI_FIRST		1 +#define NX_FDM_CI_LAST		2 +#define NX_FDM_CI_MIDDLE	3 + +#define NX_FDM_PR_NONE		0 +#define NX_FDM_PR_PAD		1 + +#define NX_KS_AES_128		1 +#define NX_KS_AES_192		2 +#define NX_KS_AES_256		3 + +#define NX_DS_SHA256		2 +#define NX_DS_SHA512		3 + +#define NX_FC_AES		0 +#define NX_FC_SHA		2 +#define NX_FC_AES_HMAC		6 + +#define NX_MAX_FC		(NX_FC_AES_HMAC + 1) +#define NX_MAX_MODE		(NX_MODE_AES_XCBC_MAC + 1) + +#define HCOP_FC_AES          NX_FC_AES +#define HCOP_FC_SHA          NX_FC_SHA +#define HCOP_FC_AES_HMAC     NX_FC_AES_HMAC + +/* indices into the array of algorithm properties */ +#define NX_PROPS_AES_128		0 +#define NX_PROPS_AES_192		1 +#define NX_PROPS_AES_256		2 +#define NX_PROPS_SHA256			1 +#define NX_PROPS_SHA512			2 + +#endif diff --git a/drivers/crypto/nx/nx_debugfs.c b/drivers/crypto/nx/nx_debugfs.c new file mode 100644 index 000000000000..7ab2e8dcd9b4 --- /dev/null +++ b/drivers/crypto/nx/nx_debugfs.c @@ -0,0 +1,103 @@ +/** + * debugfs routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * 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; version 2 only. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <yoder1@us.ibm.com> + */ + +#include <linux/device.h> +#include <linux/kobject.h> +#include <linux/string.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/crypto.h> +#include <crypto/hash.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + +#ifdef CONFIG_DEBUG_FS + +/* + * debugfs + * + * For documentation on these attributes, please see: + * + * Documentation/ABI/testing/debugfs-pfo-nx-crypto + */ + +int nx_debugfs_init(struct nx_crypto_driver *drv) +{ +	struct nx_debugfs *dfs = &drv->dfs; + +	dfs->dfs_root = debugfs_create_dir(NX_NAME, NULL); + +	dfs->dfs_aes_ops = +		debugfs_create_u32("aes_ops", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, (u32 *)&drv->stats.aes_ops); +	dfs->dfs_sha256_ops = +		debugfs_create_u32("sha256_ops", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, +				   (u32 *)&drv->stats.sha256_ops); +	dfs->dfs_sha512_ops = +		debugfs_create_u32("sha512_ops", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, +				   (u32 *)&drv->stats.sha512_ops); +	dfs->dfs_aes_bytes = +		debugfs_create_u64("aes_bytes", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, +				   (u64 *)&drv->stats.aes_bytes); +	dfs->dfs_sha256_bytes = +		debugfs_create_u64("sha256_bytes", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, +				   (u64 *)&drv->stats.sha256_bytes); +	dfs->dfs_sha512_bytes = +		debugfs_create_u64("sha512_bytes", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, +				   (u64 *)&drv->stats.sha512_bytes); +	dfs->dfs_errors = +		debugfs_create_u32("errors", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, (u32 *)&drv->stats.errors); +	dfs->dfs_last_error = +		debugfs_create_u32("last_error", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, +				   (u32 *)&drv->stats.last_error); +	dfs->dfs_last_error_pid = +		debugfs_create_u32("last_error_pid", +				   S_IRUSR | S_IRGRP | S_IROTH, +				   dfs->dfs_root, +				   (u32 *)&drv->stats.last_error_pid); +	return 0; +} + +void +nx_debugfs_fini(struct nx_crypto_driver *drv) +{ +	debugfs_remove_recursive(drv->dfs.dfs_root); +} + +#endif diff --git a/drivers/i2c/busses/i2c-powermac.c b/drivers/i2c/busses/i2c-powermac.c index 7b397c6f607e..31c47e18d83c 100644 --- a/drivers/i2c/busses/i2c-powermac.c +++ b/drivers/i2c/busses/i2c-powermac.c @@ -227,6 +227,72 @@ static int __devexit i2c_powermac_remove(struct platform_device *dev)  	return 0;  } +static void __devinit i2c_powermac_register_devices(struct i2c_adapter *adap, +						    struct pmac_i2c_bus *bus) +{ +	struct i2c_client *newdev; +	struct device_node *node; + +	for_each_child_of_node(adap->dev.of_node, node) { +		struct i2c_board_info info = {}; +		struct dev_archdata dev_ad = {}; +		const __be32 *reg; +		char tmp[16]; +		u32 addr; +		int len; + +		/* Get address & channel */ +		reg = of_get_property(node, "reg", &len); +		if (!reg || (len < sizeof(int))) { +			dev_err(&adap->dev, "i2c-powermac: invalid reg on %s\n", +				node->full_name); +			continue; +		} +		addr = be32_to_cpup(reg); + +		/* Multibus setup, check channel */ +		if (!pmac_i2c_match_adapter(node, adap)) +			continue; + +		dev_dbg(&adap->dev, "i2c-powermac: register %s\n", +			node->full_name); + +		/* Make up a modalias. Note: we to _NOT_ want the standard +		 * i2c drivers to match with any of our powermac stuff +		 * unless they have been specifically modified to handle +		 * it on a case by case basis. For example, for thermal +		 * control, things like lm75 etc... shall match with their +		 * corresponding windfarm drivers, _NOT_ the generic ones, +		 * so we force a prefix of AAPL, onto the modalias to +		 * make that happen +		 */ +		if (of_modalias_node(node, tmp, sizeof(tmp)) < 0) { +			dev_err(&adap->dev, "i2c-powermac: modalias failure" +				" on %s\n", node->full_name); +			continue; +		} +		snprintf(info.type, sizeof(info.type), "MAC,%s", tmp); + +		/* Fill out the rest of the info structure */ +		info.addr = (addr & 0xff) >> 1; +		info.irq = irq_of_parse_and_map(node, 0); +		info.of_node = of_node_get(node); +		info.archdata = &dev_ad; + +		newdev = i2c_new_device(adap, &info); +		if (!newdev) { +			dev_err(&adap->dev, "i2c-powermac: Failure to register" +				" %s\n", node->full_name); +			of_node_put(node); +			/* We do not dispose of the interrupt mapping on +			 * purpose. It's not necessary (interrupt cannot be +			 * re-used) and somebody else might have grabbed it +			 * via direct DT lookup so let's not bother +			 */ +			continue; +		} +	} +}  static int __devinit i2c_powermac_probe(struct platform_device *dev)  { @@ -272,6 +338,7 @@ static int __devinit i2c_powermac_probe(struct platform_device *dev)  	adapter->algo = &i2c_powermac_algorithm;  	i2c_set_adapdata(adapter, bus);  	adapter->dev.parent = &dev->dev; +	adapter->dev.of_node = dev->dev.of_node;  	rc = i2c_add_adapter(adapter);  	if (rc) {  		printk(KERN_ERR "i2c-powermac: Adapter %s registration " @@ -281,33 +348,10 @@ static int __devinit i2c_powermac_probe(struct platform_device *dev)  	printk(KERN_INFO "PowerMac i2c bus %s registered\n", adapter->name); -	if (!strncmp(basename, "uni-n", 5)) { -		struct device_node *np; -		const u32 *prop; -		struct i2c_board_info info; - -		/* Instantiate I2C motion sensor if present */ -		np = of_find_node_by_name(NULL, "accelerometer"); -		if (np && of_device_is_compatible(np, "AAPL,accelerometer_1") && -		    (prop = of_get_property(np, "reg", NULL))) { -			int i2c_bus; -			const char *tmp_bus; - -			/* look for bus either using "reg" or by path */ -			tmp_bus = strstr(np->full_name, "/i2c-bus@"); -			if (tmp_bus) -				i2c_bus = *(tmp_bus + 9) - '0'; -			else -				i2c_bus = ((*prop) >> 8) & 0x0f; - -			if (pmac_i2c_get_channel(bus) == i2c_bus) { -				memset(&info, 0, sizeof(struct i2c_board_info)); -				info.addr = ((*prop) & 0xff) >> 1; -				strlcpy(info.type, "ams", I2C_NAME_SIZE); -				i2c_new_device(adapter, &info); -			} -		} -	} +	/* Cannot use of_i2c_register_devices() due to Apple device-tree +	 * funkyness +	 */ +	i2c_powermac_register_devices(adapter, bus);  	return rc;  } diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig index fa51af11c6f1..a555da64224e 100644 --- a/drivers/macintosh/Kconfig +++ b/drivers/macintosh/Kconfig @@ -204,11 +204,14 @@ config THERM_ADT746X  	  better fan behaviour by default, and some manual control.  config THERM_PM72 -	tristate "Support for thermal management on PowerMac G5" +	tristate "Support for thermal management on PowerMac G5 (AGP)"  	depends on I2C && I2C_POWERMAC && PPC_PMAC64 +	default n  	help  	  This driver provides thermostat and fan control for the desktop -	  G5 machines.  +	  G5 machines. + +	  This is deprecated, use windfarm instead.  config WINDFARM  	tristate "New PowerMac thermal control infrastructure" @@ -221,6 +224,22 @@ config WINDFARM_PM81  	help  	  This driver provides thermal control for the iMacG5 +config WINDFARM_PM72 +	tristate "Support for thermal management on PowerMac G5 (AGP)" +	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU +	select I2C_POWERMAC +	help +	  This driver provides thermal control for the PowerMac G5 +	  "AGP" variants (PowerMac 7,2 and 7,3) + +config WINDFARM_RM31 +	tristate "Support for thermal management on Xserve G5" +	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU +	select I2C_POWERMAC +	help +	  This driver provides thermal control for the Xserve G5 +	  (RackMac3,1) +  config WINDFARM_PM91  	tristate "Support for thermal management on PowerMac9,1"  	depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile index 6652a6ebb6fa..6753b65f8ede 100644 --- a/drivers/macintosh/Makefile +++ b/drivers/macintosh/Makefile @@ -29,6 +29,20 @@ obj-$(CONFIG_THERM_PM72)	+= therm_pm72.o  obj-$(CONFIG_THERM_WINDTUNNEL)	+= therm_windtunnel.o  obj-$(CONFIG_THERM_ADT746X)	+= therm_adt746x.o  obj-$(CONFIG_WINDFARM)	        += windfarm_core.o +obj-$(CONFIG_WINDFARM_PM72)     += windfarm_fcu_controls.o \ +				   windfarm_ad7417_sensor.o \ +				   windfarm_lm75_sensor.o \ +				   windfarm_max6690_sensor.o \ +				   windfarm_pid.o \ +				   windfarm_cpufreq_clamp.o \ +				   windfarm_pm72.o +obj-$(CONFIG_WINDFARM_RM31)     += windfarm_fcu_controls.o \ +				   windfarm_ad7417_sensor.o \ +				   windfarm_lm75_sensor.o \ +				   windfarm_lm87_sensor.o \ +				   windfarm_pid.o \ +				   windfarm_cpufreq_clamp.o \ +				   windfarm_rm31.o  obj-$(CONFIG_WINDFARM_PM81)     += windfarm_smu_controls.o \  				   windfarm_smu_sensors.o \  				   windfarm_lm75_sensor.o windfarm_pid.o \ diff --git a/drivers/macintosh/ams/ams-i2c.c b/drivers/macintosh/ams/ams-i2c.c index abeecd27b484..978eda8d6678 100644 --- a/drivers/macintosh/ams/ams-i2c.c +++ b/drivers/macintosh/ams/ams-i2c.c @@ -65,7 +65,7 @@ static int ams_i2c_probe(struct i2c_client *client,  static int ams_i2c_remove(struct i2c_client *client);  static const struct i2c_device_id ams_id[] = { -	{ "ams", 0 }, +	{ "MAC,accelerometer_1", 0 },  	{ }  };  MODULE_DEVICE_TABLE(i2c, ams_id); diff --git a/drivers/macintosh/therm_adt746x.c b/drivers/macintosh/therm_adt746x.c index fc71723cbc48..f433521a6f9d 100644 --- a/drivers/macintosh/therm_adt746x.c +++ b/drivers/macintosh/therm_adt746x.c @@ -47,7 +47,7 @@ static u8 FAN_SPD_SET[2] = {0x30, 0x31};  static u8 default_limits_local[3] = {70, 50, 70};    /* local, sensor1, sensor2 */  static u8 default_limits_chip[3] = {80, 65, 80};    /* local, sensor1, sensor2 */ -static const char *sensor_location[3]; +static const char *sensor_location[3] = { "?", "?", "?" };  static int limit_adjust;  static int fan_speed = -1; @@ -79,18 +79,16 @@ struct thermostat {  	int			last_speed[2];  	int			last_var[2];  	int			pwm_inv[2]; +	struct task_struct	*thread; +	struct platform_device	*pdev; +	enum { +		ADT7460, +		ADT7467 +	}			type;  }; -static enum {ADT7460, ADT7467} therm_type; -static int therm_bus, therm_address; -static struct platform_device * of_dev; -static struct thermostat* thermostat; -static struct task_struct *thread_therm = NULL; -  static void write_both_fan_speed(struct thermostat *th, int speed);  static void write_fan_speed(struct thermostat *th, int speed, int fan); -static void thermostat_create_files(void); -static void thermostat_remove_files(void);  static int  write_reg(struct thermostat* th, int reg, u8 data) @@ -126,66 +124,6 @@ read_reg(struct thermostat* th, int reg)  	return data;  } -static struct i2c_driver thermostat_driver; - -static int -attach_thermostat(struct i2c_adapter *adapter) -{ -	unsigned long bus_no; -	struct i2c_board_info info; -	struct i2c_client *client; - -	if (strncmp(adapter->name, "uni-n", 5)) -		return -ENODEV; -	bus_no = simple_strtoul(adapter->name + 6, NULL, 10); -	if (bus_no != therm_bus) -		return -ENODEV; - -	memset(&info, 0, sizeof(struct i2c_board_info)); -	strlcpy(info.type, "therm_adt746x", I2C_NAME_SIZE); -	info.addr = therm_address; -	client = i2c_new_device(adapter, &info); -	if (!client) -		return -ENODEV; - -	/* -	 * Let i2c-core delete that device on driver removal. -	 * This is safe because i2c-core holds the core_lock mutex for us. -	 */ -	list_add_tail(&client->detected, &thermostat_driver.clients); -	return 0; -} - -static int -remove_thermostat(struct i2c_client *client) -{ -	struct thermostat *th = i2c_get_clientdata(client); -	int i; -	 -	thermostat_remove_files(); - -	if (thread_therm != NULL) { -		kthread_stop(thread_therm); -	} - -	printk(KERN_INFO "adt746x: Putting max temperatures back from " -			 "%d, %d, %d to %d, %d, %d\n", -		th->limits[0], th->limits[1], th->limits[2], -		th->initial_limits[0], th->initial_limits[1], -		th->initial_limits[2]); - -	for (i = 0; i < 3; i++) -		write_reg(th, LIMIT_REG[i], th->initial_limits[i]); - -	write_both_fan_speed(th, -1); - -	thermostat = NULL; - -	kfree(th); - -	return 0; -} -  static int read_fan_speed(struct thermostat *th, u8 addr)  {  	u8 tmp[2]; @@ -203,7 +141,7 @@ static int read_fan_speed(struct thermostat *th, u8 addr)  static void write_both_fan_speed(struct thermostat *th, int speed)  {  	write_fan_speed(th, speed, 0); -	if (therm_type == ADT7460) +	if (th->type == ADT7460)  		write_fan_speed(th, speed, 1);  } @@ -216,7 +154,7 @@ static void write_fan_speed(struct thermostat *th, int speed, int fan)  	else if (speed < -1)   		speed = 0; -	if (therm_type == ADT7467 && fan == 1) +	if (th->type == ADT7467 && fan == 1)  		return;  	if (th->last_speed[fan] != speed) { @@ -239,7 +177,7 @@ static void write_fan_speed(struct thermostat *th, int speed, int fan)  		write_reg(th, FAN_SPD_SET[fan], speed);  	} else {  		/* back to automatic */ -		if(therm_type == ADT7460) { +		if(th->type == ADT7460) {  			manual = read_reg(th,  				MANUAL_MODE[fan]) & (~MANUAL_MASK);  			manual &= ~INVERT_MASK; @@ -293,7 +231,7 @@ static void update_fans_speed (struct thermostat *th)  	/* we don't care about local sensor, so we start at sensor 1 */  	for (i = 1; i < 3; i++) {  		int started = 0; -		int fan_number = (therm_type == ADT7460 && i == 2); +		int fan_number = (th->type == ADT7460 && i == 2);  		int var = th->temps[i] - th->limits[i];  		if (var > -1) { @@ -370,116 +308,22 @@ static int monitor_task(void *arg)  static void set_limit(struct thermostat *th, int i)  { -		/* Set sensor1 limit higher to avoid powerdowns */ -		th->limits[i] = default_limits_chip[i] + limit_adjust; -		write_reg(th, LIMIT_REG[i], th->limits[i]); +	/* Set sensor1 limit higher to avoid powerdowns */ +	th->limits[i] = default_limits_chip[i] + limit_adjust; +	write_reg(th, LIMIT_REG[i], th->limits[i]); -		/* set our limits to normal */ -		th->limits[i] = default_limits_local[i] + limit_adjust; +	/* set our limits to normal */ +	th->limits[i] = default_limits_local[i] + limit_adjust;  } -static int probe_thermostat(struct i2c_client *client, -			    const struct i2c_device_id *id) -{ -	struct thermostat* th; -	int rc; -	int i; - -	if (thermostat) -		return 0; - -	th = kzalloc(sizeof(struct thermostat), GFP_KERNEL); -	if (!th) -		return -ENOMEM; - -	i2c_set_clientdata(client, th); -	th->clt = client; - -	rc = read_reg(th, CONFIG_REG); -	if (rc < 0) { -		dev_err(&client->dev, "Thermostat failed to read config!\n"); -		kfree(th); -		return -ENODEV; -	} - -	/* force manual control to start the fan quieter */ -	if (fan_speed == -1) -		fan_speed = 64; -	 -	if(therm_type == ADT7460) { -		printk(KERN_INFO "adt746x: ADT7460 initializing\n"); -		/* The 7460 needs to be started explicitly */ -		write_reg(th, CONFIG_REG, 1); -	} else -		printk(KERN_INFO "adt746x: ADT7467 initializing\n"); - -	for (i = 0; i < 3; i++) { -		th->initial_limits[i] = read_reg(th, LIMIT_REG[i]); -		set_limit(th, i); -	} - -	printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d" -			 " to %d, %d, %d\n", -			 th->initial_limits[0], th->initial_limits[1], -			 th->initial_limits[2], th->limits[0], th->limits[1], -			 th->limits[2]); - -	thermostat = th; - -	/* record invert bit status because fw can corrupt it after suspend */ -	th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK; -	th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK; - -	/* be sure to really write fan speed the first time */ -	th->last_speed[0] = -2; -	th->last_speed[1] = -2; -	th->last_var[0] = -80; -	th->last_var[1] = -80; - -	if (fan_speed != -1) { -		/* manual mode, stop fans */ -		write_both_fan_speed(th, 0); -	} else { -		/* automatic mode */ -		write_both_fan_speed(th, -1); -	} -	 -	thread_therm = kthread_run(monitor_task, th, "kfand"); - -	if (thread_therm == ERR_PTR(-ENOMEM)) { -		printk(KERN_INFO "adt746x: Kthread creation failed\n"); -		thread_therm = NULL; -		return -ENOMEM; -	} - -	thermostat_create_files(); - -	return 0; +#define BUILD_SHOW_FUNC_INT(name, data)				\ +static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)	\ +{								\ +	struct thermostat *th = dev_get_drvdata(dev);		\ +	return sprintf(buf, "%d\n", data);			\  } -static const struct i2c_device_id therm_adt746x_id[] = { -	{ "therm_adt746x", 0 }, -	{ } -}; - -static struct i2c_driver thermostat_driver = { -	.driver = { -		.name	= "therm_adt746x", -	}, -	.attach_adapter	= attach_thermostat, -	.probe = probe_thermostat, -	.remove = remove_thermostat, -	.id_table = therm_adt746x_id, -}; - -/*  - * Now, unfortunately, sysfs doesn't give us a nice void * we could - * pass around to the attribute functions, so we don't really have - * choice but implement a bunch of them... - * - * FIXME, it does now... - */ -#define BUILD_SHOW_FUNC_INT(name, data)				\ +#define BUILD_SHOW_FUNC_INT_LITE(name, data)				\  static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)	\  {								\  	return sprintf(buf, "%d\n", data);			\ @@ -494,22 +338,24 @@ static ssize_t show_##name(struct device *dev, struct device_attribute *attr, ch  #define BUILD_SHOW_FUNC_FAN(name, data)				\  static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf)       \  {								\ +	struct thermostat *th = dev_get_drvdata(dev);		\  	return sprintf(buf, "%d (%d rpm)\n", 			\ -		thermostat->last_speed[data],			\ -		read_fan_speed(thermostat, FAN_SPEED[data])	\ +		th->last_speed[data],				\ +		read_fan_speed(th, FAN_SPEED[data])		\  		);						\  }  #define BUILD_STORE_FUNC_DEG(name, data)			\  static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \  {								\ +	struct thermostat *th = dev_get_drvdata(dev);		\  	int val;						\  	int i;							\  	val = simple_strtol(buf, NULL, 10);			\  	printk(KERN_INFO "Adjusting limits by %d degrees\n", val);	\  	limit_adjust = val;					\  	for (i=0; i < 3; i++)					\ -		set_limit(thermostat, i);			\ +		set_limit(th, i);				\  	return n;						\  } @@ -525,20 +371,21 @@ static ssize_t store_##name(struct device *dev, struct device_attribute *attr, c  	return n;						\  } -BUILD_SHOW_FUNC_INT(sensor1_temperature,	 (read_reg(thermostat, TEMP_REG[1]))) -BUILD_SHOW_FUNC_INT(sensor2_temperature,	 (read_reg(thermostat, TEMP_REG[2]))) -BUILD_SHOW_FUNC_INT(sensor1_limit,		 thermostat->limits[1]) -BUILD_SHOW_FUNC_INT(sensor2_limit,		 thermostat->limits[2]) +BUILD_SHOW_FUNC_INT(sensor1_temperature,	 (read_reg(th, TEMP_REG[1]))) +BUILD_SHOW_FUNC_INT(sensor2_temperature,	 (read_reg(th, TEMP_REG[2]))) +BUILD_SHOW_FUNC_INT(sensor1_limit,		 th->limits[1]) +BUILD_SHOW_FUNC_INT(sensor2_limit,		 th->limits[2])  BUILD_SHOW_FUNC_STR(sensor1_location,		 sensor_location[1])  BUILD_SHOW_FUNC_STR(sensor2_location,		 sensor_location[2]) -BUILD_SHOW_FUNC_INT(specified_fan_speed, fan_speed) +BUILD_SHOW_FUNC_INT_LITE(specified_fan_speed, fan_speed) +BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed) +  BUILD_SHOW_FUNC_FAN(sensor1_fan_speed,	 0)  BUILD_SHOW_FUNC_FAN(sensor2_fan_speed,	 1) -BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed) -BUILD_SHOW_FUNC_INT(limit_adjust,	 limit_adjust) -BUILD_STORE_FUNC_DEG(limit_adjust,	 thermostat) +BUILD_SHOW_FUNC_INT_LITE(limit_adjust,	 limit_adjust) +BUILD_STORE_FUNC_DEG(limit_adjust,	 th)  static DEVICE_ATTR(sensor1_temperature,	S_IRUGO,  		   show_sensor1_temperature,NULL); @@ -564,53 +411,77 @@ static DEVICE_ATTR(sensor2_fan_speed,	S_IRUGO,  static DEVICE_ATTR(limit_adjust,	S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH,  		   show_limit_adjust,	store_limit_adjust); - -static int __init -thermostat_init(void) +static void thermostat_create_files(struct thermostat *th)  { -	struct device_node* np; -	const u32 *prop; -	int i = 0, offset = 0; +	struct device_node *np = th->clt->dev.of_node; +	struct device *dev; +	int err; -	np = of_find_node_by_name(NULL, "fan"); -	if (!np) -		return -ENODEV; -	if (of_device_is_compatible(np, "adt7460")) -		therm_type = ADT7460; -	else if (of_device_is_compatible(np, "adt7467")) -		therm_type = ADT7467; -	else { -		of_node_put(np); -		return -ENODEV; -	} +	/* To maintain ABI compatibility with userspace, create +	 * the old style platform driver and attach the attributes +	 * to it here +	 */ +	th->pdev = of_platform_device_create(np, "temperatures", NULL); +	if (!th->pdev) +		return; +	dev = &th->pdev->dev; +	dev_set_drvdata(dev, th); +	err = device_create_file(dev, &dev_attr_sensor1_temperature); +	err |= device_create_file(dev, &dev_attr_sensor2_temperature); +	err |= device_create_file(dev, &dev_attr_sensor1_limit); +	err |= device_create_file(dev, &dev_attr_sensor2_limit); +	err |= device_create_file(dev, &dev_attr_sensor1_location); +	err |= device_create_file(dev, &dev_attr_sensor2_location); +	err |= device_create_file(dev, &dev_attr_limit_adjust); +	err |= device_create_file(dev, &dev_attr_specified_fan_speed); +	err |= device_create_file(dev, &dev_attr_sensor1_fan_speed); +	if(th->type == ADT7460) +		err |= device_create_file(dev, &dev_attr_sensor2_fan_speed); +	if (err) +		printk(KERN_WARNING +			"Failed to create temperature attribute file(s).\n"); +} -	prop = of_get_property(np, "hwsensor-params-version", NULL); -	printk(KERN_INFO "adt746x: version %d (%ssupported)\n", *prop, -			 (*prop == 1)?"":"un"); -	if (*prop != 1) { -		of_node_put(np); -		return -ENODEV; -	} +static void thermostat_remove_files(struct thermostat *th) +{ +	struct device *dev; -	prop = of_get_property(np, "reg", NULL); -	if (!prop) { -		of_node_put(np); -		return -ENODEV; -	} +	if (!th->pdev) +		return; +	dev = &th->pdev->dev; +	device_remove_file(dev, &dev_attr_sensor1_temperature); +	device_remove_file(dev, &dev_attr_sensor2_temperature); +	device_remove_file(dev, &dev_attr_sensor1_limit); +	device_remove_file(dev, &dev_attr_sensor2_limit); +	device_remove_file(dev, &dev_attr_sensor1_location); +	device_remove_file(dev, &dev_attr_sensor2_location); +	device_remove_file(dev, &dev_attr_limit_adjust); +	device_remove_file(dev, &dev_attr_specified_fan_speed); +	device_remove_file(dev, &dev_attr_sensor1_fan_speed);	 +	if (th->type == ADT7460) +		device_remove_file(dev, &dev_attr_sensor2_fan_speed); +	of_device_unregister(th->pdev); -	/* look for bus either by path or using "reg" */ -	if (strstr(np->full_name, "/i2c-bus@") != NULL) { -		const char *tmp_bus = (strstr(np->full_name, "/i2c-bus@") + 9); -		therm_bus = tmp_bus[0]-'0'; -	} else { -		therm_bus = ((*prop) >> 8) & 0x0f; -	} +} -	therm_address = ((*prop) & 0xff) >> 1; +static int probe_thermostat(struct i2c_client *client, +			    const struct i2c_device_id *id) +{ +	struct device_node *np = client->dev.of_node; +	struct thermostat* th; +	const __be32 *prop; +	int i, rc, vers, offset = 0; -	printk(KERN_INFO "adt746x: Thermostat bus: %d, address: 0x%02x, " -			 "limit_adjust: %d, fan_speed: %d\n", -			 therm_bus, therm_address, limit_adjust, fan_speed); +	if (!np) +		return -ENXIO; +	prop = of_get_property(np, "hwsensor-params-version", NULL); +	if (!prop) +		return -ENXIO; +	vers = be32_to_cpup(prop); +	printk(KERN_INFO "adt746x: version %d (%ssupported)\n", +	       vers, vers == 1 ? "" : "un"); +	if (vers != 1) +		return -ENXIO;  	if (of_get_property(np, "hwsensor-location", NULL)) {  		for (i = 0; i < 3; i++) { @@ -623,72 +494,129 @@ thermostat_init(void)  			printk(KERN_INFO "sensor %d: %s\n", i, sensor_location[i]);  			offset += strlen(sensor_location[i]) + 1;  		} -	} else { -		sensor_location[0] = "?"; -		sensor_location[1] = "?"; -		sensor_location[2] = "?";  	} -	of_dev = of_platform_device_create(np, "temperatures", NULL); -	of_node_put(np); +	th = kzalloc(sizeof(struct thermostat), GFP_KERNEL); +	if (!th) +		return -ENOMEM; -	if (of_dev == NULL) { -		printk(KERN_ERR "Can't register temperatures device !\n"); +	i2c_set_clientdata(client, th); +	th->clt = client; +	th->type = id->driver_data; + +	rc = read_reg(th, CONFIG_REG); +	if (rc < 0) { +		dev_err(&client->dev, "Thermostat failed to read config!\n"); +		kfree(th);  		return -ENODEV;  	} -#ifndef CONFIG_I2C_POWERMAC -	request_module("i2c-powermac"); -#endif +	/* force manual control to start the fan quieter */ +	if (fan_speed == -1) +		fan_speed = 64; +	 +	if (th->type == ADT7460) { +		printk(KERN_INFO "adt746x: ADT7460 initializing\n"); +		/* The 7460 needs to be started explicitly */ +		write_reg(th, CONFIG_REG, 1); +	} else +		printk(KERN_INFO "adt746x: ADT7467 initializing\n"); -	return i2c_add_driver(&thermostat_driver); +	for (i = 0; i < 3; i++) { +		th->initial_limits[i] = read_reg(th, LIMIT_REG[i]); +		set_limit(th, i); +	} + +	printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d" +			 " to %d, %d, %d\n", +			 th->initial_limits[0], th->initial_limits[1], +			 th->initial_limits[2], th->limits[0], th->limits[1], +			 th->limits[2]); + +	/* record invert bit status because fw can corrupt it after suspend */ +	th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK; +	th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK; + +	/* be sure to really write fan speed the first time */ +	th->last_speed[0] = -2; +	th->last_speed[1] = -2; +	th->last_var[0] = -80; +	th->last_var[1] = -80; + +	if (fan_speed != -1) { +		/* manual mode, stop fans */ +		write_both_fan_speed(th, 0); +	} else { +		/* automatic mode */ +		write_both_fan_speed(th, -1); +	} +	 +	th->thread = kthread_run(monitor_task, th, "kfand"); +	if (th->thread == ERR_PTR(-ENOMEM)) { +		printk(KERN_INFO "adt746x: Kthread creation failed\n"); +		th->thread = NULL; +		return -ENOMEM; +	} + +	thermostat_create_files(th); + +	return 0;  } -static void thermostat_create_files(void) +static int remove_thermostat(struct i2c_client *client)  { -	int err; +	struct thermostat *th = i2c_get_clientdata(client); +	int i; +	 +	thermostat_remove_files(th); -	err = device_create_file(&of_dev->dev, &dev_attr_sensor1_temperature); -	err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_temperature); -	err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_limit); -	err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_limit); -	err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_location); -	err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_location); -	err |= device_create_file(&of_dev->dev, &dev_attr_limit_adjust); -	err |= device_create_file(&of_dev->dev, &dev_attr_specified_fan_speed); -	err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_fan_speed); -	if(therm_type == ADT7460) -		err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_fan_speed); -	if (err) -		printk(KERN_WARNING -			"Failed to create temperature attribute file(s).\n"); +	if (th->thread != NULL) +		kthread_stop(th->thread); + +	printk(KERN_INFO "adt746x: Putting max temperatures back from " +			 "%d, %d, %d to %d, %d, %d\n", +		th->limits[0], th->limits[1], th->limits[2], +		th->initial_limits[0], th->initial_limits[1], +		th->initial_limits[2]); + +	for (i = 0; i < 3; i++) +		write_reg(th, LIMIT_REG[i], th->initial_limits[i]); + +	write_both_fan_speed(th, -1); + +	kfree(th); + +	return 0;  } -static void thermostat_remove_files(void) -{ -	if (of_dev) { -		device_remove_file(&of_dev->dev, &dev_attr_sensor1_temperature); -		device_remove_file(&of_dev->dev, &dev_attr_sensor2_temperature); -		device_remove_file(&of_dev->dev, &dev_attr_sensor1_limit); -		device_remove_file(&of_dev->dev, &dev_attr_sensor2_limit); -		device_remove_file(&of_dev->dev, &dev_attr_sensor1_location); -		device_remove_file(&of_dev->dev, &dev_attr_sensor2_location); -		device_remove_file(&of_dev->dev, &dev_attr_limit_adjust); -		device_remove_file(&of_dev->dev, &dev_attr_specified_fan_speed); -		device_remove_file(&of_dev->dev, &dev_attr_sensor1_fan_speed); +static const struct i2c_device_id therm_adt746x_id[] = { +	{ "MAC,adt7460", ADT7460 }, +	{ "MAC,adt7467", ADT7467 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, therm_adt746x_id); + +static struct i2c_driver thermostat_driver = { +	.driver = { +		.name	= "therm_adt746x", +	}, +	.probe = probe_thermostat, +	.remove = remove_thermostat, +	.id_table = therm_adt746x_id, +}; -		if(therm_type == ADT7460) -			device_remove_file(&of_dev->dev, -					   &dev_attr_sensor2_fan_speed); +static int __init thermostat_init(void) +{ +#ifndef CONFIG_I2C_POWERMAC +	request_module("i2c-powermac"); +#endif -	} +	return i2c_add_driver(&thermostat_driver);  } -static void __exit -thermostat_exit(void) +static void __exit thermostat_exit(void)  {  	i2c_del_driver(&thermostat_driver); -	of_device_unregister(of_dev);  }  module_init(thermostat_init); diff --git a/drivers/macintosh/windfarm.h b/drivers/macintosh/windfarm.h index 7a2482cc26a7..028cdac2d33d 100644 --- a/drivers/macintosh/windfarm.h +++ b/drivers/macintosh/windfarm.h @@ -17,7 +17,7 @@  #include <linux/device.h>  /* Display a 16.16 fixed point value */ -#define FIX32TOPRINT(f)	((f) >> 16),((((f) & 0xffff) * 1000) >> 16) +#define FIX32TOPRINT(f)	(((s32)(f)) >> 16),(((((s32)(f)) & 0xffff) * 1000) >> 16)  /*   * Control objects @@ -35,12 +35,13 @@ struct wf_control_ops {  };  struct wf_control { -	struct list_head	link; -	struct wf_control_ops	*ops; -	char			*name; -	int			type; -	struct kref		ref; -	struct device_attribute	attr; +	struct list_head		link; +	const struct wf_control_ops	*ops; +	const char			*name; +	int				type; +	struct kref			ref; +	struct device_attribute		attr; +	void				*priv;  };  #define WF_CONTROL_TYPE_GENERIC		0 @@ -72,6 +73,26 @@ static inline int wf_control_set_min(struct wf_control *ct)  	return ct->ops->set_value(ct, vmin);  } +static inline int wf_control_set(struct wf_control *ct, s32 val) +{ +	return ct->ops->set_value(ct, val); +} + +static inline int wf_control_get(struct wf_control *ct, s32 *val) +{ +	return ct->ops->get_value(ct, val); +} + +static inline s32 wf_control_get_min(struct wf_control *ct) +{ +	return ct->ops->get_min(ct); +} + +static inline s32 wf_control_get_max(struct wf_control *ct) +{ +	return ct->ops->get_max(ct); +} +  /*   * Sensor objects   */ @@ -85,11 +106,12 @@ struct wf_sensor_ops {  };  struct wf_sensor { -	struct list_head	link; -	struct wf_sensor_ops	*ops; -	char			*name; -	struct kref		ref; -	struct device_attribute	attr; +	struct list_head		link; +	const struct wf_sensor_ops	*ops; +	const char			*name; +	struct kref			ref; +	struct device_attribute		attr; +	void				*priv;  };  /* Same lifetime rules as controls */ @@ -99,6 +121,11 @@ extern struct wf_sensor * wf_find_sensor(const char *name);  extern int wf_get_sensor(struct wf_sensor *sr);  extern void wf_put_sensor(struct wf_sensor *sr); +static inline int wf_sensor_get(struct wf_sensor *sr, s32 *val) +{ +	return sr->ops->get_value(sr, val); +} +  /* For use by clients. Note that we are a bit racy here since   * notifier_block doesn't have a module owner field. I may fix   * it one day ... diff --git a/drivers/macintosh/windfarm_ad7417_sensor.c b/drivers/macintosh/windfarm_ad7417_sensor.c new file mode 100644 index 000000000000..ac3f243b9c5a --- /dev/null +++ b/drivers/macintosh/windfarm_ad7417_sensor.c @@ -0,0 +1,347 @@ +/* + * Windfarm PowerMac thermal control. AD7417 sensors + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> + +#include "windfarm.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +struct wf_ad7417_priv { +	struct kref		ref; +	struct i2c_client	*i2c; +	u8			config; +	u8			cpu; +	const struct mpu_data	*mpu; +	struct wf_sensor	sensors[5]; +	struct mutex		lock; +}; + +static int wf_ad7417_temp_get(struct wf_sensor *sr, s32 *value) +{ +	struct wf_ad7417_priv *pv = sr->priv; +	u8 buf[2]; +	s16 raw; +	int rc; + +	*value = 0; +	mutex_lock(&pv->lock); + +	/* Read temp register */ +	buf[0] = 0; +	rc = i2c_master_send(pv->i2c, buf, 1); +	if (rc < 0) +		goto error; +	rc = i2c_master_recv(pv->i2c, buf, 2); +	if (rc < 0) +		goto error; + +	/* Read a a 16-bit signed value */ +	raw = be16_to_cpup((__le16 *)buf); + +	/* Convert 8.8-bit to 16.16 fixed point */ +	*value = ((s32)raw) << 8; + +	mutex_unlock(&pv->lock); +	return 0; + +error: +	mutex_unlock(&pv->lock); +	return -1; +} + +/* + * Scaling factors for the AD7417 ADC converters (except + * for the CPU diode which is obtained from the EEPROM). + * Those values are obtained from the property list of + * the darwin driver + */ +#define ADC_12V_CURRENT_SCALE	0x0320	/* _AD2 */ +#define ADC_CPU_VOLTAGE_SCALE	0x00a0	/* _AD3 */ +#define ADC_CPU_CURRENT_SCALE	0x1f40	/* _AD4 */ + +static void wf_ad7417_adc_convert(struct wf_ad7417_priv *pv, +				  int chan, s32 raw, s32 *value) +{ +	switch(chan) { +	case 1: /* Diode */ +		*value = (raw * (s32)pv->mpu->mdiode + +			((s32)pv->mpu->bdiode << 12)) >> 2; +		break; +	case 2: /* 12v current */ +		*value = raw * ADC_12V_CURRENT_SCALE; +		break; +	case 3: /* core voltage */ +		*value = raw * ADC_CPU_VOLTAGE_SCALE; +		break; +	case 4: /* core current */ +		*value = raw * ADC_CPU_CURRENT_SCALE; +		break; +	} +} + +static int wf_ad7417_adc_get(struct wf_sensor *sr, s32 *value) +{ +	struct wf_ad7417_priv *pv = sr->priv; +	int chan = sr - pv->sensors; +	int i, rc; +	u8 buf[2]; +	u16 raw; + +	*value = 0; +	mutex_lock(&pv->lock); +	for (i = 0; i < 10; i++) { +		/* Set channel */ +		buf[0] = 1; +		buf[1] = (pv->config & 0x1f) | (chan << 5); +		rc = i2c_master_send(pv->i2c, buf, 2); +		if (rc < 0) +			goto error; + +		/* Wait for conversion */ +		msleep(1); + +		/* Switch to data register */ +		buf[0] = 4; +		rc = i2c_master_send(pv->i2c, buf, 1); +		if (rc < 0) +			goto error; + +		/* Read result */ +		rc = i2c_master_recv(pv->i2c, buf, 2); +		if (rc < 0) +			goto error; + +		/* Read a a 16-bit signed value */ +		raw = be16_to_cpup((__le16 *)buf) >> 6; +		wf_ad7417_adc_convert(pv, chan, raw, value); + +		dev_vdbg(&pv->i2c->dev, "ADC chan %d [%s]" +			 " raw value: 0x%x, conv to: 0x%08x\n", +			 chan, sr->name, raw, *value); + +		mutex_unlock(&pv->lock); +		return 0; + +	error: +		dev_dbg(&pv->i2c->dev, +			  "Error reading ADC, try %d...\n", i); +		if (i < 9) +			msleep(10); +	} +	mutex_unlock(&pv->lock); +	return -1; +} + +static void wf_ad7417_release(struct kref *ref) +{ +	struct wf_ad7417_priv *pv = container_of(ref, +						 struct wf_ad7417_priv, ref); +	kfree(pv); +} + +static void wf_ad7417_sensor_release(struct wf_sensor *sr) +{ +	struct wf_ad7417_priv *pv = sr->priv; + +	kfree(sr->name); +	kref_put(&pv->ref, wf_ad7417_release); +} + +static const struct wf_sensor_ops wf_ad7417_temp_ops = { +	.get_value	= wf_ad7417_temp_get, +	.release	= wf_ad7417_sensor_release, +	.owner		= THIS_MODULE, +}; + +static const struct wf_sensor_ops wf_ad7417_adc_ops = { +	.get_value	= wf_ad7417_adc_get, +	.release	= wf_ad7417_sensor_release, +	.owner		= THIS_MODULE, +}; + +static void __devinit wf_ad7417_add_sensor(struct wf_ad7417_priv *pv, +					   int index, const char *name, +					   const struct wf_sensor_ops *ops) +{ +	pv->sensors[index].name = kasprintf(GFP_KERNEL, "%s-%d", name, pv->cpu); +	pv->sensors[index].priv = pv; +	pv->sensors[index].ops = ops; +	if (!wf_register_sensor(&pv->sensors[index])) +		kref_get(&pv->ref); +} + +static void __devinit wf_ad7417_init_chip(struct wf_ad7417_priv *pv) +{ +	int rc; +	u8 buf[2]; +	u8 config = 0; + +	/* +	 * Read ADC the configuration register and cache it. We +	 * also make sure Config2 contains proper values, I've seen +	 * cases where we got stale grabage in there, thus preventing +	 * proper reading of conv. values +	 */ + +	/* Clear Config2 */ +	buf[0] = 5; +	buf[1] = 0; +	i2c_master_send(pv->i2c, buf, 2); + +	/* Read & cache Config1 */ +	buf[0] = 1; +	rc = i2c_master_send(pv->i2c, buf, 1); +	if (rc > 0) { +		rc = i2c_master_recv(pv->i2c, buf, 1); +		if (rc > 0) { +			config = buf[0]; + +			dev_dbg(&pv->i2c->dev, "ADC config reg: %02x\n", +				config); + +			/* Disable shutdown mode */ +			config &= 0xfe; +			buf[0] = 1; +			buf[1] = config; +			rc = i2c_master_send(pv->i2c, buf, 2); +		} +	} +	if (rc <= 0) +		dev_err(&pv->i2c->dev, "Error reading ADC config\n"); + +	pv->config = config; +} + +static int __devinit wf_ad7417_probe(struct i2c_client *client, +				     const struct i2c_device_id *id) +{ +	struct wf_ad7417_priv *pv; +	const struct mpu_data *mpu; +	const char *loc; +	int cpu_nr; + +	loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); +	if (!loc) { +		dev_warn(&client->dev, "Missing hwsensor-location property!\n"); +		return -ENXIO; +	} + +	/* +	 * Identify which CPU we belong to by looking at the first entry +	 * in the hwsensor-location list +	 */ +	if (!strncmp(loc, "CPU A", 5)) +		cpu_nr = 0; +	else if (!strncmp(loc, "CPU B", 5)) +		cpu_nr = 1; +	else { +		pr_err("wf_ad7417: Can't identify location %s\n", loc); +		return -ENXIO; +	} +	mpu = wf_get_mpu(cpu_nr); +	if (!mpu) { +		dev_err(&client->dev, "Failed to retrieve MPU data\n"); +		return -ENXIO; +	} + +	pv = kzalloc(sizeof(struct wf_ad7417_priv), GFP_KERNEL); +	if (pv == NULL) +		return -ENODEV; + +	kref_init(&pv->ref); +	mutex_init(&pv->lock); +	pv->i2c = client; +	pv->cpu = cpu_nr; +	pv->mpu = mpu; +	dev_set_drvdata(&client->dev, pv); + +	/* Initialize the chip */ +	wf_ad7417_init_chip(pv); + +	/* +	 * We cannot rely on Apple device-tree giving us child +	 * node with the names of the individual sensors so we +	 * just hard code what we know about them +	 */ +	wf_ad7417_add_sensor(pv, 0, "cpu-amb-temp", &wf_ad7417_temp_ops); +	wf_ad7417_add_sensor(pv, 1, "cpu-diode-temp", &wf_ad7417_adc_ops); +	wf_ad7417_add_sensor(pv, 2, "cpu-12v-current", &wf_ad7417_adc_ops); +	wf_ad7417_add_sensor(pv, 3, "cpu-voltage", &wf_ad7417_adc_ops); +	wf_ad7417_add_sensor(pv, 4, "cpu-current", &wf_ad7417_adc_ops); + +	return 0; +} + +static int __devexit wf_ad7417_remove(struct i2c_client *client) +{ +	struct wf_ad7417_priv *pv = dev_get_drvdata(&client->dev); +	int i; + +	/* Mark client detached */ +	pv->i2c = NULL; + +	/* Release sensor */ +	for (i = 0; i < 5; i++) +		wf_unregister_sensor(&pv->sensors[i]); + +	kref_put(&pv->ref, wf_ad7417_release); + +	return 0; +} + +static const struct i2c_device_id wf_ad7417_id[] = { +	{ "MAC,ad7417", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, wf_ad7417_id); + +static struct i2c_driver wf_ad7417_driver = { +	.driver = { +		.name	= "wf_ad7417", +	}, +	.probe		= wf_ad7417_probe, +	.remove		= wf_ad7417_remove, +	.id_table	= wf_ad7417_id, +}; + +static int __devinit wf_ad7417_init(void) +{ +	/* This is only supported on these machines */ +	if (!of_machine_is_compatible("PowerMac7,2") && +	    !of_machine_is_compatible("PowerMac7,3") && +	    !of_machine_is_compatible("RackMac3,1")) +		return -ENODEV; + +	return i2c_add_driver(&wf_ad7417_driver); +} + +static void __devexit wf_ad7417_exit(void) +{ +	i2c_del_driver(&wf_ad7417_driver); +} + +module_init(wf_ad7417_init); +module_exit(wf_ad7417_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("ad7417 sensor driver for PowerMacs"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c index ce8897933a84..3ee198b65843 100644 --- a/drivers/macintosh/windfarm_core.c +++ b/drivers/macintosh/windfarm_core.c @@ -164,13 +164,27 @@ static ssize_t wf_show_control(struct device *dev,  			       struct device_attribute *attr, char *buf)  {  	struct wf_control *ctrl = container_of(attr, struct wf_control, attr); +	const char *typestr;  	s32 val = 0;  	int err;  	err = ctrl->ops->get_value(ctrl, &val); -	if (err < 0) +	if (err < 0) { +		if (err == -EFAULT) +			return sprintf(buf, "<HW FAULT>\n");  		return err; -	return sprintf(buf, "%d\n", val); +	} +	switch(ctrl->type) { +	case WF_CONTROL_RPM_FAN: +		typestr = " RPM"; +		break; +	case WF_CONTROL_PWM_FAN: +		typestr = " %"; +		break; +	default: +		typestr = ""; +	} +	return sprintf(buf, "%d%s\n", val, typestr);  }  /* This is really only for debugging... */ @@ -470,11 +484,6 @@ static int __init windfarm_core_init(void)  {  	DBG("wf: core loaded\n"); -	/* Don't register on old machines that use therm_pm72 for now */ -	if (of_machine_is_compatible("PowerMac7,2") || -	    of_machine_is_compatible("PowerMac7,3") || -	    of_machine_is_compatible("RackMac3,1")) -		return -ENODEV;  	platform_device_register(&wf_platform_device);  	return 0;  } diff --git a/drivers/macintosh/windfarm_cpufreq_clamp.c b/drivers/macintosh/windfarm_cpufreq_clamp.c index 1a77a7c97d0e..72d1fdfe02a5 100644 --- a/drivers/macintosh/windfarm_cpufreq_clamp.c +++ b/drivers/macintosh/windfarm_cpufreq_clamp.c @@ -75,12 +75,6 @@ static int __init wf_cpufreq_clamp_init(void)  {  	struct wf_control *clamp; -	/* Don't register on old machines that use therm_pm72 for now */ -	if (of_machine_is_compatible("PowerMac7,2") || -	    of_machine_is_compatible("PowerMac7,3") || -	    of_machine_is_compatible("RackMac3,1")) -		return -ENODEV; -  	clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL);  	if (clamp == NULL)  		return -ENOMEM; diff --git a/drivers/macintosh/windfarm_fcu_controls.c b/drivers/macintosh/windfarm_fcu_controls.c new file mode 100644 index 000000000000..b3411edb324b --- /dev/null +++ b/drivers/macintosh/windfarm_fcu_controls.c @@ -0,0 +1,613 @@ +/* + * Windfarm PowerMac thermal control. FCU fan control + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + */ +#undef DEBUG + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> + +#include "windfarm.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#ifdef DEBUG +#define DBG(args...)	printk(args) +#else +#define DBG(args...)	do { } while(0) +#endif + +/* + * This option is "weird" :) Basically, if you define this to 1 + * the control loop for the RPMs fans (not PWMs) will apply the + * correction factor obtained from the PID to the actual RPM + * speed read from the FCU. + * + * If you define the below constant to 0, then it will be + * applied to the setpoint RPM speed, that is basically the + * speed we proviously "asked" for. + * + * I'm using 0 for now which is what therm_pm72 used to do and + * what Darwin -apparently- does based on observed behaviour. + */ +#define RPM_PID_USE_ACTUAL_SPEED	0 + +/* Default min/max for pumps */ +#define CPU_PUMP_OUTPUT_MAX		3200 +#define CPU_PUMP_OUTPUT_MIN		1250 + +#define FCU_FAN_RPM		0 +#define FCU_FAN_PWM		1 + +struct wf_fcu_priv { +	struct kref		ref; +	struct i2c_client	*i2c; +	struct mutex		lock; +	struct list_head	fan_list; +	int			rpm_shift; +}; + +struct wf_fcu_fan { +	struct list_head	link; +	int			id; +	s32			min, max, target; +	struct wf_fcu_priv	*fcu_priv; +	struct wf_control	ctrl; +}; + +static void wf_fcu_release(struct kref *ref) +{ +	struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref); + +	kfree(pv); +} + +static void wf_fcu_fan_release(struct wf_control *ct) +{ +	struct wf_fcu_fan *fan = ct->priv; + +	kref_put(&fan->fcu_priv->ref, wf_fcu_release); +	kfree(fan); +} + +static int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg, +			   unsigned char *buf, int nb) +{ +	int tries, nr, nw; + +	mutex_lock(&pv->lock); + +	buf[0] = reg; +	tries = 0; +	for (;;) { +		nw = i2c_master_send(pv->i2c, buf, 1); +		if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) +			break; +		msleep(10); +		++tries; +	} +	if (nw <= 0) { +		pr_err("Failure writing address to FCU: %d", nw); +		nr = nw; +		goto bail; +	} +	tries = 0; +	for (;;) { +		nr = i2c_master_recv(pv->i2c, buf, nb); +		if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100) +			break; +		msleep(10); +		++tries; +	} +	if (nr <= 0) +		pr_err("wf_fcu: Failure reading data from FCU: %d", nw); + bail: +	mutex_unlock(&pv->lock); +	return nr; +} + +static int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg, +			    const unsigned char *ptr, int nb) +{ +	int tries, nw; +	unsigned char buf[16]; + +	buf[0] = reg; +	memcpy(buf+1, ptr, nb); +	++nb; +	tries = 0; +	for (;;) { +		nw = i2c_master_send(pv->i2c, buf, nb); +		if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) +			break; +		msleep(10); +		++tries; +	} +	if (nw < 0) +		pr_err("wf_fcu: Failure writing to FCU: %d", nw); +	return nw; +} + +static int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value) +{ +	struct wf_fcu_fan *fan = ct->priv; +	struct wf_fcu_priv *pv = fan->fcu_priv; +	int rc, shift = pv->rpm_shift; +	unsigned char buf[2]; + +	if (value < fan->min) +		value = fan->min; +	if (value > fan->max) +		value = fan->max; + +	fan->target = value; + +	buf[0] = value >> (8 - shift); +	buf[1] = value << shift; +	rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2); +	if (rc < 0) +		return -EIO; +	return 0; +} + +static int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value) +{ +	struct wf_fcu_fan *fan = ct->priv; +	struct wf_fcu_priv *pv = fan->fcu_priv; +	int rc, reg_base, shift = pv->rpm_shift; +	unsigned char failure; +	unsigned char active; +	unsigned char buf[2]; + +	rc = wf_fcu_read_reg(pv, 0xb, &failure, 1); +	if (rc != 1) +		return -EIO; +	if ((failure & (1 << fan->id)) != 0) +		return -EFAULT; +	rc = wf_fcu_read_reg(pv, 0xd, &active, 1); +	if (rc != 1) +		return -EIO; +	if ((active & (1 << fan->id)) == 0) +		return -ENXIO; + +	/* Programmed value or real current speed */ +#if RPM_PID_USE_ACTUAL_SPEED +	reg_base = 0x11; +#else +	reg_base = 0x10; +#endif +	rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2); +	if (rc != 2) +		return -EIO; + +	*value = (buf[0] << (8 - shift)) | buf[1] >> shift; + +	return 0; +} + +static int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value) +{ +	struct wf_fcu_fan *fan = ct->priv; +	struct wf_fcu_priv *pv = fan->fcu_priv; +	unsigned char buf[2]; +	int rc; + +	if (value < fan->min) +		value = fan->min; +	if (value > fan->max) +		value = fan->max; + +	fan->target = value; + +	value = (value * 2559) / 1000; +	buf[0] = value; +	rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1); +	if (rc < 0) +		return -EIO; +	return 0; +} + +static int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value) +{ +	struct wf_fcu_fan *fan = ct->priv; +	struct wf_fcu_priv *pv = fan->fcu_priv; +	unsigned char failure; +	unsigned char active; +	unsigned char buf[2]; +	int rc; + +	rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1); +	if (rc != 1) +		return -EIO; +	if ((failure & (1 << fan->id)) != 0) +		return -EFAULT; +	rc = wf_fcu_read_reg(pv, 0x2d, &active, 1); +	if (rc != 1) +		return -EIO; +	if ((active & (1 << fan->id)) == 0) +		return -ENXIO; + +	rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1); +	if (rc != 1) +		return -EIO; + +	*value = (((s32)buf[0]) * 1000) / 2559; + +	return 0; +} + +static s32 wf_fcu_fan_min(struct wf_control *ct) +{ +	struct wf_fcu_fan *fan = ct->priv; + +	return fan->min; +} + +static s32 wf_fcu_fan_max(struct wf_control *ct) +{ +	struct wf_fcu_fan *fan = ct->priv; + +	return fan->max; +} + +static const struct wf_control_ops wf_fcu_fan_rpm_ops = { +	.set_value	= wf_fcu_fan_set_rpm, +	.get_value	= wf_fcu_fan_get_rpm, +	.get_min	= wf_fcu_fan_min, +	.get_max	= wf_fcu_fan_max, +	.release	= wf_fcu_fan_release, +	.owner		= THIS_MODULE, +}; + +static const struct wf_control_ops wf_fcu_fan_pwm_ops = { +	.set_value	= wf_fcu_fan_set_pwm, +	.get_value	= wf_fcu_fan_get_pwm, +	.get_min	= wf_fcu_fan_min, +	.get_max	= wf_fcu_fan_max, +	.release	= wf_fcu_fan_release, +	.owner		= THIS_MODULE, +}; + +static void __devinit wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan) +{ +	const struct mpu_data *mpu = wf_get_mpu(0); +	u16 pump_min = 0, pump_max = 0xffff; +	u16 tmp[4]; + +	/* Try to fetch pumps min/max infos from eeprom */ +	if (mpu) { +		memcpy(&tmp, mpu->processor_part_num, 8); +		if (tmp[0] != 0xffff && tmp[1] != 0xffff) { +			pump_min = max(pump_min, tmp[0]); +			pump_max = min(pump_max, tmp[1]); +		} +		if (tmp[2] != 0xffff && tmp[3] != 0xffff) { +			pump_min = max(pump_min, tmp[2]); +			pump_max = min(pump_max, tmp[3]); +		} +	} + +	/* Double check the values, this _IS_ needed as the EEPROM on +	 * some dual 2.5Ghz G5s seem, at least, to have both min & max +	 * same to the same value ... (grrrr) +	 */ +	if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) { +		pump_min = CPU_PUMP_OUTPUT_MIN; +		pump_max = CPU_PUMP_OUTPUT_MAX; +	} + +	fan->min = pump_min; +	fan->max = pump_max; + +	DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n", +	    fan->ctrl.name, pump_min, pump_max); +} + +static void __devinit wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan) +{ +	struct wf_fcu_priv *pv = fan->fcu_priv; +	const struct mpu_data *mpu0 = wf_get_mpu(0); +	const struct mpu_data *mpu1 = wf_get_mpu(1); + +	/* Default */ +	fan->min = 2400 >> pv->rpm_shift; +	fan->max = 56000 >> pv->rpm_shift; + +	/* CPU fans have min/max in MPU */ +	if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) { +		fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); +		fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); +		goto bail; +	} +	if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) { +		fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan); +		fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan); +		goto bail; +	} +	if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) { +		fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan); +		fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan); +		goto bail; +	} +	if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) { +		fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan); +		fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan); +		goto bail; +	} +	/* Rackmac variants, we just use mpu0 intake */ +	if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) { +		fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); +		fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); +		goto bail; +	} + bail: +	DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n", +	    fan->ctrl.name, fan->min, fan->max); +} + +static void __devinit wf_fcu_add_fan(struct wf_fcu_priv *pv, +				     const char *name, +				     int type, int id) +{ +	struct wf_fcu_fan *fan; + +	fan = kzalloc(sizeof(*fan), GFP_KERNEL); +	if (!fan) +		return; +	fan->fcu_priv = pv; +	fan->id = id; +	fan->ctrl.name = name; +	fan->ctrl.priv = fan; + +	/* min/max is oddball but the code comes from +	 * therm_pm72 which seems to work so ... +	 */ +	if (type == FCU_FAN_RPM) { +		if (!strncmp(name, "cpu-pump", strlen("cpu-pump"))) +			wf_fcu_get_pump_minmax(fan); +		else +			wf_fcu_get_rpmfan_minmax(fan); +		fan->ctrl.type = WF_CONTROL_RPM_FAN; +		fan->ctrl.ops = &wf_fcu_fan_rpm_ops; +	} else { +		fan->min = 10; +		fan->max = 100; +		fan->ctrl.type = WF_CONTROL_PWM_FAN; +		fan->ctrl.ops = &wf_fcu_fan_pwm_ops; +	} + +	if (wf_register_control(&fan->ctrl)) { +		pr_err("wf_fcu: Failed to register fan %s\n", name); +		kfree(fan); +		return; +	} +	list_add(&fan->link, &pv->fan_list); +	kref_get(&pv->ref); +} + +static void __devinit wf_fcu_lookup_fans(struct wf_fcu_priv *pv) +{ +	/* Translation of device-tree location properties to +	 * windfarm fan names +	 */ +	static const struct { +		const char *dt_name;	/* Device-tree name */ +		const char *ct_name;	/* Control name */ +	} loc_trans[] = { +		{ "BACKSIDE",		"backside-fan",		}, +		{ "SYS CTRLR FAN",	"backside-fan",		}, +		{ "DRIVE BAY",		"drive-bay-fan",	}, +		{ "SLOT",		"slots-fan",		}, +		{ "PCI FAN",		"slots-fan",		}, +		{ "CPU A INTAKE",	"cpu-front-fan-0",	}, +		{ "CPU A EXHAUST",	"cpu-rear-fan-0",	}, +		{ "CPU B INTAKE",	"cpu-front-fan-1",	}, +		{ "CPU B EXHAUST",	"cpu-rear-fan-1",	}, +		{ "CPU A PUMP",		"cpu-pump-0",		}, +		{ "CPU B PUMP",		"cpu-pump-1",		}, +		{ "CPU A 1",		"cpu-fan-a-0",		}, +		{ "CPU A 2",		"cpu-fan-b-0",		}, +		{ "CPU A 3",		"cpu-fan-c-0",		}, +		{ "CPU B 1",		"cpu-fan-a-1",		}, +		{ "CPU B 2",		"cpu-fan-b-1",		}, +		{ "CPU B 3",		"cpu-fan-c-1",		}, +	}; +	struct device_node *np = NULL, *fcu = pv->i2c->dev.of_node; +	int i; + +	DBG("Looking up FCU controls in device-tree...\n"); + +	while ((np = of_get_next_child(fcu, np)) != NULL) { +		int id, type = -1; +		const char *loc; +		const char *name; +		const u32 *reg; + +		DBG(" control: %s, type: %s\n", np->name, np->type); + +		/* Detect control type */ +		if (!strcmp(np->type, "fan-rpm-control") || +		    !strcmp(np->type, "fan-rpm")) +			type = FCU_FAN_RPM; +		if (!strcmp(np->type, "fan-pwm-control") || +		    !strcmp(np->type, "fan-pwm")) +			type = FCU_FAN_PWM; +		/* Only care about fans for now */ +		if (type == -1) +			continue; + +		/* Lookup for a matching location */ +		loc = of_get_property(np, "location", NULL); +		reg = of_get_property(np, "reg", NULL); +		if (loc == NULL || reg == NULL) +			continue; +		DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg); + +		for (i = 0; i < ARRAY_SIZE(loc_trans); i++) { +			if (strncmp(loc, loc_trans[i].dt_name, +				    strlen(loc_trans[i].dt_name))) +				continue; +			name = loc_trans[i].ct_name; + +			DBG(" location match, name: %s\n", name); + +			if (type == FCU_FAN_RPM) +				id = ((*reg) - 0x10) / 2; +			else +				id = ((*reg) - 0x30) / 2; +			if (id > 7) { +				pr_warning("wf_fcu: Can't parse " +				       "fan ID in device-tree for %s\n", +					   np->full_name); +				break; +			} +			wf_fcu_add_fan(pv, name, type, id); +			break; +		} +	} +} + +static void __devinit wf_fcu_default_fans(struct wf_fcu_priv *pv) +{ +	/* We only support the default fans for PowerMac7,2 */ +	if (!of_machine_is_compatible("PowerMac7,2")) +		return; + +	wf_fcu_add_fan(pv, "backside-fan",	FCU_FAN_PWM, 1); +	wf_fcu_add_fan(pv, "drive-bay-fan",	FCU_FAN_RPM, 2); +	wf_fcu_add_fan(pv, "slots-fan",		FCU_FAN_PWM, 2); +	wf_fcu_add_fan(pv, "cpu-front-fan-0",	FCU_FAN_RPM, 3); +	wf_fcu_add_fan(pv, "cpu-rear-fan-0",	FCU_FAN_RPM, 4); +	wf_fcu_add_fan(pv, "cpu-front-fan-1",	FCU_FAN_RPM, 5); +	wf_fcu_add_fan(pv, "cpu-rear-fan-1",	FCU_FAN_RPM, 6); +} + +static int __devinit wf_fcu_init_chip(struct wf_fcu_priv *pv) +{ +	unsigned char buf = 0xff; +	int rc; + +	rc = wf_fcu_write_reg(pv, 0xe, &buf, 1); +	if (rc < 0) +		return -EIO; +	rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1); +	if (rc < 0) +		return -EIO; +	rc = wf_fcu_read_reg(pv, 0, &buf, 1); +	if (rc < 0) +		return -EIO; +	pv->rpm_shift = (buf == 1) ? 2 : 3; + +	pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n", +		 pv->rpm_shift); + +	return 0; +} + +static int __devinit wf_fcu_probe(struct i2c_client *client, +				  const struct i2c_device_id *id) +{ +	struct wf_fcu_priv *pv; + +	pv = kzalloc(sizeof(*pv), GFP_KERNEL); +	if (!pv) +		return -ENOMEM; + +	kref_init(&pv->ref); +	mutex_init(&pv->lock); +	INIT_LIST_HEAD(&pv->fan_list); +	pv->i2c = client; + +	/* +	 * First we must start the FCU which will query the +	 * shift value to apply to RPMs +	 */ +	if (wf_fcu_init_chip(pv)) { +		pr_err("wf_fcu: Initialization failed !\n"); +		kfree(pv); +		return -ENXIO; +	} + +	/* First lookup fans in the device-tree */ +	wf_fcu_lookup_fans(pv); + +	/* +	 * Older machines don't have the device-tree entries +	 * we are looking for, just hard code the list +	 */ +	if (list_empty(&pv->fan_list)) +		wf_fcu_default_fans(pv); + +	/* Still no fans ? FAIL */ +	if (list_empty(&pv->fan_list)) { +		pr_err("wf_fcu: Failed to find fans for your machine\n"); +		kfree(pv); +		return -ENODEV; +	} + +	dev_set_drvdata(&client->dev, pv); + +	return 0; +} + +static int __devexit wf_fcu_remove(struct i2c_client *client) +{ +	struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev); +	struct wf_fcu_fan *fan; + +	while (!list_empty(&pv->fan_list)) { +		fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link); +		list_del(&fan->link); +		wf_unregister_control(&fan->ctrl); +	} +	kref_put(&pv->ref, wf_fcu_release); +	return 0; +} + +static const struct i2c_device_id wf_fcu_id[] = { +	{ "MAC,fcu", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, wf_fcu_id); + +static struct i2c_driver wf_fcu_driver = { +	.driver = { +		.name	= "wf_fcu", +	}, +	.probe		= wf_fcu_probe, +	.remove		= wf_fcu_remove, +	.id_table	= wf_fcu_id, +}; + +static int __init wf_fcu_init(void) +{ +	return i2c_add_driver(&wf_fcu_driver); +} + +static void __exit wf_fcu_exit(void) +{ +	i2c_del_driver(&wf_fcu_driver); +} + + +module_init(wf_fcu_init); +module_exit(wf_fcu_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c index 4d6a90a1372b..b0c2d3695b34 100644 --- a/drivers/macintosh/windfarm_lm75_sensor.c +++ b/drivers/macintosh/windfarm_lm75_sensor.c @@ -23,7 +23,7 @@  #include "windfarm.h" -#define VERSION "0.2" +#define VERSION "1.0"  #undef DEBUG @@ -36,8 +36,8 @@  struct wf_lm75_sensor {  	int			ds1775 : 1;  	int			inited : 1; -	struct 	i2c_client	*i2c; -	struct 	wf_sensor	sens; +	struct i2c_client	*i2c; +	struct wf_sensor	sens;  };  #define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens) @@ -90,40 +90,19 @@ static struct wf_sensor_ops wf_lm75_ops = {  static int wf_lm75_probe(struct i2c_client *client,  			 const struct i2c_device_id *id) -{ +{	  	struct wf_lm75_sensor *lm; -	int rc; - -	lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); -	if (lm == NULL) -		return -ENODEV; - -	lm->inited = 0; -	lm->ds1775 = id->driver_data; -	lm->i2c = client; -	lm->sens.name = client->dev.platform_data; -	lm->sens.ops = &wf_lm75_ops; -	i2c_set_clientdata(client, lm); - -	rc = wf_register_sensor(&lm->sens); -	if (rc) -		kfree(lm); - -	return rc; -} - -static struct i2c_driver wf_lm75_driver; - -static struct i2c_client *wf_lm75_create(struct i2c_adapter *adapter, -					     u8 addr, int ds1775, -					     const char *loc) -{ -	struct i2c_board_info info; -	struct i2c_client *client; -	char *name; +	int rc, ds1775 = id->driver_data; +	const char *name, *loc;  	DBG("wf_lm75: creating  %s device at address 0x%02x\n", -	    ds1775 ? "ds1775" : "lm75", addr); +	    ds1775 ? "ds1775" : "lm75", client->addr); + +	loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); +	if (!loc) { +		dev_warn(&client->dev, "Missing hwsensor-location property!\n"); +		return -ENXIO; +	}  	/* Usual rant about sensor names not beeing very consistent in  	 * the device-tree, oh well ... @@ -137,68 +116,31 @@ static struct i2c_client *wf_lm75_create(struct i2c_adapter *adapter,  		name = "optical-drive-temp";  	else if (!strcmp(loc, "HD Temp"))  		name = "hard-drive-temp"; +	else if (!strcmp(loc, "PCI SLOTS")) +		name = "slots-temp"; +	else if (!strcmp(loc, "CPU A INLET")) +		name = "cpu-inlet-temp-0"; +	else if (!strcmp(loc, "CPU B INLET")) +		name = "cpu-inlet-temp-1";  	else -		goto fail; - -	memset(&info, 0, sizeof(struct i2c_board_info)); -	info.addr = (addr >> 1) & 0x7f; -	info.platform_data = name; -	strlcpy(info.type, ds1775 ? "wf_ds1775" : "wf_lm75", I2C_NAME_SIZE); +		return -ENXIO; + 	 -	client = i2c_new_device(adapter, &info); -	if (client == NULL) { -		printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n", -		       ds1775 ? "ds1775" : "lm75", name); -		goto fail; -	} - -	/* -	 * Let i2c-core delete that device on driver removal. -	 * This is safe because i2c-core holds the core_lock mutex for us. -	 */ -	list_add_tail(&client->detected, &wf_lm75_driver.clients); -	return client; - fail: -	return NULL; -} - -static int wf_lm75_attach(struct i2c_adapter *adapter) -{ -	struct device_node *busnode, *dev; -	struct pmac_i2c_bus *bus; - -	DBG("wf_lm75: adapter %s detected\n", adapter->name); - -	bus = pmac_i2c_adapter_to_bus(adapter); -	if (bus == NULL) +	lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); +	if (lm == NULL)  		return -ENODEV; -	busnode = pmac_i2c_get_bus_node(bus); - -	DBG("wf_lm75: bus found, looking for device...\n"); -	/* Now look for lm75(s) in there */ -	for (dev = NULL; -	     (dev = of_get_next_child(busnode, dev)) != NULL;) { -		const char *loc = -			of_get_property(dev, "hwsensor-location", NULL); -		u8 addr; +	lm->inited = 0; +	lm->ds1775 = ds1775; +	lm->i2c = client; +	lm->sens.name = (char *)name; /* XXX fix constness in structure */ +	lm->sens.ops = &wf_lm75_ops; +	i2c_set_clientdata(client, lm); -		/* We must re-match the adapter in order to properly check -		 * the channel on multibus setups -		 */ -		if (!pmac_i2c_match_adapter(dev, adapter)) -			continue; -		addr = pmac_i2c_get_dev_addr(dev); -		if (loc == NULL || addr == 0) -			continue; -		/* real lm75 */ -		if (of_device_is_compatible(dev, "lm75")) -			wf_lm75_create(adapter, addr, 0, loc); -		/* ds1775 (compatible, better resolution */ -		else if (of_device_is_compatible(dev, "ds1775")) -			wf_lm75_create(adapter, addr, 1, loc); -	} -	return 0; +	rc = wf_register_sensor(&lm->sens); +	if (rc) +		kfree(lm); +	return rc;  }  static int wf_lm75_remove(struct i2c_client *client) @@ -217,16 +159,16 @@ static int wf_lm75_remove(struct i2c_client *client)  }  static const struct i2c_device_id wf_lm75_id[] = { -	{ "wf_lm75", 0 }, -	{ "wf_ds1775", 1 }, +	{ "MAC,lm75", 0 }, +	{ "MAC,ds1775", 1 },  	{ }  }; +MODULE_DEVICE_TABLE(i2c, wf_lm75_id);  static struct i2c_driver wf_lm75_driver = {  	.driver = {  		.name	= "wf_lm75",  	}, -	.attach_adapter	= wf_lm75_attach,  	.probe		= wf_lm75_probe,  	.remove		= wf_lm75_remove,  	.id_table	= wf_lm75_id, @@ -234,11 +176,6 @@ static struct i2c_driver wf_lm75_driver = {  static int __init wf_lm75_sensor_init(void)  { -	/* Don't register on old machines that use therm_pm72 for now */ -	if (of_machine_is_compatible("PowerMac7,2") || -	    of_machine_is_compatible("PowerMac7,3") || -	    of_machine_is_compatible("RackMac3,1")) -		return -ENODEV;  	return i2c_add_driver(&wf_lm75_driver);  } diff --git a/drivers/macintosh/windfarm_lm87_sensor.c b/drivers/macintosh/windfarm_lm87_sensor.c new file mode 100644 index 000000000000..c071aab79dd1 --- /dev/null +++ b/drivers/macintosh/windfarm_lm87_sensor.c @@ -0,0 +1,201 @@ +/* + * Windfarm PowerMac thermal control. LM87 sensor + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/pmac_low_i2c.h> + +#include "windfarm.h" + +#define VERSION "1.0" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...)	printk(args) +#else +#define DBG(args...)	do { } while(0) +#endif + +struct wf_lm87_sensor { +	struct i2c_client	*i2c; +	struct wf_sensor	sens; +}; +#define wf_to_lm87(c) container_of(c, struct wf_lm87_sensor, sens) + + +static int wf_lm87_read_reg(struct i2c_client *chip, int reg) +{ +	int rc, tries = 0; +	u8 buf; + +	for (;;) { +		/* Set address */ +		buf = (u8)reg; +		rc = i2c_master_send(chip, &buf, 1); +		if (rc <= 0) +			goto error; +		rc = i2c_master_recv(chip, &buf, 1); +		if (rc <= 0) +			goto error; +		return (int)buf; +	error: +		DBG("wf_lm87: Error reading LM87, retrying...\n"); +		if (++tries > 10) { +			printk(KERN_ERR "wf_lm87: Error reading LM87 !\n"); +			return -EIO; +		} +		msleep(10); +	} +} + +static int wf_lm87_get(struct wf_sensor *sr, s32 *value) +{ +	struct wf_lm87_sensor *lm = sr->priv; +	s32 temp; + +	if (lm->i2c == NULL) +		return -ENODEV; + +#define LM87_INT_TEMP		0x27 + +	/* Read temperature register */ +	temp = wf_lm87_read_reg(lm->i2c, LM87_INT_TEMP); +	if (temp < 0) +		return temp; +	*value = temp << 16; + +	return 0; +} + +static void wf_lm87_release(struct wf_sensor *sr) +{ +	struct wf_lm87_sensor *lm = wf_to_lm87(sr); + +	kfree(lm); +} + +static struct wf_sensor_ops wf_lm87_ops = { +	.get_value	= wf_lm87_get, +	.release	= wf_lm87_release, +	.owner		= THIS_MODULE, +}; + +static int wf_lm87_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{	 +	struct wf_lm87_sensor *lm; +	const char *name = NULL, *loc; +	struct device_node *np = NULL; +	int rc; + +	/* +	 * The lm87 contains a whole pile of sensors, additionally, +	 * the Xserve G5 has several lm87's. However, for now we only +	 * care about the internal temperature sensor +	 */ +	while ((np = of_get_next_child(client->dev.of_node, np)) != NULL) { +		if (strcmp(np->name, "int-temp")) +			continue; +		loc = of_get_property(np, "location", NULL); +		if (!loc) +			continue; +		if (strstr(loc, "DIMM")) +			name = "dimms-temp"; +		else if (strstr(loc, "Processors")) +			name = "between-cpus-temp"; +		if (name) { +			of_node_put(np); +			break; +		} +	} +	if (!name) { +		pr_warning("wf_lm87: Unsupported sensor %s\n", +			   client->dev.of_node->full_name); +		return -ENODEV; +	} + +	lm = kzalloc(sizeof(struct wf_lm87_sensor), GFP_KERNEL); +	if (lm == NULL) +		return -ENODEV; + +	lm->i2c = client; +	lm->sens.name = name; +	lm->sens.ops = &wf_lm87_ops; +	lm->sens.priv = lm; +	i2c_set_clientdata(client, lm); + +	rc = wf_register_sensor(&lm->sens); +	if (rc) +		kfree(lm); +	return rc; +} + +static int wf_lm87_remove(struct i2c_client *client) +{ +	struct wf_lm87_sensor *lm = i2c_get_clientdata(client); + +	DBG("wf_lm87: i2c detatch called for %s\n", lm->sens.name); + +	/* Mark client detached */ +	lm->i2c = NULL; + +	/* release sensor */ +	wf_unregister_sensor(&lm->sens); + +	return 0; +} + +static const struct i2c_device_id wf_lm87_id[] = { +	{ "MAC,lm87cimt", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, wf_lm87_id); + +static struct i2c_driver wf_lm87_driver = { +	.driver = { +		.name	= "wf_lm87", +	}, +	.probe		= wf_lm87_probe, +	.remove		= wf_lm87_remove, +	.id_table	= wf_lm87_id, +}; + +static int __init wf_lm87_sensor_init(void) +{ +	/* We only support this on the Xserve */ +	if (!of_machine_is_compatible("RackMac3,1")) +		return -ENODEV; + +	return i2c_add_driver(&wf_lm87_driver); +} + +static void __exit wf_lm87_sensor_exit(void) +{ +	i2c_del_driver(&wf_lm87_driver); +} + + +module_init(wf_lm87_sensor_init); +module_exit(wf_lm87_sensor_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("LM87 sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_max6690_sensor.c b/drivers/macintosh/windfarm_max6690_sensor.c index 8204113268f4..371b058d2f7d 100644 --- a/drivers/macintosh/windfarm_max6690_sensor.c +++ b/drivers/macintosh/windfarm_max6690_sensor.c @@ -16,7 +16,7 @@  #include "windfarm.h" -#define VERSION "0.2" +#define VERSION "1.0"  /* This currently only exports the external temperature sensor,     since that's all the control loops need. */ @@ -64,9 +64,29 @@ static struct wf_sensor_ops wf_max6690_ops = {  static int wf_max6690_probe(struct i2c_client *client,  			    const struct i2c_device_id *id)  { +	const char *name, *loc;  	struct wf_6690_sensor *max;  	int rc; +	loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); +	if (!loc) { +		dev_warn(&client->dev, "Missing hwsensor-location property!\n"); +		return -ENXIO; +	} + +	/* +	 * We only expose the external temperature register for +	 * now as this is all we need for our control loops +	 */ +	if (!strcmp(loc, "BACKSIDE") || !strcmp(loc, "SYS CTRLR AMBIENT")) +		name = "backside-temp"; +	else if (!strcmp(loc, "NB Ambient")) +		name = "north-bridge-temp"; +	else if (!strcmp(loc, "GPU Ambient")) +		name = "gpu-temp"; +	else +		return -ENXIO; +  	max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL);  	if (max == NULL) {  		printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor: " @@ -75,90 +95,16 @@ static int wf_max6690_probe(struct i2c_client *client,  	}  	max->i2c = client; -	max->sens.name = client->dev.platform_data; +	max->sens.name = (char *)name; /* XXX fix constness in structure */  	max->sens.ops = &wf_max6690_ops;  	i2c_set_clientdata(client, max);  	rc = wf_register_sensor(&max->sens); -	if (rc) { +	if (rc)  		kfree(max); -	} -  	return rc;  } -static struct i2c_driver wf_max6690_driver; - -static struct i2c_client *wf_max6690_create(struct i2c_adapter *adapter, -					    u8 addr, const char *loc) -{ -	struct i2c_board_info info; -	struct i2c_client *client; -	char *name; - -	if (!strcmp(loc, "BACKSIDE")) -		name = "backside-temp"; -	else if (!strcmp(loc, "NB Ambient")) -		name = "north-bridge-temp"; -	else if (!strcmp(loc, "GPU Ambient")) -		name = "gpu-temp"; -	else -		goto fail; - -	memset(&info, 0, sizeof(struct i2c_board_info)); -	info.addr = addr >> 1; -	info.platform_data = name; -	strlcpy(info.type, "wf_max6690", I2C_NAME_SIZE); - -	client = i2c_new_device(adapter, &info); -	if (client == NULL) { -		printk(KERN_ERR "windfarm: failed to attach MAX6690 sensor\n"); -		goto fail; -	} - -	/* -	 * Let i2c-core delete that device on driver removal. -	 * This is safe because i2c-core holds the core_lock mutex for us. -	 */ -	list_add_tail(&client->detected, &wf_max6690_driver.clients); -	return client; - - fail: -	return NULL; -} - -static int wf_max6690_attach(struct i2c_adapter *adapter) -{ -	struct device_node *busnode, *dev = NULL; -	struct pmac_i2c_bus *bus; -	const char *loc; - -	bus = pmac_i2c_adapter_to_bus(adapter); -	if (bus == NULL) -		return -ENODEV; -	busnode = pmac_i2c_get_bus_node(bus); - -	while ((dev = of_get_next_child(busnode, dev)) != NULL) { -		u8 addr; - -		/* We must re-match the adapter in order to properly check -		 * the channel on multibus setups -		 */ -		if (!pmac_i2c_match_adapter(dev, adapter)) -			continue; -		if (!of_device_is_compatible(dev, "max6690")) -			continue; -		addr = pmac_i2c_get_dev_addr(dev); -		loc = of_get_property(dev, "hwsensor-location", NULL); -		if (loc == NULL || addr == 0) -			continue; -		printk("found max6690, loc=%s addr=0x%02x\n", loc, addr); -		wf_max6690_create(adapter, addr, loc); -	} - -	return 0; -} -  static int wf_max6690_remove(struct i2c_client *client)  {  	struct wf_6690_sensor *max = i2c_get_clientdata(client); @@ -170,15 +116,15 @@ static int wf_max6690_remove(struct i2c_client *client)  }  static const struct i2c_device_id wf_max6690_id[] = { -	{ "wf_max6690", 0 }, +	{ "MAC,max6690", 0 },  	{ }  }; +MODULE_DEVICE_TABLE(i2c, wf_max6690_id);  static struct i2c_driver wf_max6690_driver = {  	.driver = {  		.name		= "wf_max6690",  	}, -	.attach_adapter	= wf_max6690_attach,  	.probe		= wf_max6690_probe,  	.remove		= wf_max6690_remove,  	.id_table	= wf_max6690_id, @@ -186,11 +132,6 @@ static struct i2c_driver wf_max6690_driver = {  static int __init wf_max6690_sensor_init(void)  { -	/* Don't register on old machines that use therm_pm72 for now */ -	if (of_machine_is_compatible("PowerMac7,2") || -	    of_machine_is_compatible("PowerMac7,3") || -	    of_machine_is_compatible("RackMac3,1")) -		return -ENODEV;  	return i2c_add_driver(&wf_max6690_driver);  } diff --git a/drivers/macintosh/windfarm_mpu.h b/drivers/macintosh/windfarm_mpu.h new file mode 100644 index 000000000000..046edc8c2ec5 --- /dev/null +++ b/drivers/macintosh/windfarm_mpu.h @@ -0,0 +1,105 @@ +/* + * Windfarm PowerMac thermal control + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + */ + +#ifndef __WINDFARM_MPU_H +#define __WINDFARM_MPU_H + +typedef unsigned short fu16; +typedef int fs32; +typedef short fs16; + +/* Definition of the MPU data structure which contains per CPU + * calibration information (among others) for the G5 machines + */ +struct mpu_data +{ +	u8	signature;		/* 0x00 - EEPROM sig. */ +	u8	bytes_used;		/* 0x01 - Bytes used in eeprom (160 ?) */ +	u8	size;			/* 0x02 - EEPROM size (256 ?) */ +	u8	version;		/* 0x03 - EEPROM version */ +	u32	data_revision;		/* 0x04 - Dataset revision */ +	u8	processor_bin_code[3];	/* 0x08 - Processor BIN code */ +	u8	bin_code_expansion;	/* 0x0b - ??? (padding ?) */ +	u8	processor_num;		/* 0x0c - Number of CPUs on this MPU */ +	u8	input_mul_bus_div;	/* 0x0d - Clock input multiplier/bus divider */ +	u8	reserved1[2];		/* 0x0e - */ +	u32	input_clk_freq_high;	/* 0x10 - Input clock frequency high */ +	u8	cpu_nb_target_cycles;	/* 0x14 - ??? */ +	u8	cpu_statlat;		/* 0x15 - ??? */ +	u8	cpu_snooplat;		/* 0x16 - ??? */ +	u8	cpu_snoopacc;		/* 0x17 - ??? */ +	u8	nb_paamwin;		/* 0x18 - ??? */ +	u8	nb_statlat;		/* 0x19 - ??? */ +	u8	nb_snooplat;		/* 0x1a - ??? */ +	u8	nb_snoopwin;		/* 0x1b - ??? */ +	u8	api_bus_mode;		/* 0x1c - ??? */ +	u8	reserved2[3];		/* 0x1d - */ +	u32	input_clk_freq_low;	/* 0x20 - Input clock frequency low */ +	u8	processor_card_slot;	/* 0x24 - Processor card slot number */ +	u8	reserved3[2];		/* 0x25 - */ +	u8	padjmax;       		/* 0x27 - Max power adjustment (Not in OF!) */ +	u8	ttarget;		/* 0x28 - Target temperature */ +	u8	tmax;			/* 0x29 - Max temperature */ +	u8	pmaxh;			/* 0x2a - Max power */ +	u8	tguardband;		/* 0x2b - Guardband temp ??? Hist. len in OSX */ +	fs32	pid_gp;			/* 0x2c - PID proportional gain */ +	fs32	pid_gr;			/* 0x30 - PID reset gain */ +	fs32	pid_gd;			/* 0x34 - PID derivative gain */ +	fu16	voph;			/* 0x38 - Vop High */ +	fu16	vopl;			/* 0x3a - Vop Low */ +	fs16	nactual_die;		/* 0x3c - nActual Die */ +	fs16	nactual_heatsink;	/* 0x3e - nActual Heatsink */ +	fs16	nactual_system;		/* 0x40 - nActual System */ +	u16	calibration_flags;	/* 0x42 - Calibration flags */ +	fu16	mdiode;			/* 0x44 - Diode M value (scaling factor) */ +	fs16	bdiode;			/* 0x46 - Diode B value (offset) */ +	fs32	theta_heat_sink;	/* 0x48 - Theta heat sink */ +	u16	rminn_intake_fan;	/* 0x4c - Intake fan min RPM */ +	u16	rmaxn_intake_fan;	/* 0x4e - Intake fan max RPM */ +	u16	rminn_exhaust_fan;	/* 0x50 - Exhaust fan min RPM */ +	u16	rmaxn_exhaust_fan;	/* 0x52 - Exhaust fan max RPM */ +	u8	processor_part_num[8];	/* 0x54 - Processor part number XX pumps min/max */ +	u32	processor_lot_num;	/* 0x5c - Processor lot number */ +	u8	orig_card_sernum[0x10];	/* 0x60 - Card original serial number */ +	u8	curr_card_sernum[0x10];	/* 0x70 - Card current serial number */ +	u8	mlb_sernum[0x18];	/* 0x80 - MLB serial number */ +	u32	checksum1;		/* 0x98 - */ +	u32	checksum2;		/* 0x9c - */	 +}; /* Total size = 0xa0 */ + +static inline const struct mpu_data *wf_get_mpu(int cpu) +{ +	struct device_node *np; +	char nodename[64]; +	const void *data; +	int len; + +	/* +	 * prom.c routine for finding a node by path is a bit brain dead +	 * and requires exact @xxx unit numbers. This is a bit ugly but +	 * will work for these machines +	 */ +	sprintf(nodename, "/u3@0,f8000000/i2c@f8001000/cpuid@a%d", cpu ? 2 : 0); +	np = of_find_node_by_path(nodename); +	if (!np) +		return NULL; +	data = of_get_property(np, "cpuid", &len);	 +	of_node_put(np); +	if (!data) +		return NULL; + +	/* +	 * We are naughty, we have dropped the reference to the device +	 * node and still return a pointer to the content. We know we +	 * can do that though as this is only ever called on PowerMac +	 * which cannot remove those nodes +	 */ +	return data; +} + +#endif /*  __WINDFARM_MPU_H */ diff --git a/drivers/macintosh/windfarm_pm72.c b/drivers/macintosh/windfarm_pm72.c new file mode 100644 index 000000000000..84ac913d7e3a --- /dev/null +++ b/drivers/macintosh/windfarm_pm72.c @@ -0,0 +1,847 @@ +/* + * Windfarm PowerMac thermal control. + * Control loops for PowerMac7,2 and 7,3 + * + * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Use and redistribute under the terms of the GNU GPL v2. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <asm/prom.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#undef DEBUG +#undef LOTSA_DEBUG + +#ifdef DEBUG +#define DBG(args...)	printk(args) +#else +#define DBG(args...)	do { } while(0) +#endif + +#ifdef LOTSA_DEBUG +#define DBG_LOTS(args...)	printk(args) +#else +#define DBG_LOTS(args...)	do { } while(0) +#endif + +/* define this to force CPU overtemp to 60 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* We currently only handle 2 chips */ +#define NR_CHIPS	2 +#define NR_CPU_FANS	3 * NR_CHIPS + +/* Controls and sensors */ +static struct wf_sensor *sens_cpu_temp[NR_CHIPS]; +static struct wf_sensor *sens_cpu_volts[NR_CHIPS]; +static struct wf_sensor *sens_cpu_amps[NR_CHIPS]; +static struct wf_sensor *backside_temp; +static struct wf_sensor *drives_temp; + +static struct wf_control *cpu_front_fans[NR_CHIPS]; +static struct wf_control *cpu_rear_fans[NR_CHIPS]; +static struct wf_control *cpu_pumps[NR_CHIPS]; +static struct wf_control *backside_fan; +static struct wf_control *drives_fan; +static struct wf_control *slots_fan; +static struct wf_control *cpufreq_clamp; + +/* We keep a temperature history for average calculation of 180s */ +#define CPU_TEMP_HIST_SIZE	180 + +/* Fixed speed for slot fan */ +#define	SLOTS_FAN_DEFAULT_PWM	40 + +/* Scale value for CPU intake fans */ +#define CPU_INTAKE_SCALE	0x0000f852 + +/* PID loop state */ +static const struct mpu_data *cpu_mpu_data[NR_CHIPS]; +static struct wf_cpu_pid_state cpu_pid[NR_CHIPS]; +static bool cpu_pid_combined; +static u32 cpu_thist[CPU_TEMP_HIST_SIZE]; +static int cpu_thist_pt; +static s64 cpu_thist_total; +static s32 cpu_all_tmax = 100 << 16; +static struct wf_pid_state backside_pid; +static int backside_tick; +static struct wf_pid_state drives_pid; +static int drives_tick; + +static int nr_chips; +static bool have_all_controls; +static bool have_all_sensors; +static bool started; + +static int failure_state; +#define FAILURE_SENSOR		1 +#define FAILURE_FAN		2 +#define FAILURE_PERM		4 +#define FAILURE_LOW_OVERTEMP	8 +#define FAILURE_HIGH_OVERTEMP	16 + +/* Overtemp values */ +#define LOW_OVER_AVERAGE	0 +#define LOW_OVER_IMMEDIATE	(10 << 16) +#define LOW_OVER_CLEAR		((-10) << 16) +#define HIGH_OVER_IMMEDIATE	(14 << 16) +#define HIGH_OVER_AVERAGE	(10 << 16) +#define HIGH_OVER_IMMEDIATE	(14 << 16) + + +static void cpu_max_all_fans(void) +{ +	int i; + +	/* We max all CPU fans in case of a sensor error. We also do the +	 * cpufreq clamping now, even if it's supposedly done later by the +	 * generic code anyway, we do it earlier here to react faster +	 */ +	if (cpufreq_clamp) +		wf_control_set_max(cpufreq_clamp); +	for (i = 0; i < nr_chips; i++) { +		if (cpu_front_fans[i]) +			wf_control_set_max(cpu_front_fans[i]); +		if (cpu_rear_fans[i]) +			wf_control_set_max(cpu_rear_fans[i]); +		if (cpu_pumps[i]) +			wf_control_set_max(cpu_pumps[i]); +	} +} + +static int cpu_check_overtemp(s32 temp) +{ +	int new_state = 0; +	s32 t_avg, t_old; +	static bool first = true; + +	/* First check for immediate overtemps */ +	if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) { +		new_state |= FAILURE_LOW_OVERTEMP; +		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Overtemp due to immediate CPU" +			       " temperature !\n"); +	} +	if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) { +		new_state |= FAILURE_HIGH_OVERTEMP; +		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Critical overtemp due to" +			       " immediate CPU temperature !\n"); +	} + +	/* +	 * The first time around, initialize the array with the first +	 * temperature reading +	 */ +	if (first) { +		int i; + +		cpu_thist_total = 0; +		for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) { +			cpu_thist[i] = temp; +			cpu_thist_total += temp; +		} +		first = false; +	} + +	/* +	 * We calculate a history of max temperatures and use that for the +	 * overtemp management +	 */ +	t_old = cpu_thist[cpu_thist_pt]; +	cpu_thist[cpu_thist_pt] = temp; +	cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE; +	cpu_thist_total -= t_old; +	cpu_thist_total += temp; +	t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE; + +	DBG_LOTS("  t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n", +		 FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp)); + +	/* Now check for average overtemps */ +	if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) { +		new_state |= FAILURE_LOW_OVERTEMP; +		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Overtemp due to average CPU" +			       " temperature !\n"); +	} +	if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) { +		new_state |= FAILURE_HIGH_OVERTEMP; +		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Critical overtemp due to" +			       " average CPU temperature !\n"); +	} + +	/* Now handle overtemp conditions. We don't currently use the windfarm +	 * overtemp handling core as it's not fully suited to the needs of those +	 * new machine. This will be fixed later. +	 */ +	if (new_state) { +		/* High overtemp -> immediate shutdown */ +		if (new_state & FAILURE_HIGH_OVERTEMP) +			machine_power_off(); +		if ((failure_state & new_state) != new_state) +			cpu_max_all_fans(); +		failure_state |= new_state; +	} else if ((failure_state & FAILURE_LOW_OVERTEMP) && +		   (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) { +		printk(KERN_ERR "windfarm: Overtemp condition cleared !\n"); +		failure_state &= ~FAILURE_LOW_OVERTEMP; +	} + +	return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP); +} + +static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power) +{ +	s32 dtemp, volts, amps; +	int rc; + +	/* Get diode temperature */ +	rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp); +	if (rc) { +		DBG("  CPU%d: temp reading error !\n", cpu); +		return -EIO; +	} +	DBG_LOTS("  CPU%d: temp   = %d.%03d\n", cpu, FIX32TOPRINT((dtemp))); +	*temp = dtemp; + +	/* Get voltage */ +	rc = wf_sensor_get(sens_cpu_volts[cpu], &volts); +	if (rc) { +		DBG("  CPU%d, volts reading error !\n", cpu); +		return -EIO; +	} +	DBG_LOTS("  CPU%d: volts  = %d.%03d\n", cpu, FIX32TOPRINT((volts))); + +	/* Get current */ +	rc = wf_sensor_get(sens_cpu_amps[cpu], &s); +	if (rc) { +		DBG("  CPU%d, current reading error !\n", cpu); +		return -EIO; +	} +	DBG_LOTS("  CPU%d: amps   = %d.%03d\n", cpu, FIX32TOPRINT((amps))); + +	/* Calculate power */ + +	/* Scale voltage and current raw sensor values according to fixed scales +	 * obtained in Darwin and calculate power from I and V +	 */ +	*power = (((u64)volts) * ((u64)amps)) >> 16; + +	DBG_LOTS("  CPU%d: power  = %d.%03d\n", cpu, FIX32TOPRINT((*power))); + +	return 0; + +} + +static void cpu_fans_tick_split(void) +{ +	int err, cpu; +	s32 intake, temp, power, t_max = 0; + +	DBG_LOTS("* cpu fans_tick_split()\n"); + +	for (cpu = 0; cpu < nr_chips; ++cpu) { +		struct wf_cpu_pid_state *sp = &cpu_pid[cpu]; + +		/* Read current speed */ +		wf_control_get(cpu_rear_fans[cpu], &sp->target); + +		DBG_LOTS("  CPU%d: cur_target = %d RPM\n", cpu, sp->target); + +		err = read_one_cpu_vals(cpu, &temp, &power); +		if (err) { +			failure_state |= FAILURE_SENSOR; +			cpu_max_all_fans(); +			return; +		} + +		/* Keep track of highest temp */ +		t_max = max(t_max, temp); + +		/* Handle possible overtemps */ +		if (cpu_check_overtemp(t_max)) +			return; + +		/* Run PID */ +		wf_cpu_pid_run(sp, power, temp); + +		DBG_LOTS("  CPU%d: target = %d RPM\n", cpu, sp->target); + +		/* Apply result directly to exhaust fan */ +		err = wf_control_set(cpu_rear_fans[cpu], sp->target); +		if (err) { +			pr_warning("wf_pm72: Fan %s reports error %d\n", +			       cpu_rear_fans[cpu]->name, err); +			failure_state |= FAILURE_FAN; +			break; +		} + +		/* Scale result for intake fan */ +		intake = (sp->target * CPU_INTAKE_SCALE) >> 16; +		DBG_LOTS("  CPU%d: intake = %d RPM\n", cpu, intake); +		err = wf_control_set(cpu_front_fans[cpu], intake); +		if (err) { +			pr_warning("wf_pm72: Fan %s reports error %d\n", +			       cpu_front_fans[cpu]->name, err); +			failure_state |= FAILURE_FAN; +			break; +		} +	} +} + +static void cpu_fans_tick_combined(void) +{ +	s32 temp0, power0, temp1, power1, t_max = 0; +	s32 temp, power, intake, pump; +	struct wf_control *pump0, *pump1; +	struct wf_cpu_pid_state *sp = &cpu_pid[0]; +	int err, cpu; + +	DBG_LOTS("* cpu fans_tick_combined()\n"); + +	/* Read current speed from cpu 0 */ +	wf_control_get(cpu_rear_fans[0], &sp->target); + +	DBG_LOTS("  CPUs: cur_target = %d RPM\n", sp->target); + +	/* Read values for both CPUs */ +	err = read_one_cpu_vals(0, &temp0, &power0); +	if (err) { +		failure_state |= FAILURE_SENSOR; +		cpu_max_all_fans(); +		return; +	} +	err = read_one_cpu_vals(1, &temp1, &power1); +	if (err) { +		failure_state |= FAILURE_SENSOR; +		cpu_max_all_fans(); +		return; +	} + +	/* Keep track of highest temp */ +	t_max = max(t_max, max(temp0, temp1)); + +	/* Handle possible overtemps */ +	if (cpu_check_overtemp(t_max)) +		return; + +	/* Use the max temp & power of both */ +	temp = max(temp0, temp1); +	power = max(power0, power1); + +	/* Run PID */ +	wf_cpu_pid_run(sp, power, temp); + +	/* Scale result for intake fan */ +	intake = (sp->target * CPU_INTAKE_SCALE) >> 16; + +	/* Same deal with pump speed */ +	pump0 = cpu_pumps[0]; +	pump1 = cpu_pumps[1]; +	if (!pump0) { +		pump0 = pump1; +		pump1 = NULL; +	} +	pump = (sp->target * wf_control_get_max(pump0)) / +		cpu_mpu_data[0]->rmaxn_exhaust_fan; + +	DBG_LOTS("  CPUs: target = %d RPM\n", sp->target); +	DBG_LOTS("  CPUs: intake = %d RPM\n", intake); +	DBG_LOTS("  CPUs: pump   = %d RPM\n", pump); + +	for (cpu = 0; cpu < nr_chips; cpu++) { +		err = wf_control_set(cpu_rear_fans[cpu], sp->target); +		if (err) { +			pr_warning("wf_pm72: Fan %s reports error %d\n", +				   cpu_rear_fans[cpu]->name, err); +			failure_state |= FAILURE_FAN; +		} +		err = wf_control_set(cpu_front_fans[cpu], intake); +		if (err) { +			pr_warning("wf_pm72: Fan %s reports error %d\n", +				   cpu_front_fans[cpu]->name, err); +			failure_state |= FAILURE_FAN; +		} +		err = 0; +		if (cpu_pumps[cpu]) +			err = wf_control_set(cpu_pumps[cpu], pump); +		if (err) { +			pr_warning("wf_pm72: Pump %s reports error %d\n", +				   cpu_pumps[cpu]->name, err); +			failure_state |= FAILURE_FAN; +		} +	} +} + +/* Implementation... */ +static int cpu_setup_pid(int cpu) +{ +	struct wf_cpu_pid_param pid; +	const struct mpu_data *mpu = cpu_mpu_data[cpu]; +	s32 tmax, ttarget, ptarget; +	int fmin, fmax, hsize; + +	/* Get PID params from the appropriate MPU EEPROM */ +	tmax = mpu->tmax << 16; +	ttarget = mpu->ttarget << 16; +	ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16; + +	DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n", +	    cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax)); + +	/* We keep a global tmax for overtemp calculations */ +	if (tmax < cpu_all_tmax) +		cpu_all_tmax = tmax; + +	/* Set PID min/max by using the rear fan min/max */ +	fmin = wf_control_get_min(cpu_rear_fans[cpu]); +	fmax = wf_control_get_max(cpu_rear_fans[cpu]); +	DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax); + +	/* History size */ +	hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY); +	DBG("wf_72: CPU%d history size = %d\n", cpu, hsize); + +	/* Initialize PID loop */ +	pid.interval	= 1;	/* seconds */ +	pid.history_len = hsize; +	pid.gd		= mpu->pid_gd; +	pid.gp		= mpu->pid_gp; +	pid.gr		= mpu->pid_gr; +	pid.tmax	= tmax; +	pid.ttarget	= ttarget; +	pid.pmaxadj	= ptarget; +	pid.min		= fmin; +	pid.max		= fmax; + +	wf_cpu_pid_init(&cpu_pid[cpu], &pid); +	cpu_pid[cpu].target = 1000; + +	return 0; +} + +/* Backside/U3 fan */ +static struct wf_pid_param backside_u3_param = { +	.interval	= 5, +	.history_len	= 2, +	.gd		= 40 << 20, +	.gp		= 5 << 20, +	.gr		= 0, +	.itarget	= 65 << 16, +	.additive	= 1, +	.min		= 20, +	.max		= 100, +}; + +static struct wf_pid_param backside_u3h_param = { +	.interval	= 5, +	.history_len	= 2, +	.gd		= 20 << 20, +	.gp		= 5 << 20, +	.gr		= 0, +	.itarget	= 75 << 16, +	.additive	= 1, +	.min		= 20, +	.max		= 100, +}; + +static void backside_fan_tick(void) +{ +	s32 temp; +	int speed; +	int err; + +	if (!backside_fan || !backside_temp || !backside_tick) +		return; +	if (--backside_tick > 0) +		return; +	backside_tick = backside_pid.param.interval; + +	DBG_LOTS("* backside fans tick\n"); + +	/* Update fan speed from actual fans */ +	err = wf_control_get(backside_fan, &speed); +	if (!err) +		backside_pid.target = speed; + +	err = wf_sensor_get(backside_temp, &temp); +	if (err) { +		printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n", +		       err); +		failure_state |= FAILURE_SENSOR; +		wf_control_set_max(backside_fan); +		return; +	} +	speed = wf_pid_run(&backside_pid, temp); + +	DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n", +		 FIX32TOPRINT(temp), speed); + +	err = wf_control_set(backside_fan, speed); +	if (err) { +		printk(KERN_WARNING "windfarm: backside fan error %d\n", err); +		failure_state |= FAILURE_FAN; +	} +} + +static void backside_setup_pid(void) +{ +	/* first time initialize things */ +	s32 fmin = wf_control_get_min(backside_fan); +	s32 fmax = wf_control_get_max(backside_fan); +	struct wf_pid_param param; +	struct device_node *u3; +	int u3h = 1; /* conservative by default */ + +	u3 = of_find_node_by_path("/u3@0,f8000000"); +	if (u3 != NULL) { +		const u32 *vers = of_get_property(u3, "device-rev", NULL); +		if (vers) +			if (((*vers) & 0x3f) < 0x34) +				u3h = 0; +		of_node_put(u3); +	} + +	param = u3h ? backside_u3h_param : backside_u3_param; + +	param.min = max(param.min, fmin); +	param.max = min(param.max, fmax); +	wf_pid_init(&backside_pid, ¶m); +	backside_tick = 1; + +	pr_info("wf_pm72: Backside control loop started.\n"); +} + +/* Drive bay fan */ +static const struct wf_pid_param drives_param = { +	.interval	= 5, +	.history_len	= 2, +	.gd		= 30 << 20, +	.gp		= 5 << 20, +	.gr		= 0, +	.itarget	= 40 << 16, +	.additive	= 1, +	.min		= 300, +	.max		= 4000, +}; + +static void drives_fan_tick(void) +{ +	s32 temp; +	int speed; +	int err; + +	if (!drives_fan || !drives_temp || !drives_tick) +		return; +	if (--drives_tick > 0) +		return; +	drives_tick = drives_pid.param.interval; + +	DBG_LOTS("* drives fans tick\n"); + +	/* Update fan speed from actual fans */ +	err = wf_control_get(drives_fan, &speed); +	if (!err) +		drives_pid.target = speed; + +	err = wf_sensor_get(drives_temp, &temp); +	if (err) { +		pr_warning("wf_pm72: drive bay temp sensor error %d\n", err); +		failure_state |= FAILURE_SENSOR; +		wf_control_set_max(drives_fan); +		return; +	} +	speed = wf_pid_run(&drives_pid, temp); + +	DBG_LOTS("drives PID temp=%d.%.3d speed=%d\n", +		 FIX32TOPRINT(temp), speed); + +	err = wf_control_set(drives_fan, speed); +	if (err) { +		printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err); +		failure_state |= FAILURE_FAN; +	} +} + +static void drives_setup_pid(void) +{ +	/* first time initialize things */ +	s32 fmin = wf_control_get_min(drives_fan); +	s32 fmax = wf_control_get_max(drives_fan); +	struct wf_pid_param param = drives_param; + +	param.min = max(param.min, fmin); +	param.max = min(param.max, fmax); +	wf_pid_init(&drives_pid, ¶m); +	drives_tick = 1; + +	pr_info("wf_pm72: Drive bay control loop started.\n"); +} + +static void set_fail_state(void) +{ +	cpu_max_all_fans(); + +	if (backside_fan) +		wf_control_set_max(backside_fan); +	if (slots_fan) +		wf_control_set_max(slots_fan); +	if (drives_fan) +		wf_control_set_max(drives_fan); +} + +static void pm72_tick(void) +{ +	int i, last_failure; + +	if (!started) { +		started = 1; +		printk(KERN_INFO "windfarm: CPUs control loops started.\n"); +		for (i = 0; i < nr_chips; ++i) { +			if (cpu_setup_pid(i) < 0) { +				failure_state = FAILURE_PERM; +				set_fail_state(); +				break; +			} +		} +		DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax)); + +		backside_setup_pid(); +		drives_setup_pid(); + +		/* +		 * We don't have the right stuff to drive the PCI fan +		 * so we fix it to a default value +		 */ +		wf_control_set(slots_fan, SLOTS_FAN_DEFAULT_PWM); + +#ifdef HACKED_OVERTEMP +		cpu_all_tmax = 60 << 16; +#endif +	} + +	/* Permanent failure, bail out */ +	if (failure_state & FAILURE_PERM) +		return; + +	/* +	 * Clear all failure bits except low overtemp which will be eventually +	 * cleared by the control loop itself +	 */ +	last_failure = failure_state; +	failure_state &= FAILURE_LOW_OVERTEMP; +	if (cpu_pid_combined) +		cpu_fans_tick_combined(); +	else +		cpu_fans_tick_split(); +	backside_fan_tick(); +	drives_fan_tick(); + +	DBG_LOTS("  last_failure: 0x%x, failure_state: %x\n", +		 last_failure, failure_state); + +	/* Check for failures. Any failure causes cpufreq clamping */ +	if (failure_state && last_failure == 0 && cpufreq_clamp) +		wf_control_set_max(cpufreq_clamp); +	if (failure_state == 0 && last_failure && cpufreq_clamp) +		wf_control_set_min(cpufreq_clamp); + +	/* That's it for now, we might want to deal with other failures +	 * differently in the future though +	 */ +} + +static void pm72_new_control(struct wf_control *ct) +{ +	bool all_controls; +	bool had_pump = cpu_pumps[0] || cpu_pumps[1]; + +	if (!strcmp(ct->name, "cpu-front-fan-0")) +		cpu_front_fans[0] = ct; +	else if (!strcmp(ct->name, "cpu-front-fan-1")) +		cpu_front_fans[1] = ct; +	else if (!strcmp(ct->name, "cpu-rear-fan-0")) +		cpu_rear_fans[0] = ct; +	else if (!strcmp(ct->name, "cpu-rear-fan-1")) +		cpu_rear_fans[1] = ct; +	else if (!strcmp(ct->name, "cpu-pump-0")) +		cpu_pumps[0] = ct; +	else if (!strcmp(ct->name, "cpu-pump-1")) +		cpu_pumps[1] = ct; +	else if (!strcmp(ct->name, "backside-fan")) +		backside_fan = ct; +	else if (!strcmp(ct->name, "slots-fan")) +		slots_fan = ct; +	else if (!strcmp(ct->name, "drive-bay-fan")) +		drives_fan = ct; +	else if (!strcmp(ct->name, "cpufreq-clamp")) +		cpufreq_clamp = ct; + +	all_controls = +		cpu_front_fans[0] && +		cpu_rear_fans[0] && +		backside_fan && +		slots_fan && +		drives_fan; +	if (nr_chips > 1) +		all_controls &= +			cpu_front_fans[1] && +			cpu_rear_fans[1]; +	have_all_controls = all_controls; + +	if ((cpu_pumps[0] || cpu_pumps[1]) && !had_pump) { +		pr_info("wf_pm72: Liquid cooling pump(s) detected," +			" using new algorithm !\n"); +		cpu_pid_combined = true; +	} +} + + +static void pm72_new_sensor(struct wf_sensor *sr) +{ +	bool all_sensors; + +	if (!strcmp(sr->name, "cpu-diode-temp-0")) +		sens_cpu_temp[0] = sr; +	else if (!strcmp(sr->name, "cpu-diode-temp-1")) +		sens_cpu_temp[1] = sr; +	else if (!strcmp(sr->name, "cpu-voltage-0")) +		sens_cpu_volts[0] = sr; +	else if (!strcmp(sr->name, "cpu-voltage-1")) +		sens_cpu_volts[1] = sr; +	else if (!strcmp(sr->name, "cpu-current-0")) +		sens_cpu_amps[0] = sr; +	else if (!strcmp(sr->name, "cpu-current-1")) +		sens_cpu_amps[1] = sr; +	else if (!strcmp(sr->name, "backside-temp")) +		backside_temp = sr; +	else if (!strcmp(sr->name, "hd-temp")) +		drives_temp = sr; + +	all_sensors = +		sens_cpu_temp[0] && +		sens_cpu_volts[0] && +		sens_cpu_amps[0] && +		backside_temp && +		drives_temp; +	if (nr_chips > 1) +		all_sensors &= +			sens_cpu_temp[1] && +			sens_cpu_volts[1] && +			sens_cpu_amps[1]; + +	have_all_sensors = all_sensors; +} + +static int pm72_wf_notify(struct notifier_block *self, +			  unsigned long event, void *data) +{ +	switch (event) { +	case WF_EVENT_NEW_SENSOR: +		pm72_new_sensor(data); +		break; +	case WF_EVENT_NEW_CONTROL: +		pm72_new_control(data); +		break; +	case WF_EVENT_TICK: +		if (have_all_controls && have_all_sensors) +			pm72_tick(); +	} +	return 0; +} + +static struct notifier_block pm72_events = { +	.notifier_call = pm72_wf_notify, +}; + +static int wf_pm72_probe(struct platform_device *dev) +{ +	wf_register_client(&pm72_events); +	return 0; +} + +static int __devexit wf_pm72_remove(struct platform_device *dev) +{ +	wf_unregister_client(&pm72_events); + +	/* should release all sensors and controls */ +	return 0; +} + +static struct platform_driver wf_pm72_driver = { +	.probe	= wf_pm72_probe, +	.remove	= wf_pm72_remove, +	.driver	= { +		.name = "windfarm", +		.owner	= THIS_MODULE, +	}, +}; + +static int __init wf_pm72_init(void) +{ +	struct device_node *cpu; +	int i; + +	if (!of_machine_is_compatible("PowerMac7,2") && +	    !of_machine_is_compatible("PowerMac7,3")) +		return -ENODEV; + +	/* Count the number of CPU cores */ +	nr_chips = 0; +	for (cpu = NULL; (cpu = of_find_node_by_type(cpu, "cpu")) != NULL; ) +		++nr_chips; +	if (nr_chips > NR_CHIPS) +		nr_chips = NR_CHIPS; + +	pr_info("windfarm: Initializing for desktop G5 with %d chips\n", +		nr_chips); + +	/* Get MPU data for each CPU */ +	for (i = 0; i < nr_chips; i++) { +		cpu_mpu_data[i] = wf_get_mpu(i); +		if (!cpu_mpu_data[i]) { +			pr_err("wf_pm72: Failed to find MPU data for CPU %d\n", i); +			return -ENXIO; +		} +	} + +#ifdef MODULE +	request_module("windfarm_fcu_controls"); +	request_module("windfarm_lm75_sensor"); +	request_module("windfarm_ad7417_sensor"); +	request_module("windfarm_max6690_sensor"); +	request_module("windfarm_cpufreq_clamp"); +#endif /* MODULE */ + +	platform_driver_register(&wf_pm72_driver); +	return 0; +} + +static void __exit wf_pm72_exit(void) +{ +	platform_driver_unregister(&wf_pm72_driver); +} + +module_init(wf_pm72_init); +module_exit(wf_pm72_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control for AGP PowerMac G5s"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_pm81.c b/drivers/macintosh/windfarm_pm81.c index fc13d0f2663b..990c87606be9 100644 --- a/drivers/macintosh/windfarm_pm81.c +++ b/drivers/macintosh/windfarm_pm81.c @@ -302,13 +302,13 @@ static void wf_smu_create_sys_fans(void)  	pid_param.interval = WF_SMU_SYS_FANS_INTERVAL;  	pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE;  	pid_param.itarget = param->itarget; -	pid_param.min = fan_system->ops->get_min(fan_system); -	pid_param.max = fan_system->ops->get_max(fan_system); +	pid_param.min = wf_control_get_min(fan_system); +	pid_param.max = wf_control_get_max(fan_system);  	if (fan_hd) {  		pid_param.min = -			max(pid_param.min,fan_hd->ops->get_min(fan_hd)); +			max(pid_param.min, wf_control_get_min(fan_hd));  		pid_param.max = -			min(pid_param.max,fan_hd->ops->get_max(fan_hd)); +			min(pid_param.max, wf_control_get_max(fan_hd));  	}  	wf_pid_init(&wf_smu_sys_fans->pid, &pid_param); @@ -337,7 +337,7 @@ static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)  	}  	st->ticks = WF_SMU_SYS_FANS_INTERVAL; -	rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); +	rc = wf_sensor_get(sensor_hd_temp, &temp);  	if (rc) {  		printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",  		       rc); @@ -373,7 +373,7 @@ static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)  	st->hd_setpoint = new_setpoint;   readjust:  	if (fan_system && wf_smu_failure_state == 0) { -		rc = fan_system->ops->set_value(fan_system, st->sys_setpoint); +		rc = wf_control_set(fan_system, st->sys_setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: Sys fan error %d\n",  			       rc); @@ -381,7 +381,7 @@ static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st)  		}  	}  	if (fan_hd && wf_smu_failure_state == 0) { -		rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint); +		rc = wf_control_set(fan_hd, st->hd_setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: HD fan error %d\n",  			       rc); @@ -447,8 +447,8 @@ static void wf_smu_create_cpu_fans(void)  	pid_param.ttarget = tmax - tdelta;  	pid_param.pmaxadj = maxpow - powadj; -	pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); -	pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); +	pid_param.min = wf_control_get_min(fan_cpu_main); +	pid_param.max = wf_control_get_max(fan_cpu_main);  	wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); @@ -481,7 +481,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  	}  	st->ticks = WF_SMU_CPU_FANS_INTERVAL; -	rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); +	rc = wf_sensor_get(sensor_cpu_temp, &temp);  	if (rc) {  		printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",  		       rc); @@ -489,7 +489,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  		return;  	} -	rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); +	rc = wf_sensor_get(sensor_cpu_power, &power);  	if (rc) {  		printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",  		       rc); @@ -525,8 +525,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  	st->cpu_setpoint = new_setpoint;   readjust:  	if (fan_cpu_main && wf_smu_failure_state == 0) { -		rc = fan_cpu_main->ops->set_value(fan_cpu_main, -						  st->cpu_setpoint); +		rc = wf_control_set(fan_cpu_main, st->cpu_setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: CPU main fan"  			       " error %d\n", rc); diff --git a/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c index a9430ed4f36c..7653603cb00e 100644 --- a/drivers/macintosh/windfarm_pm91.c +++ b/drivers/macintosh/windfarm_pm91.c @@ -192,8 +192,8 @@ static void wf_smu_create_cpu_fans(void)  	pid_param.ttarget = tmax - tdelta;  	pid_param.pmaxadj = maxpow - powadj; -	pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); -	pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); +	pid_param.min = wf_control_get_min(fan_cpu_main); +	pid_param.max = wf_control_get_max(fan_cpu_main);  	wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); @@ -226,7 +226,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  	}  	st->ticks = WF_SMU_CPU_FANS_INTERVAL; -	rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); +	rc = wf_sensor_get(sensor_cpu_temp, &temp);  	if (rc) {  		printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n",  		       rc); @@ -234,7 +234,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  		return;  	} -	rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); +	rc = wf_sensor_get(sensor_cpu_power, &power);  	if (rc) {  		printk(KERN_WARNING "windfarm: CPU power sensor error %d\n",  		       rc); @@ -261,8 +261,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  	st->cpu_setpoint = new_setpoint;   readjust:  	if (fan_cpu_main && wf_smu_failure_state == 0) { -		rc = fan_cpu_main->ops->set_value(fan_cpu_main, -						  st->cpu_setpoint); +		rc = wf_control_set(fan_cpu_main, st->cpu_setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: CPU main fan"  			       " error %d\n", rc); @@ -270,8 +269,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  		}  	}  	if (fan_cpu_second && wf_smu_failure_state == 0) { -		rc = fan_cpu_second->ops->set_value(fan_cpu_second, -						    st->cpu_setpoint); +		rc = wf_control_set(fan_cpu_second, st->cpu_setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: CPU second fan"  			       " error %d\n", rc); @@ -279,8 +277,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st)  		}  	}  	if (fan_cpu_third && wf_smu_failure_state == 0) { -		rc = fan_cpu_main->ops->set_value(fan_cpu_third, -						  st->cpu_setpoint); +		rc = wf_control_set(fan_cpu_third, st->cpu_setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: CPU third fan"  			       " error %d\n", rc); @@ -312,8 +309,8 @@ static void wf_smu_create_drive_fans(void)  	/* Fill PID params */  	param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN); -	param.min = fan_hd->ops->get_min(fan_hd); -	param.max = fan_hd->ops->get_max(fan_hd); +	param.min = wf_control_get_min(fan_hd); +	param.max = wf_control_get_max(fan_hd);  	wf_pid_init(&wf_smu_drive_fans->pid, ¶m);  	DBG("wf: Drive Fan control initialized.\n"); @@ -338,7 +335,7 @@ static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)  	}  	st->ticks = st->pid.param.interval; -	rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); +	rc = wf_sensor_get(sensor_hd_temp, &temp);  	if (rc) {  		printk(KERN_WARNING "windfarm: HD temp sensor error %d\n",  		       rc); @@ -361,7 +358,7 @@ static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st)  	st->setpoint = new_setpoint;   readjust:  	if (fan_hd && wf_smu_failure_state == 0) { -		rc = fan_hd->ops->set_value(fan_hd, st->setpoint); +		rc = wf_control_set(fan_hd, st->setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: HD fan error %d\n",  			       rc); @@ -393,8 +390,8 @@ static void wf_smu_create_slots_fans(void)  	/* Fill PID params */  	param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN); -	param.min = fan_slots->ops->get_min(fan_slots); -	param.max = fan_slots->ops->get_max(fan_slots); +	param.min = wf_control_get_min(fan_slots); +	param.max = wf_control_get_max(fan_slots);  	wf_pid_init(&wf_smu_slots_fans->pid, ¶m);  	DBG("wf: Slots Fan control initialized.\n"); @@ -419,7 +416,7 @@ static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)  	}  	st->ticks = st->pid.param.interval; -	rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power); +	rc = wf_sensor_get(sensor_slots_power, &power);  	if (rc) {  		printk(KERN_WARNING "windfarm: Slots power sensor error %d\n",  		       rc); @@ -444,7 +441,7 @@ static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st)  	st->setpoint = new_setpoint;   readjust:  	if (fan_slots && wf_smu_failure_state == 0) { -		rc = fan_slots->ops->set_value(fan_slots, st->setpoint); +		rc = wf_control_set(fan_slots, st->setpoint);  		if (rc) {  			printk(KERN_WARNING "windfarm: Slots fan error %d\n",  			       rc); diff --git a/drivers/macintosh/windfarm_rm31.c b/drivers/macintosh/windfarm_rm31.c new file mode 100644 index 000000000000..3eca6d4b52fc --- /dev/null +++ b/drivers/macintosh/windfarm_rm31.c @@ -0,0 +1,740 @@ +/* + * Windfarm PowerMac thermal control. + * Control loops for RackMack3,1 (Xserve G5) + * + * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Use and redistribute under the terms of the GNU GPL v2. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <asm/prom.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#undef DEBUG +#undef LOTSA_DEBUG + +#ifdef DEBUG +#define DBG(args...)	printk(args) +#else +#define DBG(args...)	do { } while(0) +#endif + +#ifdef LOTSA_DEBUG +#define DBG_LOTS(args...)	printk(args) +#else +#define DBG_LOTS(args...)	do { } while(0) +#endif + +/* define this to force CPU overtemp to 60 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* We currently only handle 2 chips */ +#define NR_CHIPS	2 +#define NR_CPU_FANS	3 * NR_CHIPS + +/* Controls and sensors */ +static struct wf_sensor *sens_cpu_temp[NR_CHIPS]; +static struct wf_sensor *sens_cpu_volts[NR_CHIPS]; +static struct wf_sensor *sens_cpu_amps[NR_CHIPS]; +static struct wf_sensor *backside_temp; +static struct wf_sensor *slots_temp; +static struct wf_sensor *dimms_temp; + +static struct wf_control *cpu_fans[NR_CHIPS][3]; +static struct wf_control *backside_fan; +static struct wf_control *slots_fan; +static struct wf_control *cpufreq_clamp; + +/* We keep a temperature history for average calculation of 180s */ +#define CPU_TEMP_HIST_SIZE	180 + +/* PID loop state */ +static const struct mpu_data *cpu_mpu_data[NR_CHIPS]; +static struct wf_cpu_pid_state cpu_pid[NR_CHIPS]; +static u32 cpu_thist[CPU_TEMP_HIST_SIZE]; +static int cpu_thist_pt; +static s64 cpu_thist_total; +static s32 cpu_all_tmax = 100 << 16; +static struct wf_pid_state backside_pid; +static int backside_tick; +static struct wf_pid_state slots_pid; +static int slots_tick; +static int slots_speed; +static struct wf_pid_state dimms_pid; +static int dimms_output_clamp; + +static int nr_chips; +static bool have_all_controls; +static bool have_all_sensors; +static bool started; + +static int failure_state; +#define FAILURE_SENSOR		1 +#define FAILURE_FAN		2 +#define FAILURE_PERM		4 +#define FAILURE_LOW_OVERTEMP	8 +#define FAILURE_HIGH_OVERTEMP	16 + +/* Overtemp values */ +#define LOW_OVER_AVERAGE	0 +#define LOW_OVER_IMMEDIATE	(10 << 16) +#define LOW_OVER_CLEAR		((-10) << 16) +#define HIGH_OVER_IMMEDIATE	(14 << 16) +#define HIGH_OVER_AVERAGE	(10 << 16) +#define HIGH_OVER_IMMEDIATE	(14 << 16) + + +static void cpu_max_all_fans(void) +{ +	int i; + +	/* We max all CPU fans in case of a sensor error. We also do the +	 * cpufreq clamping now, even if it's supposedly done later by the +	 * generic code anyway, we do it earlier here to react faster +	 */ +	if (cpufreq_clamp) +		wf_control_set_max(cpufreq_clamp); +	for (i = 0; i < nr_chips; i++) { +		if (cpu_fans[i][0]) +			wf_control_set_max(cpu_fans[i][0]); +		if (cpu_fans[i][1]) +			wf_control_set_max(cpu_fans[i][1]); +		if (cpu_fans[i][2]) +			wf_control_set_max(cpu_fans[i][2]); +	} +} + +static int cpu_check_overtemp(s32 temp) +{ +	int new_state = 0; +	s32 t_avg, t_old; +	static bool first = true; + +	/* First check for immediate overtemps */ +	if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) { +		new_state |= FAILURE_LOW_OVERTEMP; +		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Overtemp due to immediate CPU" +			       " temperature !\n"); +	} +	if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) { +		new_state |= FAILURE_HIGH_OVERTEMP; +		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Critical overtemp due to" +			       " immediate CPU temperature !\n"); +	} + +	/* +	 * The first time around, initialize the array with the first +	 * temperature reading +	 */ +	if (first) { +		int i; + +		cpu_thist_total = 0; +		for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) { +			cpu_thist[i] = temp; +			cpu_thist_total += temp; +		} +		first = false; +	} + +	/* +	 * We calculate a history of max temperatures and use that for the +	 * overtemp management +	 */ +	t_old = cpu_thist[cpu_thist_pt]; +	cpu_thist[cpu_thist_pt] = temp; +	cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE; +	cpu_thist_total -= t_old; +	cpu_thist_total += temp; +	t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE; + +	DBG_LOTS("  t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n", +		 FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp)); + +	/* Now check for average overtemps */ +	if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) { +		new_state |= FAILURE_LOW_OVERTEMP; +		if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Overtemp due to average CPU" +			       " temperature !\n"); +	} +	if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) { +		new_state |= FAILURE_HIGH_OVERTEMP; +		if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) +			printk(KERN_ERR "windfarm: Critical overtemp due to" +			       " average CPU temperature !\n"); +	} + +	/* Now handle overtemp conditions. We don't currently use the windfarm +	 * overtemp handling core as it's not fully suited to the needs of those +	 * new machine. This will be fixed later. +	 */ +	if (new_state) { +		/* High overtemp -> immediate shutdown */ +		if (new_state & FAILURE_HIGH_OVERTEMP) +			machine_power_off(); +		if ((failure_state & new_state) != new_state) +			cpu_max_all_fans(); +		failure_state |= new_state; +	} else if ((failure_state & FAILURE_LOW_OVERTEMP) && +		   (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) { +		printk(KERN_ERR "windfarm: Overtemp condition cleared !\n"); +		failure_state &= ~FAILURE_LOW_OVERTEMP; +	} + +	return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP); +} + +static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power) +{ +	s32 dtemp, volts, amps; +	int rc; + +	/* Get diode temperature */ +	rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp); +	if (rc) { +		DBG("  CPU%d: temp reading error !\n", cpu); +		return -EIO; +	} +	DBG_LOTS("  CPU%d: temp   = %d.%03d\n", cpu, FIX32TOPRINT((dtemp))); +	*temp = dtemp; + +	/* Get voltage */ +	rc = wf_sensor_get(sens_cpu_volts[cpu], &volts); +	if (rc) { +		DBG("  CPU%d, volts reading error !\n", cpu); +		return -EIO; +	} +	DBG_LOTS("  CPU%d: volts  = %d.%03d\n", cpu, FIX32TOPRINT((volts))); + +	/* Get current */ +	rc = wf_sensor_get(sens_cpu_amps[cpu], &s); +	if (rc) { +		DBG("  CPU%d, current reading error !\n", cpu); +		return -EIO; +	} +	DBG_LOTS("  CPU%d: amps   = %d.%03d\n", cpu, FIX32TOPRINT((amps))); + +	/* Calculate power */ + +	/* Scale voltage and current raw sensor values according to fixed scales +	 * obtained in Darwin and calculate power from I and V +	 */ +	*power = (((u64)volts) * ((u64)amps)) >> 16; + +	DBG_LOTS("  CPU%d: power  = %d.%03d\n", cpu, FIX32TOPRINT((*power))); + +	return 0; + +} + +static void cpu_fans_tick(void) +{ +	int err, cpu, i; +	s32 speed, temp, power, t_max = 0; + +	DBG_LOTS("* cpu fans_tick_split()\n"); + +	for (cpu = 0; cpu < nr_chips; ++cpu) { +		struct wf_cpu_pid_state *sp = &cpu_pid[cpu]; + +		/* Read current speed */ +		wf_control_get(cpu_fans[cpu][0], &sp->target); + +		err = read_one_cpu_vals(cpu, &temp, &power); +		if (err) { +			failure_state |= FAILURE_SENSOR; +			cpu_max_all_fans(); +			return; +		} + +		/* Keep track of highest temp */ +		t_max = max(t_max, temp); + +		/* Handle possible overtemps */ +		if (cpu_check_overtemp(t_max)) +			return; + +		/* Run PID */ +		wf_cpu_pid_run(sp, power, temp); + +		DBG_LOTS("  CPU%d: target = %d RPM\n", cpu, sp->target); + +		/* Apply DIMMs clamp */ +		speed = max(sp->target, dimms_output_clamp); + +		/* Apply result to all cpu fans */ +		for (i = 0; i < 3; i++) { +			err = wf_control_set(cpu_fans[cpu][i], speed); +			if (err) { +				pr_warning("wf_rm31: Fan %s reports error %d\n", +					   cpu_fans[cpu][i]->name, err); +				failure_state |= FAILURE_FAN; +			} +		} +	} +} + +/* Implementation... */ +static int cpu_setup_pid(int cpu) +{ +	struct wf_cpu_pid_param pid; +	const struct mpu_data *mpu = cpu_mpu_data[cpu]; +	s32 tmax, ttarget, ptarget; +	int fmin, fmax, hsize; + +	/* Get PID params from the appropriate MPU EEPROM */ +	tmax = mpu->tmax << 16; +	ttarget = mpu->ttarget << 16; +	ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16; + +	DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n", +	    cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax)); + +	/* We keep a global tmax for overtemp calculations */ +	if (tmax < cpu_all_tmax) +		cpu_all_tmax = tmax; + +	/* Set PID min/max by using the rear fan min/max */ +	fmin = wf_control_get_min(cpu_fans[cpu][0]); +	fmax = wf_control_get_max(cpu_fans[cpu][0]); +	DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax); + +	/* History size */ +	hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY); +	DBG("wf_72: CPU%d history size = %d\n", cpu, hsize); + +	/* Initialize PID loop */ +	pid.interval	= 1;	/* seconds */ +	pid.history_len = hsize; +	pid.gd		= mpu->pid_gd; +	pid.gp		= mpu->pid_gp; +	pid.gr		= mpu->pid_gr; +	pid.tmax	= tmax; +	pid.ttarget	= ttarget; +	pid.pmaxadj	= ptarget; +	pid.min		= fmin; +	pid.max		= fmax; + +	wf_cpu_pid_init(&cpu_pid[cpu], &pid); +	cpu_pid[cpu].target = 4000; +	 +	return 0; +} + +/* Backside/U3 fan */ +static struct wf_pid_param backside_param = { +	.interval	= 1, +	.history_len	= 2, +	.gd		= 0x00500000, +	.gp		= 0x0004cccc, +	.gr		= 0, +	.itarget	= 70 << 16, +	.additive	= 0, +	.min		= 20, +	.max		= 100, +}; + +/* DIMMs temperature (clamp the backside fan) */ +static struct wf_pid_param dimms_param = { +	.interval	= 1, +	.history_len	= 20, +	.gd		= 0, +	.gp		= 0, +	.gr		= 0x06553600, +	.itarget	= 50 << 16, +	.additive	= 0, +	.min		= 4000, +	.max		= 14000, +}; + +static void backside_fan_tick(void) +{ +	s32 temp, dtemp; +	int speed, dspeed, fan_min; +	int err; + +	if (!backside_fan || !backside_temp || !dimms_temp || !backside_tick) +		return; +	if (--backside_tick > 0) +		return; +	backside_tick = backside_pid.param.interval; + +	DBG_LOTS("* backside fans tick\n"); + +	/* Update fan speed from actual fans */ +	err = wf_control_get(backside_fan, &speed); +	if (!err) +		backside_pid.target = speed; + +	err = wf_sensor_get(backside_temp, &temp); +	if (err) { +		printk(KERN_WARNING "windfarm: U3 temp sensor error %d\n", +		       err); +		failure_state |= FAILURE_SENSOR; +		wf_control_set_max(backside_fan); +		return; +	} +	speed = wf_pid_run(&backside_pid, temp); + +	DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n", +		 FIX32TOPRINT(temp), speed); + +	err = wf_sensor_get(dimms_temp, &dtemp); +	if (err) { +		printk(KERN_WARNING "windfarm: DIMMs temp sensor error %d\n", +		       err); +		failure_state |= FAILURE_SENSOR; +		wf_control_set_max(backside_fan); +		return; +	} +	dspeed = wf_pid_run(&dimms_pid, dtemp); +	dimms_output_clamp = dspeed; + +	fan_min = (dspeed * 100) / 14000; +	fan_min = max(fan_min, backside_param.min); +	speed = max(speed, fan_min); + +	err = wf_control_set(backside_fan, speed); +	if (err) { +		printk(KERN_WARNING "windfarm: backside fan error %d\n", err); +		failure_state |= FAILURE_FAN; +	} +} + +static void backside_setup_pid(void) +{ +	/* first time initialize things */ +	s32 fmin = wf_control_get_min(backside_fan); +	s32 fmax = wf_control_get_max(backside_fan); +	struct wf_pid_param param; + +	param = backside_param; +	param.min = max(param.min, fmin); +	param.max = min(param.max, fmax); +	wf_pid_init(&backside_pid, ¶m); + +	param = dimms_param; +	wf_pid_init(&dimms_pid, ¶m); + +	backside_tick = 1; + +	pr_info("wf_rm31: Backside control loop started.\n"); +} + +/* Slots fan */ +static const struct wf_pid_param slots_param = { +	.interval	= 5, +	.history_len	= 2, +	.gd		= 30 << 20, +	.gp		= 5 << 20, +	.gr		= 0, +	.itarget	= 40 << 16, +	.additive	= 1, +	.min		= 300, +	.max		= 4000, +}; + +static void slots_fan_tick(void) +{ +	s32 temp; +	int speed; +	int err; + +	if (!slots_fan || !slots_temp || !slots_tick) +		return; +	if (--slots_tick > 0) +		return; +	slots_tick = slots_pid.param.interval; + +	DBG_LOTS("* slots fans tick\n"); + +	err = wf_sensor_get(slots_temp, &temp); +	if (err) { +		pr_warning("wf_rm31: slots temp sensor error %d\n", err); +		failure_state |= FAILURE_SENSOR; +		wf_control_set_max(slots_fan); +		return; +	} +	speed = wf_pid_run(&slots_pid, temp); + +	DBG_LOTS("slots PID temp=%d.%.3d speed=%d\n", +		 FIX32TOPRINT(temp), speed); + +	slots_speed = speed; +	err = wf_control_set(slots_fan, speed); +	if (err) { +		printk(KERN_WARNING "windfarm: slots bay fan error %d\n", err); +		failure_state |= FAILURE_FAN; +	} +} + +static void slots_setup_pid(void) +{ +	/* first time initialize things */ +	s32 fmin = wf_control_get_min(slots_fan); +	s32 fmax = wf_control_get_max(slots_fan); +	struct wf_pid_param param = slots_param; + +	param.min = max(param.min, fmin); +	param.max = min(param.max, fmax); +	wf_pid_init(&slots_pid, ¶m); +	slots_tick = 1; + +	pr_info("wf_rm31: Slots control loop started.\n"); +} + +static void set_fail_state(void) +{ +	cpu_max_all_fans(); + +	if (backside_fan) +		wf_control_set_max(backside_fan); +	if (slots_fan) +		wf_control_set_max(slots_fan); +} + +static void rm31_tick(void) +{ +	int i, last_failure; + +	if (!started) { +		started = 1; +		printk(KERN_INFO "windfarm: CPUs control loops started.\n"); +		for (i = 0; i < nr_chips; ++i) { +			if (cpu_setup_pid(i) < 0) { +				failure_state = FAILURE_PERM; +				set_fail_state(); +				break; +			} +		} +		DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax)); + +		backside_setup_pid(); +		slots_setup_pid(); + +#ifdef HACKED_OVERTEMP +		cpu_all_tmax = 60 << 16; +#endif +	} + +	/* Permanent failure, bail out */ +	if (failure_state & FAILURE_PERM) +		return; + +	/* +	 * Clear all failure bits except low overtemp which will be eventually +	 * cleared by the control loop itself +	 */ +	last_failure = failure_state; +	failure_state &= FAILURE_LOW_OVERTEMP; +	backside_fan_tick(); +	slots_fan_tick(); + +	/* We do CPUs last because they can be clamped high by +	 * DIMM temperature +	 */ +	cpu_fans_tick(); + +	DBG_LOTS("  last_failure: 0x%x, failure_state: %x\n", +		 last_failure, failure_state); + +	/* Check for failures. Any failure causes cpufreq clamping */ +	if (failure_state && last_failure == 0 && cpufreq_clamp) +		wf_control_set_max(cpufreq_clamp); +	if (failure_state == 0 && last_failure && cpufreq_clamp) +		wf_control_set_min(cpufreq_clamp); + +	/* That's it for now, we might want to deal with other failures +	 * differently in the future though +	 */ +} + +static void rm31_new_control(struct wf_control *ct) +{ +	bool all_controls; + +	if (!strcmp(ct->name, "cpu-fan-a-0")) +		cpu_fans[0][0] = ct; +	else if (!strcmp(ct->name, "cpu-fan-b-0")) +		cpu_fans[0][1] = ct; +	else if (!strcmp(ct->name, "cpu-fan-c-0")) +		cpu_fans[0][2] = ct; +	else if (!strcmp(ct->name, "cpu-fan-a-1")) +		cpu_fans[1][0] = ct; +	else if (!strcmp(ct->name, "cpu-fan-b-1")) +		cpu_fans[1][1] = ct; +	else if (!strcmp(ct->name, "cpu-fan-c-1")) +		cpu_fans[1][2] = ct; +	else if (!strcmp(ct->name, "backside-fan")) +		backside_fan = ct; +	else if (!strcmp(ct->name, "slots-fan")) +		slots_fan = ct; +	else if (!strcmp(ct->name, "cpufreq-clamp")) +		cpufreq_clamp = ct; + +	all_controls = +		cpu_fans[0][0] && +		cpu_fans[0][1] && +		cpu_fans[0][2] && +		backside_fan && +		slots_fan; +	if (nr_chips > 1) +		all_controls &= +			cpu_fans[1][0] && +			cpu_fans[1][1] && +			cpu_fans[1][2]; +	have_all_controls = all_controls; +} + + +static void rm31_new_sensor(struct wf_sensor *sr) +{ +	bool all_sensors; + +	if (!strcmp(sr->name, "cpu-diode-temp-0")) +		sens_cpu_temp[0] = sr; +	else if (!strcmp(sr->name, "cpu-diode-temp-1")) +		sens_cpu_temp[1] = sr; +	else if (!strcmp(sr->name, "cpu-voltage-0")) +		sens_cpu_volts[0] = sr; +	else if (!strcmp(sr->name, "cpu-voltage-1")) +		sens_cpu_volts[1] = sr; +	else if (!strcmp(sr->name, "cpu-current-0")) +		sens_cpu_amps[0] = sr; +	else if (!strcmp(sr->name, "cpu-current-1")) +		sens_cpu_amps[1] = sr; +	else if (!strcmp(sr->name, "backside-temp")) +		backside_temp = sr; +	else if (!strcmp(sr->name, "slots-temp")) +		slots_temp = sr; +	else if (!strcmp(sr->name, "dimms-temp")) +		dimms_temp = sr; + +	all_sensors = +		sens_cpu_temp[0] && +		sens_cpu_volts[0] && +		sens_cpu_amps[0] && +		backside_temp && +		slots_temp && +		dimms_temp; +	if (nr_chips > 1) +		all_sensors &= +			sens_cpu_temp[1] && +			sens_cpu_volts[1] && +			sens_cpu_amps[1]; + +	have_all_sensors = all_sensors; +} + +static int rm31_wf_notify(struct notifier_block *self, +			  unsigned long event, void *data) +{ +	switch (event) { +	case WF_EVENT_NEW_SENSOR: +		rm31_new_sensor(data); +		break; +	case WF_EVENT_NEW_CONTROL: +		rm31_new_control(data); +		break; +	case WF_EVENT_TICK: +		if (have_all_controls && have_all_sensors) +			rm31_tick(); +	} +	return 0; +} + +static struct notifier_block rm31_events = { +	.notifier_call = rm31_wf_notify, +}; + +static int wf_rm31_probe(struct platform_device *dev) +{ +	wf_register_client(&rm31_events); +	return 0; +} + +static int __devexit wf_rm31_remove(struct platform_device *dev) +{ +	wf_unregister_client(&rm31_events); + +	/* should release all sensors and controls */ +	return 0; +} + +static struct platform_driver wf_rm31_driver = { +	.probe	= wf_rm31_probe, +	.remove	= wf_rm31_remove, +	.driver	= { +		.name = "windfarm", +		.owner	= THIS_MODULE, +	}, +}; + +static int __init wf_rm31_init(void) +{ +	struct device_node *cpu; +	int i; + +	if (!of_machine_is_compatible("RackMac3,1")) +		return -ENODEV; + +	/* Count the number of CPU cores */ +	nr_chips = 0; +	for (cpu = NULL; (cpu = of_find_node_by_type(cpu, "cpu")) != NULL; ) +		++nr_chips; +	if (nr_chips > NR_CHIPS) +		nr_chips = NR_CHIPS; + +	pr_info("windfarm: Initializing for desktop G5 with %d chips\n", +		nr_chips); + +	/* Get MPU data for each CPU */ +	for (i = 0; i < nr_chips; i++) { +		cpu_mpu_data[i] = wf_get_mpu(i); +		if (!cpu_mpu_data[i]) { +			pr_err("wf_rm31: Failed to find MPU data for CPU %d\n", i); +			return -ENXIO; +		} +	} + +#ifdef MODULE +	request_module("windfarm_fcu_controls"); +	request_module("windfarm_lm75_sensor"); +	request_module("windfarm_lm87_sensor"); +	request_module("windfarm_ad7417_sensor"); +	request_module("windfarm_max6690_sensor"); +	request_module("windfarm_cpufreq_clamp"); +#endif /* MODULE */ + +	platform_driver_register(&wf_rm31_driver); +	return 0; +} + +static void __exit wf_rm31_exit(void) +{ +	platform_driver_unregister(&wf_rm31_driver); +} + +module_init(wf_rm31_init); +module_exit(wf_rm31_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Thermal control for Xserve G5"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_smu_controls.c b/drivers/macintosh/windfarm_smu_controls.c index 3c2be5193fd5..c155a54e8638 100644 --- a/drivers/macintosh/windfarm_smu_controls.c +++ b/drivers/macintosh/windfarm_smu_controls.c @@ -172,7 +172,6 @@ static struct smu_fan_control *smu_fan_create(struct device_node *node,  	fct->fan_type = pwm_fan;  	fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN; -	sysfs_attr_init(&fct->ctrl.attr.attr);  	/* We use the name & location here the same way we do for SMU sensors,  	 * see the comment in windfarm_smu_sensors.c. The locations are a bit diff --git a/drivers/macintosh/windfarm_smu_sat.c b/drivers/macintosh/windfarm_smu_sat.c index 65a8ff3e1f8e..426e810233d7 100644 --- a/drivers/macintosh/windfarm_smu_sat.c +++ b/drivers/macintosh/windfarm_smu_sat.c @@ -20,7 +20,7 @@  #include "windfarm.h" -#define VERSION "0.2" +#define VERSION "1.0"  #define DEBUG @@ -34,11 +34,12 @@  #define MAX_AGE		msecs_to_jiffies(800)  struct wf_sat { +	struct kref		ref;  	int			nr; -	atomic_t		refcnt;  	struct mutex		mutex;  	unsigned long		last_read; /* jiffies when cache last updated */  	u8			cache[16]; +	struct list_head	sensors;  	struct i2c_client	*i2c;  	struct device_node	*node;  }; @@ -46,11 +47,12 @@ struct wf_sat {  static struct wf_sat *sats[2];  struct wf_sat_sensor { -	int		index; -	int		index2;		/* used for power sensors */ -	int		shift; -	struct wf_sat	*sat; -	struct wf_sensor sens; +	struct list_head	link; +	int			index; +	int			index2;		/* used for power sensors */ +	int			shift; +	struct wf_sat		*sat; +	struct wf_sensor 	sens;  };  #define wf_to_sat(c)	container_of(c, struct wf_sat_sensor, sens) @@ -142,7 +144,7 @@ static int wf_sat_read_cache(struct wf_sat *sat)  	return 0;  } -static int wf_sat_get(struct wf_sensor *sr, s32 *value) +static int wf_sat_sensor_get(struct wf_sensor *sr, s32 *value)  {  	struct wf_sat_sensor *sens = wf_to_sat(sr);  	struct wf_sat *sat = sens->sat; @@ -175,62 +177,34 @@ static int wf_sat_get(struct wf_sensor *sr, s32 *value)  	return err;  } -static void wf_sat_release(struct wf_sensor *sr) +static void wf_sat_release(struct kref *ref) +{ +	struct wf_sat *sat = container_of(ref, struct wf_sat, ref); + +	if (sat->nr >= 0) +		sats[sat->nr] = NULL; +	kfree(sat); +} + +static void wf_sat_sensor_release(struct wf_sensor *sr)  {  	struct wf_sat_sensor *sens = wf_to_sat(sr);  	struct wf_sat *sat = sens->sat; -	if (atomic_dec_and_test(&sat->refcnt)) { -		if (sat->nr >= 0) -			sats[sat->nr] = NULL; -		kfree(sat); -	}  	kfree(sens); +	kref_put(&sat->ref, wf_sat_release);  }  static struct wf_sensor_ops wf_sat_ops = { -	.get_value	= wf_sat_get, -	.release	= wf_sat_release, +	.get_value	= wf_sat_sensor_get, +	.release	= wf_sat_sensor_release,  	.owner		= THIS_MODULE,  }; -static struct i2c_driver wf_sat_driver; - -static void wf_sat_create(struct i2c_adapter *adapter, struct device_node *dev) -{ -	struct i2c_board_info info; -	struct i2c_client *client; -	const u32 *reg; -	u8 addr; - -	reg = of_get_property(dev, "reg", NULL); -	if (reg == NULL) -		return; -	addr = *reg; -	DBG(KERN_DEBUG "wf_sat: creating sat at address %x\n", addr); - -	memset(&info, 0, sizeof(struct i2c_board_info)); -	info.addr = (addr >> 1) & 0x7f; -	info.platform_data = dev; -	strlcpy(info.type, "wf_sat", I2C_NAME_SIZE); - -	client = i2c_new_device(adapter, &info); -	if (client == NULL) { -		printk(KERN_ERR "windfarm: failed to attach smu-sat to i2c\n"); -		return; -	} - -	/* -	 * Let i2c-core delete that device on driver removal. -	 * This is safe because i2c-core holds the core_lock mutex for us. -	 */ -	list_add_tail(&client->detected, &wf_sat_driver.clients); -} -  static int wf_sat_probe(struct i2c_client *client,  			const struct i2c_device_id *id)  { -	struct device_node *dev = client->dev.platform_data; +	struct device_node *dev = client->dev.of_node;  	struct wf_sat *sat;  	struct wf_sat_sensor *sens;  	const u32 *reg; @@ -246,9 +220,10 @@ static int wf_sat_probe(struct i2c_client *client,  		return -ENOMEM;  	sat->nr = -1;  	sat->node = of_node_get(dev); -	atomic_set(&sat->refcnt, 0); +	kref_init(&sat->ref);  	mutex_init(&sat->mutex);  	sat->i2c = client; +	INIT_LIST_HEAD(&sat->sensors);  	i2c_set_clientdata(client, sat);  	vsens[0] = vsens[1] = -1; @@ -310,14 +285,15 @@ static int wf_sat_probe(struct i2c_client *client,  		sens->index2 = -1;  		sens->shift = shift;  		sens->sat = sat; -		atomic_inc(&sat->refcnt);  		sens->sens.ops = &wf_sat_ops;  		sens->sens.name = (char *) (sens + 1); -		snprintf(sens->sens.name, 16, "%s-%d", name, cpu); +		snprintf((char *)sens->sens.name, 16, "%s-%d", name, cpu); -		if (wf_register_sensor(&sens->sens)) { -			atomic_dec(&sat->refcnt); +		if (wf_register_sensor(&sens->sens))  			kfree(sens); +		else { +			list_add(&sens->link, &sat->sensors); +			kref_get(&sat->ref);  		}  	} @@ -336,14 +312,15 @@ static int wf_sat_probe(struct i2c_client *client,  		sens->index2 = isens[core];  		sens->shift = 0;  		sens->sat = sat; -		atomic_inc(&sat->refcnt);  		sens->sens.ops = &wf_sat_ops;  		sens->sens.name = (char *) (sens + 1); -		snprintf(sens->sens.name, 16, "cpu-power-%d", cpu); +		snprintf((char *)sens->sens.name, 16, "cpu-power-%d", cpu); -		if (wf_register_sensor(&sens->sens)) { -			atomic_dec(&sat->refcnt); +		if (wf_register_sensor(&sens->sens))  			kfree(sens); +		else { +			list_add(&sens->link, &sat->sensors); +			kref_get(&sat->ref);  		}  	} @@ -353,42 +330,35 @@ static int wf_sat_probe(struct i2c_client *client,  	return 0;  } -static int wf_sat_attach(struct i2c_adapter *adapter) -{ -	struct device_node *busnode, *dev = NULL; -	struct pmac_i2c_bus *bus; - -	bus = pmac_i2c_adapter_to_bus(adapter); -	if (bus == NULL) -		return -ENODEV; -	busnode = pmac_i2c_get_bus_node(bus); - -	while ((dev = of_get_next_child(busnode, dev)) != NULL) -		if (of_device_is_compatible(dev, "smu-sat")) -			wf_sat_create(adapter, dev); -	return 0; -} -  static int wf_sat_remove(struct i2c_client *client)  {  	struct wf_sat *sat = i2c_get_clientdata(client); +	struct wf_sat_sensor *sens; -	/* XXX TODO */ - +	/* release sensors */ +	while(!list_empty(&sat->sensors)) { +		sens = list_first_entry(&sat->sensors, +					struct wf_sat_sensor, link); +		list_del(&sens->link); +		wf_unregister_sensor(&sens->sens); +	}  	sat->i2c = NULL; +	i2c_set_clientdata(client, NULL); +	kref_put(&sat->ref, wf_sat_release); +  	return 0;  }  static const struct i2c_device_id wf_sat_id[] = { -	{ "wf_sat", 0 }, +	{ "MAC,smu-sat", 0 },  	{ }  }; +MODULE_DEVICE_TABLE(i2c, wf_sat_id);  static struct i2c_driver wf_sat_driver = {  	.driver = {  		.name		= "wf_smu_sat",  	}, -	.attach_adapter	= wf_sat_attach,  	.probe		= wf_sat_probe,  	.remove		= wf_sat_remove,  	.id_table	= wf_sat_id, @@ -399,15 +369,13 @@ static int __init sat_sensors_init(void)  	return i2c_add_driver(&wf_sat_driver);  } -#if 0	/* uncomment when module_exit() below is uncommented */  static void __exit sat_sensors_exit(void)  {  	i2c_del_driver(&wf_sat_driver);  } -#endif  module_init(sat_sensors_init); -/*module_exit(sat_sensors_exit); Uncomment when cleanup is implemented */ +module_exit(sat_sensors_exit);  MODULE_AUTHOR("Paul Mackerras <paulus@samba.org>");  MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control"); diff --git a/drivers/ps3/ps3av.c b/drivers/ps3/ps3av.c index a409fa050a1a..93d0a8b7718a 100644 --- a/drivers/ps3/ps3av.c +++ b/drivers/ps3/ps3av.c @@ -338,7 +338,7 @@ int ps3av_do_pkt(u32 cid, u16 send_len, size_t usr_buf_size,  	mutex_unlock(&ps3av->mutex);  	return 0; -      err: +err:  	mutex_unlock(&ps3av->mutex);  	printk(KERN_ERR "%s: failed cid:%x res:%d\n", __func__, cid, res);  	return res; @@ -477,7 +477,6 @@ int ps3av_set_audio_mode(u32 ch, u32 fs, u32 word_bits, u32 format, u32 source)  	return 0;  } -  EXPORT_SYMBOL_GPL(ps3av_set_audio_mode);  static int ps3av_set_videomode(void) @@ -501,7 +500,7 @@ static void ps3av_set_videomode_packet(u32 id)  	video_mode = &video_mode_table[id & PS3AV_MODE_MASK]; -	avb_param.num_of_video_pkt = PS3AV_AVB_NUM_VIDEO;	/* num of head */ +	avb_param.num_of_video_pkt = PS3AV_AVB_NUM_VIDEO; /* num of head */  	avb_param.num_of_audio_pkt = 0;  	avb_param.num_of_av_video_pkt = ps3av->av_hw_conf.num_of_hdmi +  					ps3av->av_hw_conf.num_of_avmulti; @@ -521,7 +520,7 @@ static void ps3av_set_videomode_packet(u32 id)  #ifndef PS3AV_HDMI_YUV  		if (ps3av->av_port[i] == PS3AV_CMD_AVPORT_HDMI_0 ||  		    ps3av->av_port[i] == PS3AV_CMD_AVPORT_HDMI_1) -			av_video_cs = RGB8;	/* use RGB for HDMI */ +			av_video_cs = RGB8; /* use RGB for HDMI */  #endif  		len += ps3av_cmd_set_av_video_cs(&avb_param.buf[len],  						 ps3av->av_port[i], @@ -590,8 +589,8 @@ static void ps3avd(struct work_struct *work)  #define SHIFT_VESA	8  static const struct { -	unsigned mask : 19; -	unsigned id :  4; +	unsigned mask:19; +	unsigned id:4;  } ps3av_preferred_modes[] = {  	{ PS3AV_RESBIT_WUXGA      << SHIFT_VESA, PS3AV_MODE_WUXGA   },  	{ PS3AV_RESBIT_1920x1080P << SHIFT_60,   PS3AV_MODE_1080P60 }, @@ -667,7 +666,8 @@ static enum ps3av_mode_num ps3av_hdmi_get_id(struct ps3av_info_monitor *info)  	return id;  } -static void ps3av_monitor_info_dump(const struct ps3av_pkt_av_get_monitor_info *monitor_info) +static void ps3av_monitor_info_dump( +	const struct ps3av_pkt_av_get_monitor_info *monitor_info)  {  	const struct ps3av_info_monitor *info = &monitor_info->info;  	const struct ps3av_info_audio *audio = info->audio; @@ -717,8 +717,8 @@ static void ps3av_monitor_info_dump(const struct ps3av_pkt_av_get_monitor_info *  	/* audio block */  	for (i = 0; i < info->num_of_audio_block; i++) { -		pr_debug("audio[%d] type: %02x max_ch: %02x fs: %02x sbit: " -			 "%02x\n", +		pr_debug( +			"audio[%d] type: %02x max_ch: %02x fs: %02x sbit: %02x\n",  			 i, audio->type, audio->max_num_of_ch, audio->fs,  			 audio->sbit);  		audio++; @@ -870,21 +870,18 @@ int ps3av_set_video_mode(int id)  	return 0;  } -  EXPORT_SYMBOL_GPL(ps3av_set_video_mode);  int ps3av_get_auto_mode(void)  {  	return ps3av_auto_videomode(&ps3av->av_hw_conf);  } -  EXPORT_SYMBOL_GPL(ps3av_get_auto_mode);  int ps3av_get_mode(void)  {  	return ps3av ? ps3av->ps3av_mode : 0;  } -  EXPORT_SYMBOL_GPL(ps3av_get_mode);  /* get resolution by video_mode */ @@ -902,7 +899,6 @@ int ps3av_video_mode2res(u32 id, u32 *xres, u32 *yres)  	*yres = video_mode_table[id].y;  	return 0;  } -  EXPORT_SYMBOL_GPL(ps3av_video_mode2res);  /* mute */ @@ -911,7 +907,6 @@ int ps3av_video_mute(int mute)  	return ps3av_set_av_video_mute(mute ? PS3AV_CMD_MUTE_ON  					    : PS3AV_CMD_MUTE_OFF);  } -  EXPORT_SYMBOL_GPL(ps3av_video_mute);  /* mute analog output only */ @@ -935,7 +930,6 @@ int ps3av_audio_mute(int mute)  	return ps3av_set_audio_mute(mute ? PS3AV_CMD_MUTE_ON  					 : PS3AV_CMD_MUTE_OFF);  } -  EXPORT_SYMBOL_GPL(ps3av_audio_mute);  static int __devinit ps3av_probe(struct ps3_system_bus_device *dev) | 
