From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/sbus/Makefile | 6 + drivers/sbus/char/Kconfig | 77 +++ drivers/sbus/char/Makefile | 19 + drivers/sbus/char/bbc_envctrl.c | 601 +++++++++++++++++++++ drivers/sbus/char/bbc_i2c.c | 422 +++++++++++++++ drivers/sbus/char/bbc_i2c.h | 88 +++ drivers/sbus/char/display7seg.c | 267 +++++++++ drivers/sbus/char/envctrl.c | 1134 +++++++++++++++++++++++++++++++++++++++ drivers/sbus/char/flash.c | 215 ++++++++ drivers/sbus/char/max1617.h | 28 + drivers/sbus/char/openprom.c | 725 +++++++++++++++++++++++++ drivers/sbus/char/oradax.c | 988 ++++++++++++++++++++++++++++++++++ drivers/sbus/char/uctrl.c | 434 +++++++++++++++ 13 files changed, 5004 insertions(+) create mode 100644 drivers/sbus/Makefile create mode 100644 drivers/sbus/char/Kconfig create mode 100644 drivers/sbus/char/Makefile create mode 100644 drivers/sbus/char/bbc_envctrl.c create mode 100644 drivers/sbus/char/bbc_i2c.c create mode 100644 drivers/sbus/char/bbc_i2c.h create mode 100644 drivers/sbus/char/display7seg.c create mode 100644 drivers/sbus/char/envctrl.c create mode 100644 drivers/sbus/char/flash.c create mode 100644 drivers/sbus/char/max1617.h create mode 100644 drivers/sbus/char/openprom.c create mode 100644 drivers/sbus/char/oradax.c create mode 100644 drivers/sbus/char/uctrl.c (limited to 'drivers/sbus') diff --git a/drivers/sbus/Makefile b/drivers/sbus/Makefile new file mode 100644 index 000000000..5c56763af --- /dev/null +++ b/drivers/sbus/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the linux kernel. +# + +obj-$(CONFIG_SBUSCHAR) += char/ diff --git a/drivers/sbus/char/Kconfig b/drivers/sbus/char/Kconfig new file mode 100644 index 000000000..7c0a308e4 --- /dev/null +++ b/drivers/sbus/char/Kconfig @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "Misc Linux/SPARC drivers" + +config SUN_OPENPROMIO + tristate "/dev/openprom device support" + help + This driver provides user programs with an interface to the SPARC + PROM device tree. The driver implements a SunOS-compatible + interface and a NetBSD-compatible interface. + + To compile this driver as a module, choose M here: the + module will be called openprom. + + If unsure, say Y. + +config OBP_FLASH + tristate "OBP Flash Device support" + depends on SPARC64 + help + The OpenBoot PROM on Ultra systems is flashable. If you want to be + able to upgrade the OBP firmware, say Y here. + +config TADPOLE_TS102_UCTRL + tristate "Tadpole TS102 Microcontroller support" + help + Say Y here to directly support the TS102 Microcontroller interface + on the Tadpole Sparcbook 3. This device handles power-management + events, and can also notice the attachment/detachment of external + monitors and mice. + +config BBC_I2C + tristate "UltraSPARC-III bootbus i2c controller driver" + depends on PCI && SPARC64 + help + The BBC devices on the UltraSPARC III have two I2C controllers. The + first I2C controller connects mainly to configuration PROMs (NVRAM, + CPU configuration, DIMM types, etc.). The second I2C controller + connects to environmental control devices such as fans and + temperature sensors. The second controller also connects to the + smartcard reader, if present. Say Y to enable support for these. + +config ENVCTRL + tristate "SUNW, envctrl support" + depends on PCI && SPARC64 + help + Kernel support for temperature and fan monitoring on Sun SME + machines. + + To compile this driver as a module, choose M here: the + module will be called envctrl. + +config DISPLAY7SEG + tristate "7-Segment Display support" + depends on PCI && SPARC64 + help + This is the driver for the 7-segment display and LED present on + Sun Microsystems CompactPCI models CP1400 and CP1500. + + To compile this driver as a module, choose M here: the + module will be called display7seg. + + If you do not have a CompactPCI model CP1400 or CP1500, or + another UltraSPARC-IIi-cEngine boardset with a 7-segment display, + you should say N to this option. + +config ORACLE_DAX + tristate "Oracle Data Analytics Accelerator" + depends on SPARC64 + default m + help + Driver for Oracle Data Analytics Accelerator, which is + a coprocessor that performs database operations in hardware. + It is available on M7 and M8 based systems only. + +endmenu + diff --git a/drivers/sbus/char/Makefile b/drivers/sbus/char/Makefile new file mode 100644 index 000000000..44347c918 --- /dev/null +++ b/drivers/sbus/char/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the kernel miscellaneous SPARC device drivers. +# +# Dave Redman Frame Buffer tuning support. +# +# 7 October 2000, Bartlomiej Zolnierkiewicz +# Rewritten to use lists instead of if-statements. +# + +bbc-objs := bbc_i2c.o bbc_envctrl.o + +obj-$(CONFIG_ENVCTRL) += envctrl.o +obj-$(CONFIG_DISPLAY7SEG) += display7seg.o +obj-$(CONFIG_OBP_FLASH) += flash.o +obj-$(CONFIG_SUN_OPENPROMIO) += openprom.o +obj-$(CONFIG_TADPOLE_TS102_UCTRL) += uctrl.o +obj-$(CONFIG_BBC_I2C) += bbc.o +obj-$(CONFIG_ORACLE_DAX) += oradax.o diff --git a/drivers/sbus/char/bbc_envctrl.c b/drivers/sbus/char/bbc_envctrl.c new file mode 100644 index 000000000..d3083a547 --- /dev/null +++ b/drivers/sbus/char/bbc_envctrl.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0 +/* bbc_envctrl.c: UltraSPARC-III environment control driver. + * + * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bbc_i2c.h" +#include "max1617.h" + +#undef ENVCTRL_TRACE + +/* WARNING: Making changes to this driver is very dangerous. + * If you misprogram the sensor chips they can + * cut the power on you instantly. + */ + +/* Two temperature sensors exist in the SunBLADE-1000 enclosure. + * Both are implemented using max1617 i2c devices. Each max1617 + * monitors 2 temperatures, one for one of the cpu dies and the other + * for the ambient temperature. + * + * The max1617 is capable of being programmed with power-off + * temperature values, one low limit and one high limit. These + * can be controlled independently for the cpu or ambient temperature. + * If a limit is violated, the power is simply shut off. The frequency + * with which the max1617 does temperature sampling can be controlled + * as well. + * + * Three fans exist inside the machine, all three are controlled with + * an i2c digital to analog converter. There is a fan directed at the + * two processor slots, another for the rest of the enclosure, and the + * third is for the power supply. The first two fans may be speed + * controlled by changing the voltage fed to them. The third fan may + * only be completely off or on. The third fan is meant to only be + * disabled/enabled when entering/exiting the lowest power-saving + * mode of the machine. + * + * An environmental control kernel thread periodically monitors all + * temperature sensors. Based upon the samples it will adjust the + * fan speeds to try and keep the system within a certain temperature + * range (the goal being to make the fans as quiet as possible without + * allowing the system to get too hot). + * + * If the temperature begins to rise/fall outside of the acceptable + * operating range, a periodic warning will be sent to the kernel log. + * The fans will be put on full blast to attempt to deal with this + * situation. After exceeding the acceptable operating range by a + * certain threshold, the kernel thread will shut down the system. + * Here, the thread is attempting to shut the machine down cleanly + * before the hardware based power-off event is triggered. + */ + +/* These settings are in Celsius. We use these defaults only + * if we cannot interrogate the cpu-fru SEEPROM. + */ +struct temp_limits { + s8 high_pwroff, high_shutdown, high_warn; + s8 low_warn, low_shutdown, low_pwroff; +}; + +static struct temp_limits cpu_temp_limits[2] = { + { 100, 85, 80, 5, -5, -10 }, + { 100, 85, 80, 5, -5, -10 }, +}; + +static struct temp_limits amb_temp_limits[2] = { + { 65, 55, 40, 5, -5, -10 }, + { 65, 55, 40, 5, -5, -10 }, +}; + +static LIST_HEAD(all_temps); +static LIST_HEAD(all_fans); + +#define CPU_FAN_REG 0xf0 +#define SYS_FAN_REG 0xf2 +#define PSUPPLY_FAN_REG 0xf4 + +#define FAN_SPEED_MIN 0x0c +#define FAN_SPEED_MAX 0x3f + +#define PSUPPLY_FAN_ON 0x1f +#define PSUPPLY_FAN_OFF 0x00 + +static void set_fan_speeds(struct bbc_fan_control *fp) +{ + /* Put temperatures into range so we don't mis-program + * the hardware. + */ + if (fp->cpu_fan_speed < FAN_SPEED_MIN) + fp->cpu_fan_speed = FAN_SPEED_MIN; + if (fp->cpu_fan_speed > FAN_SPEED_MAX) + fp->cpu_fan_speed = FAN_SPEED_MAX; + if (fp->system_fan_speed < FAN_SPEED_MIN) + fp->system_fan_speed = FAN_SPEED_MIN; + if (fp->system_fan_speed > FAN_SPEED_MAX) + fp->system_fan_speed = FAN_SPEED_MAX; +#ifdef ENVCTRL_TRACE + printk("fan%d: Changed fan speed to cpu(%02x) sys(%02x)\n", + fp->index, + fp->cpu_fan_speed, fp->system_fan_speed); +#endif + + bbc_i2c_writeb(fp->client, fp->cpu_fan_speed, CPU_FAN_REG); + bbc_i2c_writeb(fp->client, fp->system_fan_speed, SYS_FAN_REG); + bbc_i2c_writeb(fp->client, + (fp->psupply_fan_on ? + PSUPPLY_FAN_ON : PSUPPLY_FAN_OFF), + PSUPPLY_FAN_REG); +} + +static void get_current_temps(struct bbc_cpu_temperature *tp) +{ + tp->prev_amb_temp = tp->curr_amb_temp; + bbc_i2c_readb(tp->client, + (unsigned char *) &tp->curr_amb_temp, + MAX1617_AMB_TEMP); + tp->prev_cpu_temp = tp->curr_cpu_temp; + bbc_i2c_readb(tp->client, + (unsigned char *) &tp->curr_cpu_temp, + MAX1617_CPU_TEMP); +#ifdef ENVCTRL_TRACE + printk("temp%d: cpu(%d C) amb(%d C)\n", + tp->index, + (int) tp->curr_cpu_temp, (int) tp->curr_amb_temp); +#endif +} + + +static void do_envctrl_shutdown(struct bbc_cpu_temperature *tp) +{ + static int shutting_down = 0; + char *type = "???"; + s8 val = -1; + + if (shutting_down != 0) + return; + + if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown || + tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) { + type = "ambient"; + val = tp->curr_amb_temp; + } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown || + tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) { + type = "CPU"; + val = tp->curr_cpu_temp; + } + + printk(KERN_CRIT "temp%d: Outside of safe %s " + "operating temperature, %d C.\n", + tp->index, type, val); + + printk(KERN_CRIT "kenvctrld: Shutting down the system now.\n"); + + shutting_down = 1; + orderly_poweroff(true); +} + +#define WARN_INTERVAL (30 * HZ) + +static void analyze_ambient_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick) +{ + int ret = 0; + + if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) { + if (tp->curr_amb_temp >= + amb_temp_limits[tp->index].high_warn) { + printk(KERN_WARNING "temp%d: " + "Above safe ambient operating temperature, %d C.\n", + tp->index, (int) tp->curr_amb_temp); + ret = 1; + } else if (tp->curr_amb_temp < + amb_temp_limits[tp->index].low_warn) { + printk(KERN_WARNING "temp%d: " + "Below safe ambient operating temperature, %d C.\n", + tp->index, (int) tp->curr_amb_temp); + ret = 1; + } + if (ret) + *last_warn = jiffies; + } else if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_warn || + tp->curr_amb_temp < amb_temp_limits[tp->index].low_warn) + ret = 1; + + /* Now check the shutdown limits. */ + if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown || + tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) { + do_envctrl_shutdown(tp); + ret = 1; + } + + if (ret) { + tp->fan_todo[FAN_AMBIENT] = FAN_FULLBLAST; + } else if ((tick & (8 - 1)) == 0) { + s8 amb_goal_hi = amb_temp_limits[tp->index].high_warn - 10; + s8 amb_goal_lo; + + amb_goal_lo = amb_goal_hi - 3; + + /* We do not try to avoid 'too cold' events. Basically we + * only try to deal with over-heating and fan noise reduction. + */ + if (tp->avg_amb_temp < amb_goal_hi) { + if (tp->avg_amb_temp >= amb_goal_lo) + tp->fan_todo[FAN_AMBIENT] = FAN_SAME; + else + tp->fan_todo[FAN_AMBIENT] = FAN_SLOWER; + } else { + tp->fan_todo[FAN_AMBIENT] = FAN_FASTER; + } + } else { + tp->fan_todo[FAN_AMBIENT] = FAN_SAME; + } +} + +static void analyze_cpu_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick) +{ + int ret = 0; + + if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) { + if (tp->curr_cpu_temp >= + cpu_temp_limits[tp->index].high_warn) { + printk(KERN_WARNING "temp%d: " + "Above safe CPU operating temperature, %d C.\n", + tp->index, (int) tp->curr_cpu_temp); + ret = 1; + } else if (tp->curr_cpu_temp < + cpu_temp_limits[tp->index].low_warn) { + printk(KERN_WARNING "temp%d: " + "Below safe CPU operating temperature, %d C.\n", + tp->index, (int) tp->curr_cpu_temp); + ret = 1; + } + if (ret) + *last_warn = jiffies; + } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_warn || + tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_warn) + ret = 1; + + /* Now check the shutdown limits. */ + if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown || + tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) { + do_envctrl_shutdown(tp); + ret = 1; + } + + if (ret) { + tp->fan_todo[FAN_CPU] = FAN_FULLBLAST; + } else if ((tick & (8 - 1)) == 0) { + s8 cpu_goal_hi = cpu_temp_limits[tp->index].high_warn - 10; + s8 cpu_goal_lo; + + cpu_goal_lo = cpu_goal_hi - 3; + + /* We do not try to avoid 'too cold' events. Basically we + * only try to deal with over-heating and fan noise reduction. + */ + if (tp->avg_cpu_temp < cpu_goal_hi) { + if (tp->avg_cpu_temp >= cpu_goal_lo) + tp->fan_todo[FAN_CPU] = FAN_SAME; + else + tp->fan_todo[FAN_CPU] = FAN_SLOWER; + } else { + tp->fan_todo[FAN_CPU] = FAN_FASTER; + } + } else { + tp->fan_todo[FAN_CPU] = FAN_SAME; + } +} + +static void analyze_temps(struct bbc_cpu_temperature *tp, unsigned long *last_warn) +{ + tp->avg_amb_temp = (s8)((int)((int)tp->avg_amb_temp + (int)tp->curr_amb_temp) / 2); + tp->avg_cpu_temp = (s8)((int)((int)tp->avg_cpu_temp + (int)tp->curr_cpu_temp) / 2); + + analyze_ambient_temp(tp, last_warn, tp->sample_tick); + analyze_cpu_temp(tp, last_warn, tp->sample_tick); + + tp->sample_tick++; +} + +static enum fan_action prioritize_fan_action(int which_fan) +{ + struct bbc_cpu_temperature *tp; + enum fan_action decision = FAN_STATE_MAX; + + /* Basically, prioritize what the temperature sensors + * recommend we do, and perform that action on all the + * fans. + */ + list_for_each_entry(tp, &all_temps, glob_list) { + if (tp->fan_todo[which_fan] == FAN_FULLBLAST) { + decision = FAN_FULLBLAST; + break; + } + if (tp->fan_todo[which_fan] == FAN_SAME && + decision != FAN_FASTER) + decision = FAN_SAME; + else if (tp->fan_todo[which_fan] == FAN_FASTER) + decision = FAN_FASTER; + else if (decision != FAN_FASTER && + decision != FAN_SAME && + tp->fan_todo[which_fan] == FAN_SLOWER) + decision = FAN_SLOWER; + } + if (decision == FAN_STATE_MAX) + decision = FAN_SAME; + + return decision; +} + +static int maybe_new_ambient_fan_speed(struct bbc_fan_control *fp) +{ + enum fan_action decision = prioritize_fan_action(FAN_AMBIENT); + int ret; + + if (decision == FAN_SAME) + return 0; + + ret = 1; + if (decision == FAN_FULLBLAST) { + if (fp->system_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else + fp->system_fan_speed = FAN_SPEED_MAX; + } else { + if (decision == FAN_FASTER) { + if (fp->system_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else + fp->system_fan_speed += 2; + } else { + int orig_speed = fp->system_fan_speed; + + if (orig_speed <= FAN_SPEED_MIN || + orig_speed <= (fp->cpu_fan_speed - 3)) + ret = 0; + else + fp->system_fan_speed -= 1; + } + } + + return ret; +} + +static int maybe_new_cpu_fan_speed(struct bbc_fan_control *fp) +{ + enum fan_action decision = prioritize_fan_action(FAN_CPU); + int ret; + + if (decision == FAN_SAME) + return 0; + + ret = 1; + if (decision == FAN_FULLBLAST) { + if (fp->cpu_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else + fp->cpu_fan_speed = FAN_SPEED_MAX; + } else { + if (decision == FAN_FASTER) { + if (fp->cpu_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else { + fp->cpu_fan_speed += 2; + if (fp->system_fan_speed < + (fp->cpu_fan_speed - 3)) + fp->system_fan_speed = + fp->cpu_fan_speed - 3; + } + } else { + if (fp->cpu_fan_speed <= FAN_SPEED_MIN) + ret = 0; + else + fp->cpu_fan_speed -= 1; + } + } + + return ret; +} + +static void maybe_new_fan_speeds(struct bbc_fan_control *fp) +{ + int new; + + new = maybe_new_ambient_fan_speed(fp); + new |= maybe_new_cpu_fan_speed(fp); + + if (new) + set_fan_speeds(fp); +} + +static void fans_full_blast(void) +{ + struct bbc_fan_control *fp; + + /* Since we will not be monitoring things anymore, put + * the fans on full blast. + */ + list_for_each_entry(fp, &all_fans, glob_list) { + fp->cpu_fan_speed = FAN_SPEED_MAX; + fp->system_fan_speed = FAN_SPEED_MAX; + fp->psupply_fan_on = 1; + set_fan_speeds(fp); + } +} + +#define POLL_INTERVAL (5 * 1000) +static unsigned long last_warning_jiffies; +static struct task_struct *kenvctrld_task; + +static int kenvctrld(void *__unused) +{ + printk(KERN_INFO "bbc_envctrl: kenvctrld starting...\n"); + last_warning_jiffies = jiffies - WARN_INTERVAL; + for (;;) { + struct bbc_cpu_temperature *tp; + struct bbc_fan_control *fp; + + msleep_interruptible(POLL_INTERVAL); + if (kthread_should_stop()) + break; + + list_for_each_entry(tp, &all_temps, glob_list) { + get_current_temps(tp); + analyze_temps(tp, &last_warning_jiffies); + } + list_for_each_entry(fp, &all_fans, glob_list) + maybe_new_fan_speeds(fp); + } + printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n"); + + fans_full_blast(); + + return 0; +} + +static void attach_one_temp(struct bbc_i2c_bus *bp, struct platform_device *op, + int temp_idx) +{ + struct bbc_cpu_temperature *tp; + + tp = kzalloc_obj(*tp); + if (!tp) + return; + + INIT_LIST_HEAD(&tp->bp_list); + INIT_LIST_HEAD(&tp->glob_list); + + tp->client = bbc_i2c_attach(bp, op); + if (!tp->client) { + kfree(tp); + return; + } + + + tp->index = temp_idx; + + list_add(&tp->glob_list, &all_temps); + list_add(&tp->bp_list, &bp->temps); + + /* Tell it to convert once every 5 seconds, clear all cfg + * bits. + */ + bbc_i2c_writeb(tp->client, 0x00, MAX1617_WR_CFG_BYTE); + bbc_i2c_writeb(tp->client, 0x02, MAX1617_WR_CVRATE_BYTE); + + /* Program the hard temperature limits into the chip. */ + bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].high_pwroff, + MAX1617_WR_AMB_HIGHLIM); + bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].low_pwroff, + MAX1617_WR_AMB_LOWLIM); + bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].high_pwroff, + MAX1617_WR_CPU_HIGHLIM); + bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].low_pwroff, + MAX1617_WR_CPU_LOWLIM); + + get_current_temps(tp); + tp->prev_cpu_temp = tp->avg_cpu_temp = tp->curr_cpu_temp; + tp->prev_amb_temp = tp->avg_amb_temp = tp->curr_amb_temp; + + tp->fan_todo[FAN_AMBIENT] = FAN_SAME; + tp->fan_todo[FAN_CPU] = FAN_SAME; +} + +static void attach_one_fan(struct bbc_i2c_bus *bp, struct platform_device *op, + int fan_idx) +{ + struct bbc_fan_control *fp; + + fp = kzalloc_obj(*fp); + if (!fp) + return; + + INIT_LIST_HEAD(&fp->bp_list); + INIT_LIST_HEAD(&fp->glob_list); + + fp->client = bbc_i2c_attach(bp, op); + if (!fp->client) { + kfree(fp); + return; + } + + fp->index = fan_idx; + + list_add(&fp->glob_list, &all_fans); + list_add(&fp->bp_list, &bp->fans); + + /* The i2c device controlling the fans is write-only. + * So the only way to keep track of the current power + * level fed to the fans is via software. Choose half + * power for cpu/system and 'on' fo the powersupply fan + * and set it now. + */ + fp->psupply_fan_on = 1; + fp->cpu_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2; + fp->cpu_fan_speed += FAN_SPEED_MIN; + fp->system_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2; + fp->system_fan_speed += FAN_SPEED_MIN; + + set_fan_speeds(fp); +} + +static void destroy_one_temp(struct bbc_cpu_temperature *tp) +{ + bbc_i2c_detach(tp->client); + kfree(tp); +} + +static void destroy_all_temps(struct bbc_i2c_bus *bp) +{ + struct bbc_cpu_temperature *tp, *tpos; + + list_for_each_entry_safe(tp, tpos, &bp->temps, bp_list) { + list_del(&tp->bp_list); + list_del(&tp->glob_list); + destroy_one_temp(tp); + } +} + +static void destroy_one_fan(struct bbc_fan_control *fp) +{ + bbc_i2c_detach(fp->client); + kfree(fp); +} + +static void destroy_all_fans(struct bbc_i2c_bus *bp) +{ + struct bbc_fan_control *fp, *fpos; + + list_for_each_entry_safe(fp, fpos, &bp->fans, bp_list) { + list_del(&fp->bp_list); + list_del(&fp->glob_list); + destroy_one_fan(fp); + } +} + +int bbc_envctrl_init(struct bbc_i2c_bus *bp) +{ + struct platform_device *op; + int temp_index = 0; + int fan_index = 0; + int devidx = 0; + + while ((op = bbc_i2c_getdev(bp, devidx++)) != NULL) { + if (of_node_name_eq(op->dev.of_node, "temperature")) + attach_one_temp(bp, op, temp_index++); + if (of_node_name_eq(op->dev.of_node, "fan-control")) + attach_one_fan(bp, op, fan_index++); + } + if (temp_index != 0 && fan_index != 0) { + kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld"); + if (IS_ERR(kenvctrld_task)) { + int err = PTR_ERR(kenvctrld_task); + + kenvctrld_task = NULL; + destroy_all_temps(bp); + destroy_all_fans(bp); + return err; + } + } + + return 0; +} + +void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp) +{ + if (kenvctrld_task) + kthread_stop(kenvctrld_task); + + destroy_all_temps(bp); + destroy_all_fans(bp); +} diff --git a/drivers/sbus/char/bbc_i2c.c b/drivers/sbus/char/bbc_i2c.c new file mode 100644 index 000000000..1801ba563 --- /dev/null +++ b/drivers/sbus/char/bbc_i2c.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III + * platforms. + * + * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bbc_i2c.h" + +/* Convert this driver to use i2c bus layer someday... */ +#define I2C_PCF_PIN 0x80 +#define I2C_PCF_ESO 0x40 +#define I2C_PCF_ES1 0x20 +#define I2C_PCF_ES2 0x10 +#define I2C_PCF_ENI 0x08 +#define I2C_PCF_STA 0x04 +#define I2C_PCF_STO 0x02 +#define I2C_PCF_ACK 0x01 + +#define I2C_PCF_START (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ENI | I2C_PCF_STA | I2C_PCF_ACK) +#define I2C_PCF_STOP (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK) +#define I2C_PCF_REPSTART ( I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK) +#define I2C_PCF_IDLE (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ACK) + +#define I2C_PCF_INI 0x40 /* 1 if not initialized */ +#define I2C_PCF_STS 0x20 +#define I2C_PCF_BER 0x10 +#define I2C_PCF_AD0 0x08 +#define I2C_PCF_LRB 0x08 +#define I2C_PCF_AAS 0x04 +#define I2C_PCF_LAB 0x02 +#define I2C_PCF_BB 0x01 + +/* The BBC devices have two I2C controllers. The first I2C controller + * connects mainly to configuration proms (NVRAM, cpu configuration, + * dimm types, etc.). Whereas the second I2C controller connects to + * environmental control devices such as fans and temperature sensors. + * The second controller also connects to the smartcard reader, if present. + */ + +static void set_device_claimage(struct bbc_i2c_bus *bp, struct platform_device *op, int val) +{ + int i; + + for (i = 0; i < NUM_CHILDREN; i++) { + if (bp->devs[i].device == op) { + bp->devs[i].client_claimed = val; + return; + } + } +} + +#define claim_device(BP,ECHILD) set_device_claimage(BP,ECHILD,1) +#define release_device(BP,ECHILD) set_device_claimage(BP,ECHILD,0) + +struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *bp, int index) +{ + struct platform_device *op = NULL; + int curidx = 0, i; + + for (i = 0; i < NUM_CHILDREN; i++) { + if (!(op = bp->devs[i].device)) + break; + if (curidx == index) + goto out; + op = NULL; + curidx++; + } + +out: + if (curidx == index) + return op; + return NULL; +} + +struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *op) +{ + struct bbc_i2c_client *client; + const u32 *reg; + + client = kzalloc_obj(*client); + if (!client) + return NULL; + client->bp = bp; + client->op = op; + + reg = of_get_property(op->dev.of_node, "reg", NULL); + if (!reg) { + kfree(client); + return NULL; + } + + client->bus = reg[0]; + client->address = reg[1]; + + claim_device(bp, op); + + return client; +} + +void bbc_i2c_detach(struct bbc_i2c_client *client) +{ + struct bbc_i2c_bus *bp = client->bp; + struct platform_device *op = client->op; + + release_device(bp, op); + kfree(client); +} + +static int wait_for_pin(struct bbc_i2c_bus *bp, u8 *status) +{ + DECLARE_WAITQUEUE(wait, current); + int limit = 32; + int ret = 1; + + bp->waiting = 1; + add_wait_queue(&bp->wq, &wait); + while (limit-- > 0) { + long val; + + val = wait_event_interruptible_timeout( + bp->wq, + (((*status = readb(bp->i2c_control_regs + 0)) + & I2C_PCF_PIN) == 0), + msecs_to_jiffies(250)); + if (val > 0) { + ret = 0; + break; + } + } + remove_wait_queue(&bp->wq, &wait); + bp->waiting = 0; + + return ret; +} + +int bbc_i2c_writeb(struct bbc_i2c_client *client, unsigned char val, int off) +{ + struct bbc_i2c_bus *bp = client->bp; + int address = client->address; + u8 status; + int ret = -1; + + if (bp->i2c_bussel_reg != NULL) + writeb(client->bus, bp->i2c_bussel_reg); + + writeb(address, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); + if (wait_for_pin(bp, &status)) + goto out; + + writeb(off, bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status) || + (status & I2C_PCF_LRB) != 0) + goto out; + + writeb(val, bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status)) + goto out; + + ret = 0; + +out: + writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); + return ret; +} + +int bbc_i2c_readb(struct bbc_i2c_client *client, unsigned char *byte, int off) +{ + struct bbc_i2c_bus *bp = client->bp; + unsigned char address = client->address, status; + int ret = -1; + + if (bp->i2c_bussel_reg != NULL) + writeb(client->bus, bp->i2c_bussel_reg); + + writeb(address, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); + if (wait_for_pin(bp, &status)) + goto out; + + writeb(off, bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status) || + (status & I2C_PCF_LRB) != 0) + goto out; + + writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); + + address |= 0x1; /* READ */ + + writeb(address, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); + if (wait_for_pin(bp, &status)) + goto out; + + /* Set PIN back to one so the device sends the first + * byte. + */ + (void) readb(bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status)) + goto out; + + writeb(I2C_PCF_ESO | I2C_PCF_ENI, bp->i2c_control_regs + 0x0); + *byte = readb(bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status)) + goto out; + + ret = 0; + +out: + writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); + (void) readb(bp->i2c_control_regs + 0x1); + + return ret; +} + +int bbc_i2c_write_buf(struct bbc_i2c_client *client, + char *buf, int len, int off) +{ + int ret = 0; + + while (len > 0) { + ret = bbc_i2c_writeb(client, *buf, off); + if (ret < 0) + break; + len--; + buf++; + off++; + } + return ret; +} + +int bbc_i2c_read_buf(struct bbc_i2c_client *client, + char *buf, int len, int off) +{ + int ret = 0; + + while (len > 0) { + ret = bbc_i2c_readb(client, buf, off); + if (ret < 0) + break; + len--; + buf++; + off++; + } + + return ret; +} + +EXPORT_SYMBOL(bbc_i2c_getdev); +EXPORT_SYMBOL(bbc_i2c_attach); +EXPORT_SYMBOL(bbc_i2c_detach); +EXPORT_SYMBOL(bbc_i2c_writeb); +EXPORT_SYMBOL(bbc_i2c_readb); +EXPORT_SYMBOL(bbc_i2c_write_buf); +EXPORT_SYMBOL(bbc_i2c_read_buf); + +static irqreturn_t bbc_i2c_interrupt(int irq, void *dev_id) +{ + struct bbc_i2c_bus *bp = dev_id; + + /* PIN going from set to clear is the only event which + * makes the i2c assert an interrupt. + */ + if (bp->waiting && + !(readb(bp->i2c_control_regs + 0x0) & I2C_PCF_PIN)) + wake_up_interruptible(&bp->wq); + + return IRQ_HANDLED; +} + +static void reset_one_i2c(struct bbc_i2c_bus *bp) +{ + writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); + writeb(bp->own, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); + writeb(bp->clock, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_IDLE, bp->i2c_control_regs + 0x0); +} + +static struct bbc_i2c_bus * attach_one_i2c(struct platform_device *op, int index) +{ + struct bbc_i2c_bus *bp; + struct device_node *dp; + int entry; + + bp = kzalloc_obj(*bp); + if (!bp) + return NULL; + + INIT_LIST_HEAD(&bp->temps); + INIT_LIST_HEAD(&bp->fans); + + bp->i2c_control_regs = of_ioremap(&op->resource[0], 0, 0x2, "bbc_i2c_regs"); + if (!bp->i2c_control_regs) + goto fail; + + if (op->num_resources == 2) { + bp->i2c_bussel_reg = of_ioremap(&op->resource[1], 0, 0x1, "bbc_i2c_bussel"); + if (!bp->i2c_bussel_reg) + goto fail; + } + + bp->waiting = 0; + init_waitqueue_head(&bp->wq); + if (request_irq(op->archdata.irqs[0], bbc_i2c_interrupt, + IRQF_SHARED, "bbc_i2c", bp)) + goto fail; + + bp->index = index; + bp->op = op; + + spin_lock_init(&bp->lock); + + entry = 0; + for (dp = op->dev.of_node->child; + dp && entry < 8; + dp = dp->sibling, entry++) { + struct platform_device *child_op; + + child_op = of_find_device_by_node(dp); + bp->devs[entry].device = child_op; + bp->devs[entry].client_claimed = 0; + } + + writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); + bp->own = readb(bp->i2c_control_regs + 0x01); + writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); + bp->clock = readb(bp->i2c_control_regs + 0x01); + + printk(KERN_INFO "i2c-%d: Regs at %p, %d devices, own %02x, clock %02x.\n", + bp->index, bp->i2c_control_regs, entry, bp->own, bp->clock); + + reset_one_i2c(bp); + + return bp; + +fail: + if (bp->i2c_bussel_reg) + of_iounmap(&op->resource[1], bp->i2c_bussel_reg, 1); + if (bp->i2c_control_regs) + of_iounmap(&op->resource[0], bp->i2c_control_regs, 2); + kfree(bp); + return NULL; +} + +static int bbc_i2c_probe(struct platform_device *op) +{ + struct bbc_i2c_bus *bp; + int err, index = 0; + + bp = attach_one_i2c(op, index); + if (!bp) + return -EINVAL; + + err = bbc_envctrl_init(bp); + if (err) { + free_irq(op->archdata.irqs[0], bp); + if (bp->i2c_bussel_reg) + of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1); + if (bp->i2c_control_regs) + of_iounmap(&op->resource[1], bp->i2c_control_regs, 2); + kfree(bp); + } else { + dev_set_drvdata(&op->dev, bp); + } + + return err; +} + +static void bbc_i2c_remove(struct platform_device *op) +{ + struct bbc_i2c_bus *bp = dev_get_drvdata(&op->dev); + + bbc_envctrl_cleanup(bp); + + free_irq(op->archdata.irqs[0], bp); + + if (bp->i2c_bussel_reg) + of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1); + if (bp->i2c_control_regs) + of_iounmap(&op->resource[1], bp->i2c_control_regs, 2); + + kfree(bp); +} + +static const struct of_device_id bbc_i2c_match[] = { + { + .name = "i2c", + .compatible = "SUNW,bbc-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bbc_i2c_match); + +static struct platform_driver bbc_i2c_driver = { + .driver = { + .name = "bbc_i2c", + .of_match_table = bbc_i2c_match, + }, + .probe = bbc_i2c_probe, + .remove = bbc_i2c_remove, +}; + +module_platform_driver(bbc_i2c_driver); + +MODULE_DESCRIPTION("UltraSPARC-III bootbus i2c controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sbus/char/bbc_i2c.h b/drivers/sbus/char/bbc_i2c.h new file mode 100644 index 000000000..6c7488367 --- /dev/null +++ b/drivers/sbus/char/bbc_i2c.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BBC_I2C_H +#define _BBC_I2C_H + +#include +#include + +struct bbc_i2c_client { + struct bbc_i2c_bus *bp; + struct platform_device *op; + int bus; + int address; +}; + +enum fan_action { FAN_SLOWER, FAN_SAME, FAN_FASTER, FAN_FULLBLAST, FAN_STATE_MAX }; + +struct bbc_cpu_temperature { + struct list_head bp_list; + struct list_head glob_list; + + struct bbc_i2c_client *client; + int index; + + /* Current readings, and history. */ + s8 curr_cpu_temp; + s8 curr_amb_temp; + s8 prev_cpu_temp; + s8 prev_amb_temp; + s8 avg_cpu_temp; + s8 avg_amb_temp; + + int sample_tick; + + enum fan_action fan_todo[2]; +#define FAN_AMBIENT 0 +#define FAN_CPU 1 +}; + +struct bbc_fan_control { + struct list_head bp_list; + struct list_head glob_list; + + struct bbc_i2c_client *client; + int index; + + int psupply_fan_on; + int cpu_fan_speed; + int system_fan_speed; +}; + +#define NUM_CHILDREN 8 + +struct bbc_i2c_bus { + struct bbc_i2c_bus *next; + int index; + spinlock_t lock; + void __iomem *i2c_bussel_reg; + void __iomem *i2c_control_regs; + unsigned char own, clock; + + wait_queue_head_t wq; + volatile int waiting; + + struct list_head temps; + struct list_head fans; + + struct platform_device *op; + struct { + struct platform_device *device; + int client_claimed; + } devs[NUM_CHILDREN]; +}; + +/* Probing and attachment. */ +extern struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *, int); +extern struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *); +extern void bbc_i2c_detach(struct bbc_i2c_client *); + +/* Register read/write. NOTE: Blocking! */ +extern int bbc_i2c_writeb(struct bbc_i2c_client *, unsigned char val, int off); +extern int bbc_i2c_readb(struct bbc_i2c_client *, unsigned char *byte, int off); +extern int bbc_i2c_write_buf(struct bbc_i2c_client *, char *buf, int len, int off); +extern int bbc_i2c_read_buf(struct bbc_i2c_client *, char *buf, int len, int off); + +extern int bbc_envctrl_init(struct bbc_i2c_bus *bp); +extern void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp); + +#endif /* _BBC_I2C_H */ diff --git a/drivers/sbus/char/display7seg.c b/drivers/sbus/char/display7seg.c new file mode 100644 index 000000000..e94222b35 --- /dev/null +++ b/drivers/sbus/char/display7seg.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* display7seg.c - Driver implementation for the 7-segment display + * present on Sun Microsystems CP1400 and CP1500 + * + * Copyright (c) 2000 Eric Brower (ebrower@usa.net) + */ + +#include +#include +#include +#include +#include +#include +#include +#include /* request_region */ +#include +#include +#include +#include +#include +#include /* put_/get_user */ +#include + +#include + +#define DRIVER_NAME "d7s" +#define PFX DRIVER_NAME ": " + +static DEFINE_MUTEX(d7s_mutex); +static int sol_compat = 0; /* Solaris compatibility mode */ + +/* Solaris compatibility flag - + * The Solaris implementation omits support for several + * documented driver features (ref Sun doc 806-0180-03). + * By default, this module supports the documented driver + * abilities, rather than the Solaris implementation: + * + * 1) Device ALWAYS reverts to OBP-specified FLIPPED mode + * upon closure of device or module unload. + * 2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of + * FLIP bit + * + * If you wish the device to operate as under Solaris, + * omitting above features, set this parameter to non-zero. + */ +module_param(sol_compat, int, 0); +MODULE_PARM_DESC(sol_compat, + "Disables documented functionality omitted from Solaris driver"); + +MODULE_AUTHOR("Eric Brower "); +MODULE_DESCRIPTION("7-Segment Display driver for Sun Microsystems CP1400/1500"); +MODULE_LICENSE("GPL"); + +struct d7s { + void __iomem *regs; + bool flipped; +}; +struct d7s *d7s_device; + +/* + * Register block address- see header for details + * ----------------------------------------- + * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 | + * ----------------------------------------- + * + * DP - Toggles decimal point on/off + * ALARM - Toggles "Alarm" LED green/red + * FLIP - Inverts display for upside-down mounted board + * bits 0-4 - 7-segment display contents + */ +static atomic_t d7s_users = ATOMIC_INIT(0); + +static int d7s_open(struct inode *inode, struct file *f) +{ + if (D7S_MINOR != iminor(inode)) + return -ENODEV; + atomic_inc(&d7s_users); + return 0; +} + +static int d7s_release(struct inode *inode, struct file *f) +{ + /* Reset flipped state to OBP default only if + * no other users have the device open and we + * are not operating in solaris-compat mode + */ + if (atomic_dec_and_test(&d7s_users) && !sol_compat) { + struct d7s *p = d7s_device; + u8 regval = 0; + + regval = readb(p->regs); + if (p->flipped) + regval |= D7S_FLIP; + else + regval &= ~D7S_FLIP; + writeb(regval, p->regs); + } + + return 0; +} + +static long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct d7s *p = d7s_device; + u8 regs = readb(p->regs); + int error = 0; + u8 ireg = 0; + + if (D7S_MINOR != iminor(file_inode(file))) + return -ENODEV; + + mutex_lock(&d7s_mutex); + switch (cmd) { + case D7SIOCWR: + /* assign device register values we mask-out D7S_FLIP + * if in sol_compat mode + */ + if (get_user(ireg, (int __user *) arg)) { + error = -EFAULT; + break; + } + if (sol_compat) { + if (regs & D7S_FLIP) + ireg |= D7S_FLIP; + else + ireg &= ~D7S_FLIP; + } + writeb(ireg, p->regs); + break; + + case D7SIOCRD: + /* retrieve device register values + * NOTE: Solaris implementation returns D7S_FLIP bit + * as toggled by user, even though it does not honor it. + * This driver will not misinform you about the state + * of your hardware while in sol_compat mode + */ + if (put_user(regs, (int __user *) arg)) { + error = -EFAULT; + break; + } + break; + + case D7SIOCTM: + /* toggle device mode-- flip display orientation */ + regs ^= D7S_FLIP; + writeb(regs, p->regs); + break; + } + mutex_unlock(&d7s_mutex); + + return error; +} + +static const struct file_operations d7s_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = d7s_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = d7s_open, + .release = d7s_release, + .llseek = noop_llseek, +}; + +static struct miscdevice d7s_miscdev = { + .minor = D7S_MINOR, + .name = DRIVER_NAME, + .fops = &d7s_fops +}; + +static int d7s_probe(struct platform_device *op) +{ + struct device_node *opts; + int err = -EINVAL; + struct d7s *p; + u8 regs; + + if (d7s_device) + goto out; + + p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL); + err = -ENOMEM; + if (!p) + goto out; + + p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s"); + if (!p->regs) { + printk(KERN_ERR PFX "Cannot map chip registers\n"); + goto out; + } + + err = misc_register(&d7s_miscdev); + if (err) { + printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n", + D7S_MINOR); + goto out_iounmap; + } + + /* OBP option "d7s-flipped?" is honored as default for the + * device, and reset default when detached + */ + regs = readb(p->regs); + opts = of_find_node_by_path("/options"); + if (opts) + p->flipped = of_property_read_bool(opts, "d7s-flipped?"); + + if (p->flipped) + regs |= D7S_FLIP; + else + regs &= ~D7S_FLIP; + + writeb(regs, p->regs); + + printk(KERN_INFO PFX "7-Segment Display%pOF at [%s:0x%llx] %s\n", + op->dev.of_node, + (regs & D7S_FLIP) ? " (FLIPPED)" : "", + op->resource[0].start, + sol_compat ? "in sol_compat mode" : ""); + + dev_set_drvdata(&op->dev, p); + d7s_device = p; + err = 0; + of_node_put(opts); + +out: + return err; + +out_iounmap: + of_iounmap(&op->resource[0], p->regs, sizeof(u8)); + goto out; +} + +static void d7s_remove(struct platform_device *op) +{ + struct d7s *p = dev_get_drvdata(&op->dev); + u8 regs = readb(p->regs); + + /* Honor OBP d7s-flipped? unless operating in solaris-compat mode */ + if (sol_compat) { + if (p->flipped) + regs |= D7S_FLIP; + else + regs &= ~D7S_FLIP; + writeb(regs, p->regs); + } + + misc_deregister(&d7s_miscdev); + of_iounmap(&op->resource[0], p->regs, sizeof(u8)); +} + +static const struct of_device_id d7s_match[] = { + { + .name = "display7seg", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, d7s_match); + +static struct platform_driver d7s_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = d7s_match, + }, + .probe = d7s_probe, + .remove = d7s_remove, +}; + +module_platform_driver(d7s_driver); diff --git a/drivers/sbus/char/envctrl.c b/drivers/sbus/char/envctrl.c new file mode 100644 index 000000000..81918aa67 --- /dev/null +++ b/drivers/sbus/char/envctrl.c @@ -0,0 +1,1134 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* envctrl.c: Temperature and Fan monitoring on Machines providing it. + * + * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 2000 Vinh Truong (vinh.truong@eng.sun.com) + * VT - The implementation is to support Sun Microelectronics (SME) platform + * environment monitoring. SME platforms use pcf8584 as the i2c bus + * controller to access pcf8591 (8-bit A/D and D/A converter) and + * pcf8571 (256 x 8-bit static low-voltage RAM with I2C-bus interface). + * At board level, it follows SME Firmware I2C Specification. Reference: + * http://www-eu2.semiconductors.com/pip/PCF8584P + * http://www-eu2.semiconductors.com/pip/PCF8574AP + * http://www-eu2.semiconductors.com/pip/PCF8591P + * + * EB - Added support for CP1500 Global Address and PS/Voltage monitoring. + * Eric Brower + * + * DB - Audit every copy_to_user in envctrl_read. + * Daniele Bellucci + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRIVER_NAME "envctrl" +#define PFX DRIVER_NAME ": " + +#define PCF8584_ADDRESS 0x55 + +#define CONTROL_PIN 0x80 +#define CONTROL_ES0 0x40 +#define CONTROL_ES1 0x20 +#define CONTROL_ES2 0x10 +#define CONTROL_ENI 0x08 +#define CONTROL_STA 0x04 +#define CONTROL_STO 0x02 +#define CONTROL_ACK 0x01 + +#define STATUS_PIN 0x80 +#define STATUS_STS 0x20 +#define STATUS_BER 0x10 +#define STATUS_LRB 0x08 +#define STATUS_AD0 0x08 +#define STATUS_AAB 0x04 +#define STATUS_LAB 0x02 +#define STATUS_BB 0x01 + +/* + * CLK Mode Register. + */ +#define BUS_CLK_90 0x00 +#define BUS_CLK_45 0x01 +#define BUS_CLK_11 0x02 +#define BUS_CLK_1_5 0x03 + +#define CLK_3 0x00 +#define CLK_4_43 0x10 +#define CLK_6 0x14 +#define CLK_8 0x18 +#define CLK_12 0x1c + +#define OBD_SEND_START 0xc5 /* value to generate I2c_bus START condition */ +#define OBD_SEND_STOP 0xc3 /* value to generate I2c_bus STOP condition */ + +/* Monitor type of i2c child device. + * Firmware definitions. + */ +#define PCF8584_MAX_CHANNELS 8 +#define PCF8584_GLOBALADDR_TYPE 6 /* global address monitor */ +#define PCF8584_FANSTAT_TYPE 3 /* fan status monitor */ +#define PCF8584_VOLTAGE_TYPE 2 /* voltage monitor */ +#define PCF8584_TEMP_TYPE 1 /* temperature monitor*/ + +/* Monitor type of i2c child device. + * Driver definitions. + */ +#define ENVCTRL_NOMON 0 +#define ENVCTRL_CPUTEMP_MON 1 /* cpu temperature monitor */ +#define ENVCTRL_CPUVOLTAGE_MON 2 /* voltage monitor */ +#define ENVCTRL_FANSTAT_MON 3 /* fan status monitor */ +#define ENVCTRL_ETHERTEMP_MON 4 /* ethernet temperature */ + /* monitor */ +#define ENVCTRL_VOLTAGESTAT_MON 5 /* voltage status monitor */ +#define ENVCTRL_MTHRBDTEMP_MON 6 /* motherboard temperature */ +#define ENVCTRL_SCSITEMP_MON 7 /* scsi temperature */ +#define ENVCTRL_GLOBALADDR_MON 8 /* global address */ + +/* Child device type. + * Driver definitions. + */ +#define I2C_ADC 0 /* pcf8591 */ +#define I2C_GPIO 1 /* pcf8571 */ + +/* Data read from child device may need to decode + * through a data table and a scale. + * Translation type as defined by firmware. + */ +#define ENVCTRL_TRANSLATE_NO 0 +#define ENVCTRL_TRANSLATE_PARTIAL 1 +#define ENVCTRL_TRANSLATE_COMBINED 2 +#define ENVCTRL_TRANSLATE_FULL 3 /* table[data] */ +#define ENVCTRL_TRANSLATE_SCALE 4 /* table[data]/scale */ + +/* Driver miscellaneous definitions. */ +#define ENVCTRL_MAX_CPU 4 +#define CHANNEL_DESC_SZ 256 + +/* Mask values for combined GlobalAddress/PowerStatus node */ +#define ENVCTRL_GLOBALADDR_ADDR_MASK 0x1F +#define ENVCTRL_GLOBALADDR_PSTAT_MASK 0x60 + +/* Node 0x70 ignored on CompactPCI CP1400/1500 platforms + * (see envctrl_init_i2c_child) + */ +#define ENVCTRL_CPCI_IGNORED_NODE 0x70 + +#define PCF8584_DATA 0x00 +#define PCF8584_CSR 0x01 + +/* Each child device can be monitored by up to PCF8584_MAX_CHANNELS. + * Property of a port or channel as defined by the firmware. + */ +struct pcf8584_channel { + unsigned char chnl_no; + unsigned char io_direction; + unsigned char type; + unsigned char last; +}; + +/* Each child device may have one or more tables of bytes to help decode + * data. Table property as defined by the firmware. + */ +struct pcf8584_tblprop { + unsigned int type; + unsigned int scale; + unsigned int offset; /* offset from the beginning of the table */ + unsigned int size; +}; + +/* i2c child */ +struct i2c_child_t { + /* Either ADC or GPIO. */ + unsigned char i2ctype; + unsigned long addr; + struct pcf8584_channel chnl_array[PCF8584_MAX_CHANNELS]; + + /* Channel info. */ + unsigned int total_chnls; /* Number of monitor channels. */ + unsigned char fan_mask; /* Byte mask for fan status channels. */ + unsigned char voltage_mask; /* Byte mask for voltage status channels. */ + struct pcf8584_tblprop tblprop_array[PCF8584_MAX_CHANNELS]; + + /* Properties of all monitor channels. */ + unsigned int total_tbls; /* Number of monitor tables. */ + char *tables; /* Pointer to table(s). */ + char chnls_desc[CHANNEL_DESC_SZ]; /* Channel description. */ + char mon_type[PCF8584_MAX_CHANNELS]; +}; + +static void __iomem *i2c; +static struct i2c_child_t i2c_childlist[ENVCTRL_MAX_CPU*2]; +static unsigned char chnls_mask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; +static unsigned int warning_temperature = 0; +static unsigned int shutdown_temperature = 0; +static char read_cpu; + +/* Forward declarations. */ +static struct i2c_child_t *envctrl_get_i2c_child(unsigned char); + +/* Function Description: Test the PIN bit (Pending Interrupt Not) + * to test when serial transmission is completed . + * Return : None. + */ +static void envtrl_i2c_test_pin(void) +{ + int limit = 1000000; + + while (--limit > 0) { + if (!(readb(i2c + PCF8584_CSR) & STATUS_PIN)) + break; + udelay(1); + } + + if (limit <= 0) + printk(KERN_INFO PFX "Pin status will not clear.\n"); +} + +/* Function Description: Test busy bit. + * Return : None. + */ +static void envctrl_i2c_test_bb(void) +{ + int limit = 1000000; + + while (--limit > 0) { + /* Busy bit 0 means busy. */ + if (readb(i2c + PCF8584_CSR) & STATUS_BB) + break; + udelay(1); + } + + if (limit <= 0) + printk(KERN_INFO PFX "Busy bit will not clear.\n"); +} + +/* Function Description: Send the address for a read access. + * Return : 0 if not acknowledged, otherwise acknowledged. + */ +static int envctrl_i2c_read_addr(unsigned char addr) +{ + envctrl_i2c_test_bb(); + + /* Load address. */ + writeb(addr + 1, i2c + PCF8584_DATA); + + envctrl_i2c_test_bb(); + + writeb(OBD_SEND_START, i2c + PCF8584_CSR); + + /* Wait for PIN. */ + envtrl_i2c_test_pin(); + + /* CSR 0 means acknowledged. */ + if (!(readb(i2c + PCF8584_CSR) & STATUS_LRB)) { + return readb(i2c + PCF8584_DATA); + } else { + writeb(OBD_SEND_STOP, i2c + PCF8584_CSR); + return 0; + } +} + +/* Function Description: Send the address for write mode. + * Return : None. + */ +static void envctrl_i2c_write_addr(unsigned char addr) +{ + envctrl_i2c_test_bb(); + writeb(addr, i2c + PCF8584_DATA); + + /* Generate Start condition. */ + writeb(OBD_SEND_START, i2c + PCF8584_CSR); +} + +/* Function Description: Read 1 byte of data from addr + * set by envctrl_i2c_read_addr() + * Return : Data from address set by envctrl_i2c_read_addr(). + */ +static unsigned char envctrl_i2c_read_data(void) +{ + envtrl_i2c_test_pin(); + writeb(CONTROL_ES0, i2c + PCF8584_CSR); /* Send neg ack. */ + return readb(i2c + PCF8584_DATA); +} + +/* Function Description: Instruct the device which port to read data from. + * Return : None. + */ +static void envctrl_i2c_write_data(unsigned char port) +{ + envtrl_i2c_test_pin(); + writeb(port, i2c + PCF8584_DATA); +} + +/* Function Description: Generate Stop condition after last byte is sent. + * Return : None. + */ +static void envctrl_i2c_stop(void) +{ + envtrl_i2c_test_pin(); + writeb(OBD_SEND_STOP, i2c + PCF8584_CSR); +} + +/* Function Description: Read adc device. + * Return : Data at address and port. + */ +static unsigned char envctrl_i2c_read_8591(unsigned char addr, unsigned char port) +{ + /* Send address. */ + envctrl_i2c_write_addr(addr); + + /* Setup port to read. */ + envctrl_i2c_write_data(port); + envctrl_i2c_stop(); + + /* Read port. */ + envctrl_i2c_read_addr(addr); + + /* Do a single byte read and send stop. */ + envctrl_i2c_read_data(); + envctrl_i2c_stop(); + + return readb(i2c + PCF8584_DATA); +} + +/* Function Description: Read gpio device. + * Return : Data at address. + */ +static unsigned char envctrl_i2c_read_8574(unsigned char addr) +{ + unsigned char rd; + + envctrl_i2c_read_addr(addr); + + /* Do a single byte read and send stop. */ + rd = envctrl_i2c_read_data(); + envctrl_i2c_stop(); + return rd; +} + +/* Function Description: Decode data read from an adc device using firmware + * table. + * Return: Number of read bytes. Data is stored in bufdata in ascii format. + */ +static int envctrl_i2c_data_translate(unsigned char data, int translate_type, + int scale, char *tbl, char *bufdata) +{ + int len = 0; + + switch (translate_type) { + case ENVCTRL_TRANSLATE_NO: + /* No decode necessary. */ + len = 1; + bufdata[0] = data; + break; + + case ENVCTRL_TRANSLATE_FULL: + /* Decode this way: data = table[data]. */ + len = 1; + bufdata[0] = tbl[data]; + break; + + case ENVCTRL_TRANSLATE_SCALE: + /* Decode this way: data = table[data]/scale */ + sprintf(bufdata,"%d ", (tbl[data] * 10) / (scale)); + len = strlen(bufdata); + bufdata[len - 1] = bufdata[len - 2]; + bufdata[len - 2] = '.'; + break; + + default: + break; + } + + return len; +} + +/* Function Description: Read cpu-related data such as cpu temperature, voltage. + * Return: Number of read bytes. Data is stored in bufdata in ascii format. + */ +static int envctrl_read_cpu_info(int cpu, struct i2c_child_t *pchild, + char mon_type, unsigned char *bufdata) +{ + unsigned char data; + int i, j = -1; + char *tbl; + + /* Find the right monitor type and channel. */ + for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { + if (pchild->mon_type[i] == mon_type) { + if (++j == cpu) { + break; + } + } + } + + if (j != cpu) + return 0; + + /* Read data from address and port. */ + data = envctrl_i2c_read_8591((unsigned char)pchild->addr, + (unsigned char)pchild->chnl_array[i].chnl_no); + + /* Find decoding table. */ + tbl = pchild->tables + pchild->tblprop_array[i].offset; + + return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type, + pchild->tblprop_array[i].scale, + tbl, bufdata); +} + +/* Function Description: Read noncpu-related data such as motherboard + * temperature. + * Return: Number of read bytes. Data is stored in bufdata in ascii format. + */ +static int envctrl_read_noncpu_info(struct i2c_child_t *pchild, + char mon_type, unsigned char *bufdata) +{ + unsigned char data; + int i; + char *tbl = NULL; + + for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { + if (pchild->mon_type[i] == mon_type) + break; + } + + if (i >= PCF8584_MAX_CHANNELS) + return 0; + + /* Read data from address and port. */ + data = envctrl_i2c_read_8591((unsigned char)pchild->addr, + (unsigned char)pchild->chnl_array[i].chnl_no); + + /* Find decoding table. */ + tbl = pchild->tables + pchild->tblprop_array[i].offset; + + return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type, + pchild->tblprop_array[i].scale, + tbl, bufdata); +} + +/* Function Description: Read fan status. + * Return : Always 1 byte. Status stored in bufdata. + */ +static int envctrl_i2c_fan_status(struct i2c_child_t *pchild, + unsigned char data, + char *bufdata) +{ + unsigned char tmp, ret = 0; + int i, j = 0; + + tmp = data & pchild->fan_mask; + + if (tmp == pchild->fan_mask) { + /* All bits are on. All fans are functioning. */ + ret = ENVCTRL_ALL_FANS_GOOD; + } else if (tmp == 0) { + /* No bits are on. No fans are functioning. */ + ret = ENVCTRL_ALL_FANS_BAD; + } else { + /* Go through all channels, mark 'on' the matched bits. + * Notice that fan_mask may have discontiguous bits but + * return mask are always contiguous. For example if we + * monitor 4 fans at channels 0,1,2,4, the return mask + * should be 00010000 if only fan at channel 4 is working. + */ + for (i = 0; i < PCF8584_MAX_CHANNELS;i++) { + if (pchild->fan_mask & chnls_mask[i]) { + if (!(chnls_mask[i] & tmp)) + ret |= chnls_mask[j]; + + j++; + } + } + } + + bufdata[0] = ret; + return 1; +} + +/* Function Description: Read global addressing line. + * Return : Always 1 byte. Status stored in bufdata. + */ +static int envctrl_i2c_globaladdr(struct i2c_child_t *pchild, + unsigned char data, + char *bufdata) +{ + /* Translatation table is not necessary, as global + * addr is the integer value of the GA# bits. + * + * NOTE: MSB is documented as zero, but I see it as '1' always.... + * + * ----------------------------------------------- + * | 0 | FAL | DEG | GA4 | GA3 | GA2 | GA1 | GA0 | + * ----------------------------------------------- + * GA0 - GA4 integer value of Global Address (backplane slot#) + * DEG 0 = cPCI Power supply output is starting to degrade + * 1 = cPCI Power supply output is OK + * FAL 0 = cPCI Power supply has failed + * 1 = cPCI Power supply output is OK + */ + bufdata[0] = (data & ENVCTRL_GLOBALADDR_ADDR_MASK); + return 1; +} + +/* Function Description: Read standard voltage and power supply status. + * Return : Always 1 byte. Status stored in bufdata. + */ +static unsigned char envctrl_i2c_voltage_status(struct i2c_child_t *pchild, + unsigned char data, + char *bufdata) +{ + unsigned char tmp, ret = 0; + int i, j = 0; + + tmp = data & pchild->voltage_mask; + + /* Two channels are used to monitor voltage and power supply. */ + if (tmp == pchild->voltage_mask) { + /* All bits are on. Voltage and power supply are okay. */ + ret = ENVCTRL_VOLTAGE_POWERSUPPLY_GOOD; + } else if (tmp == 0) { + /* All bits are off. Voltage and power supply are bad */ + ret = ENVCTRL_VOLTAGE_POWERSUPPLY_BAD; + } else { + /* Either voltage or power supply has problem. */ + for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { + if (pchild->voltage_mask & chnls_mask[i]) { + j++; + + /* Break out when there is a mismatch. */ + if (!(chnls_mask[i] & tmp)) + break; + } + } + + /* Make a wish that hardware will always use the + * first channel for voltage and the second for + * power supply. + */ + if (j == 1) + ret = ENVCTRL_VOLTAGE_BAD; + else + ret = ENVCTRL_POWERSUPPLY_BAD; + } + + bufdata[0] = ret; + return 1; +} + +/* Function Description: Read a byte from /dev/envctrl. Mapped to user read(). + * Return: Number of read bytes. 0 for error. + */ +static ssize_t +envctrl_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + struct i2c_child_t *pchild; + unsigned char data[10]; + int ret = 0; + + /* Get the type of read as decided in ioctl() call. + * Find the appropriate i2c child. + * Get the data and put back to the user buffer. + */ + + switch ((int)(long)file->private_data) { + case ENVCTRL_RD_WARNING_TEMPERATURE: + if (warning_temperature == 0) + return 0; + + data[0] = (unsigned char)(warning_temperature); + ret = 1; + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_SHUTDOWN_TEMPERATURE: + if (shutdown_temperature == 0) + return 0; + + data[0] = (unsigned char)(shutdown_temperature); + ret = 1; + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_MTHRBD_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_MTHRBDTEMP_MON))) + return 0; + ret = envctrl_read_noncpu_info(pchild, ENVCTRL_MTHRBDTEMP_MON, data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_CPU_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) + return 0; + ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUTEMP_MON, data); + + /* Reset cpu to the default cpu0. */ + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_CPU_VOLTAGE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUVOLTAGE_MON))) + return 0; + ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUVOLTAGE_MON, data); + + /* Reset cpu to the default cpu0. */ + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_SCSI_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_SCSITEMP_MON))) + return 0; + ret = envctrl_read_noncpu_info(pchild, ENVCTRL_SCSITEMP_MON, data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_ETHERNET_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_ETHERTEMP_MON))) + return 0; + ret = envctrl_read_noncpu_info(pchild, ENVCTRL_ETHERTEMP_MON, data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_FAN_STATUS: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_FANSTAT_MON))) + return 0; + data[0] = envctrl_i2c_read_8574(pchild->addr); + ret = envctrl_i2c_fan_status(pchild,data[0], data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_GLOBALADDRESS: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON))) + return 0; + data[0] = envctrl_i2c_read_8574(pchild->addr); + ret = envctrl_i2c_globaladdr(pchild, data[0], data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_VOLTAGE_STATUS: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_VOLTAGESTAT_MON))) + /* If voltage monitor not present, check for CPCI equivalent */ + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON))) + return 0; + data[0] = envctrl_i2c_read_8574(pchild->addr); + ret = envctrl_i2c_voltage_status(pchild, data[0], data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + default: + break; + + } + + return ret; +} + +/* Function Description: Command what to read. Mapped to user ioctl(). + * Return: Gives 0 for implemented commands, -EINVAL otherwise. + */ +static long +envctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + char __user *infobuf; + + switch (cmd) { + case ENVCTRL_RD_WARNING_TEMPERATURE: + case ENVCTRL_RD_SHUTDOWN_TEMPERATURE: + case ENVCTRL_RD_MTHRBD_TEMPERATURE: + case ENVCTRL_RD_FAN_STATUS: + case ENVCTRL_RD_VOLTAGE_STATUS: + case ENVCTRL_RD_ETHERNET_TEMPERATURE: + case ENVCTRL_RD_SCSI_TEMPERATURE: + case ENVCTRL_RD_GLOBALADDRESS: + file->private_data = (void *)(long)cmd; + break; + + case ENVCTRL_RD_CPU_TEMPERATURE: + case ENVCTRL_RD_CPU_VOLTAGE: + /* Check to see if application passes in any cpu number, + * the default is cpu0. + */ + infobuf = (char __user *) arg; + if (infobuf == NULL) { + read_cpu = 0; + }else { + get_user(read_cpu, infobuf); + } + + /* Save the command for use when reading. */ + file->private_data = (void *)(long)cmd; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* Function Description: open device. Mapped to user open(). + * Return: Always 0. + */ +static int +envctrl_open(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +/* Function Description: Open device. Mapped to user close(). + * Return: Always 0. + */ +static int +envctrl_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations envctrl_fops = { + .owner = THIS_MODULE, + .read = envctrl_read, + .unlocked_ioctl = envctrl_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = envctrl_open, + .release = envctrl_release, + .llseek = noop_llseek, +}; + +static struct miscdevice envctrl_dev = { + ENVCTRL_MINOR, + "envctrl", + &envctrl_fops +}; + +/* Function Description: Set monitor type based on firmware description. + * Return: None. + */ +static void envctrl_set_mon(struct i2c_child_t *pchild, + const char *chnl_desc, + int chnl_no) +{ + /* Firmware only has temperature type. It does not distinguish + * different kinds of temperatures. We use channel description + * to disinguish them. + */ + if (!(strcmp(chnl_desc,"temp,cpu")) || + !(strcmp(chnl_desc,"temp,cpu0")) || + !(strcmp(chnl_desc,"temp,cpu1")) || + !(strcmp(chnl_desc,"temp,cpu2")) || + !(strcmp(chnl_desc,"temp,cpu3"))) + pchild->mon_type[chnl_no] = ENVCTRL_CPUTEMP_MON; + + if (!(strcmp(chnl_desc,"vddcore,cpu0")) || + !(strcmp(chnl_desc,"vddcore,cpu1")) || + !(strcmp(chnl_desc,"vddcore,cpu2")) || + !(strcmp(chnl_desc,"vddcore,cpu3"))) + pchild->mon_type[chnl_no] = ENVCTRL_CPUVOLTAGE_MON; + + if (!(strcmp(chnl_desc,"temp,motherboard"))) + pchild->mon_type[chnl_no] = ENVCTRL_MTHRBDTEMP_MON; + + if (!(strcmp(chnl_desc,"temp,scsi"))) + pchild->mon_type[chnl_no] = ENVCTRL_SCSITEMP_MON; + + if (!(strcmp(chnl_desc,"temp,ethernet"))) + pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON; +} + +/* Function Description: Initialize monitor channel with channel desc, + * decoding tables, monitor type, optional properties. + * Return: None. + */ +static void envctrl_init_adc(struct i2c_child_t *pchild, struct device_node *dp) +{ + int i = 0, len; + const char *pos; + const unsigned int *pval; + + /* Firmware describe channels into a stream separated by a '\0'. */ + pos = of_get_property(dp, "channels-description", &len); + + while (len > 0) { + int l = strlen(pos) + 1; + envctrl_set_mon(pchild, pos, i++); + len -= l; + pos += l; + } + + /* Get optional properties. */ + pval = of_get_property(dp, "warning-temp", NULL); + if (pval) + warning_temperature = *pval; + + pval = of_get_property(dp, "shutdown-temp", NULL); + if (pval) + shutdown_temperature = *pval; +} + +/* Function Description: Initialize child device monitoring fan status. + * Return: None. + */ +static void envctrl_init_fanstat(struct i2c_child_t *pchild) +{ + int i; + + /* Go through all channels and set up the mask. */ + for (i = 0; i < pchild->total_chnls; i++) + pchild->fan_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no]; + + /* We only need to know if this child has fan status monitored. + * We don't care which channels since we have the mask already. + */ + pchild->mon_type[0] = ENVCTRL_FANSTAT_MON; +} + +/* Function Description: Initialize child device for global addressing line. + * Return: None. + */ +static void envctrl_init_globaladdr(struct i2c_child_t *pchild) +{ + int i; + + /* Voltage/PowerSupply monitoring is piggybacked + * with Global Address on CompactPCI. See comments + * within envctrl_i2c_globaladdr for bit assignments. + * + * The mask is created here by assigning mask bits to each + * bit position that represents PCF8584_VOLTAGE_TYPE data. + * Channel numbers are not consecutive within the globaladdr + * node (why?), so we use the actual counter value as chnls_mask + * index instead of the chnl_array[x].chnl_no value. + * + * NOTE: This loop could be replaced with a constant representing + * a mask of bits 5&6 (ENVCTRL_GLOBALADDR_PSTAT_MASK). + */ + for (i = 0; i < pchild->total_chnls; i++) { + if (PCF8584_VOLTAGE_TYPE == pchild->chnl_array[i].type) { + pchild->voltage_mask |= chnls_mask[i]; + } + } + + /* We only need to know if this child has global addressing + * line monitored. We don't care which channels since we know + * the mask already (ENVCTRL_GLOBALADDR_ADDR_MASK). + */ + pchild->mon_type[0] = ENVCTRL_GLOBALADDR_MON; +} + +/* Initialize child device monitoring voltage status. */ +static void envctrl_init_voltage_status(struct i2c_child_t *pchild) +{ + int i; + + /* Go through all channels and set up the mask. */ + for (i = 0; i < pchild->total_chnls; i++) + pchild->voltage_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no]; + + /* We only need to know if this child has voltage status monitored. + * We don't care which channels since we have the mask already. + */ + pchild->mon_type[0] = ENVCTRL_VOLTAGESTAT_MON; +} + +/* Function Description: Initialize i2c child device. + * Return: None. + */ +static void envctrl_init_i2c_child(struct device_node *dp, + struct i2c_child_t *pchild) +{ + int len, i, tbls_size = 0; + const void *pval; + + /* Get device address. */ + pval = of_get_property(dp, "reg", &len); + memcpy(&pchild->addr, pval, len); + + /* Get tables property. Read firmware temperature tables. */ + pval = of_get_property(dp, "translation", &len); + if (pval && len > 0) { + memcpy(pchild->tblprop_array, pval, len); + pchild->total_tbls = len / sizeof(struct pcf8584_tblprop); + for (i = 0; i < pchild->total_tbls; i++) { + if ((pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset) > tbls_size) { + tbls_size = pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset; + } + } + + pchild->tables = kmalloc(tbls_size, GFP_KERNEL); + if (pchild->tables == NULL){ + printk(KERN_ERR PFX "Failed to allocate table.\n"); + return; + } + pval = of_get_property(dp, "tables", &len); + if (!pval || len <= 0) { + printk(KERN_ERR PFX "Failed to get table.\n"); + return; + } + memcpy(pchild->tables, pval, len); + } + + /* SPARCengine ASM Reference Manual (ref. SMI doc 805-7581-04) + * sections 2.5, 3.5, 4.5 state node 0x70 for CP1400/1500 is + * "For Factory Use Only." + * + * We ignore the node on these platforms by assigning the + * 'NULL' monitor type. + */ + if (ENVCTRL_CPCI_IGNORED_NODE == pchild->addr) { + struct device_node *root_node; + int len; + + root_node = of_find_node_by_path("/"); + if (of_node_name_eq(root_node, "SUNW,UltraSPARC-IIi-cEngine")) { + for (len = 0; len < PCF8584_MAX_CHANNELS; ++len) { + pchild->mon_type[len] = ENVCTRL_NOMON; + } + of_node_put(root_node); + return; + } + of_node_put(root_node); + } + + /* Get the monitor channels. */ + pval = of_get_property(dp, "channels-in-use", &len); + memcpy(pchild->chnl_array, pval, len); + pchild->total_chnls = len / sizeof(struct pcf8584_channel); + + for (i = 0; i < pchild->total_chnls; i++) { + switch (pchild->chnl_array[i].type) { + case PCF8584_TEMP_TYPE: + envctrl_init_adc(pchild, dp); + break; + + case PCF8584_GLOBALADDR_TYPE: + envctrl_init_globaladdr(pchild); + i = pchild->total_chnls; + break; + + case PCF8584_FANSTAT_TYPE: + envctrl_init_fanstat(pchild); + i = pchild->total_chnls; + break; + + case PCF8584_VOLTAGE_TYPE: + if (pchild->i2ctype == I2C_ADC) { + envctrl_init_adc(pchild,dp); + } else { + envctrl_init_voltage_status(pchild); + } + i = pchild->total_chnls; + break; + + default: + break; + } + } +} + +/* Function Description: Search the child device list for a device. + * Return : The i2c child if found. NULL otherwise. + */ +static struct i2c_child_t *envctrl_get_i2c_child(unsigned char mon_type) +{ + int i, j; + + for (i = 0; i < ENVCTRL_MAX_CPU*2; i++) { + for (j = 0; j < PCF8584_MAX_CHANNELS; j++) { + if (i2c_childlist[i].mon_type[j] == mon_type) { + return (struct i2c_child_t *)(&(i2c_childlist[i])); + } + } + } + return NULL; +} + +static void envctrl_do_shutdown(void) +{ + static int inprog = 0; + + if (inprog != 0) + return; + + inprog = 1; + printk(KERN_CRIT "kenvctrld: WARNING: Shutting down the system now.\n"); + orderly_poweroff(true); +} + +static struct task_struct *kenvctrld_task; + +static int kenvctrld(void *__unused) +{ + int poll_interval; + int whichcpu; + char tempbuf[10]; + struct i2c_child_t *cputemp; + + if (NULL == (cputemp = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) { + printk(KERN_ERR PFX + "kenvctrld unable to monitor CPU temp-- exiting\n"); + return -ENODEV; + } + + poll_interval = 5000; /* TODO env_mon_interval */ + + printk(KERN_INFO PFX "%s starting...\n", current->comm); + for (;;) { + msleep_interruptible(poll_interval); + + if (kthread_should_stop()) + break; + + for (whichcpu = 0; whichcpu < ENVCTRL_MAX_CPU; ++whichcpu) { + if (0 < envctrl_read_cpu_info(whichcpu, cputemp, + ENVCTRL_CPUTEMP_MON, + tempbuf)) { + if (tempbuf[0] >= shutdown_temperature) { + printk(KERN_CRIT + "%s: WARNING: CPU%i temperature %i C meets or exceeds "\ + "shutdown threshold %i C\n", + current->comm, whichcpu, + tempbuf[0], shutdown_temperature); + envctrl_do_shutdown(); + } + } + } + } + printk(KERN_INFO PFX "%s exiting...\n", current->comm); + return 0; +} + +static int envctrl_probe(struct platform_device *op) +{ + struct device_node *dp; + int index, err; + + if (i2c) + return -EINVAL; + + i2c = of_ioremap(&op->resource[0], 0, 0x2, DRIVER_NAME); + if (!i2c) + return -ENOMEM; + + index = 0; + dp = op->dev.of_node->child; + while (dp) { + if (of_node_name_eq(dp, "gpio")) { + i2c_childlist[index].i2ctype = I2C_GPIO; + envctrl_init_i2c_child(dp, &(i2c_childlist[index++])); + } else if (of_node_name_eq(dp, "adc")) { + i2c_childlist[index].i2ctype = I2C_ADC; + envctrl_init_i2c_child(dp, &(i2c_childlist[index++])); + } + + dp = dp->sibling; + } + + /* Set device address. */ + writeb(CONTROL_PIN, i2c + PCF8584_CSR); + writeb(PCF8584_ADDRESS, i2c + PCF8584_DATA); + + /* Set system clock and SCL frequencies. */ + writeb(CONTROL_PIN | CONTROL_ES1, i2c + PCF8584_CSR); + writeb(CLK_4_43 | BUS_CLK_90, i2c + PCF8584_DATA); + + /* Enable serial interface. */ + writeb(CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK, i2c + PCF8584_CSR); + udelay(200); + + /* Register the device as a minor miscellaneous device. */ + err = misc_register(&envctrl_dev); + if (err) { + printk(KERN_ERR PFX "Unable to get misc minor %d\n", + envctrl_dev.minor); + goto out_iounmap; + } + + /* Note above traversal routine post-incremented 'i' to accommodate + * a next child device, so we decrement before reverse-traversal of + * child devices. + */ + printk(KERN_INFO PFX "Initialized "); + for (--index; index >= 0; --index) { + printk("[%s 0x%lx]%s", + (I2C_ADC == i2c_childlist[index].i2ctype) ? "adc" : + ((I2C_GPIO == i2c_childlist[index].i2ctype) ? "gpio" : "unknown"), + i2c_childlist[index].addr, (0 == index) ? "\n" : " "); + } + + kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld"); + if (IS_ERR(kenvctrld_task)) { + err = PTR_ERR(kenvctrld_task); + goto out_deregister; + } + + return 0; + +out_deregister: + misc_deregister(&envctrl_dev); +out_iounmap: + of_iounmap(&op->resource[0], i2c, 0x2); + for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++) + kfree(i2c_childlist[index].tables); + + return err; +} + +static void envctrl_remove(struct platform_device *op) +{ + int index; + + kthread_stop(kenvctrld_task); + + of_iounmap(&op->resource[0], i2c, 0x2); + misc_deregister(&envctrl_dev); + + for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++) + kfree(i2c_childlist[index].tables); +} + +static const struct of_device_id envctrl_match[] = { + { + .name = "i2c", + .compatible = "i2cpcf,8584", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, envctrl_match); + +static struct platform_driver envctrl_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = envctrl_match, + }, + .probe = envctrl_probe, + .remove = envctrl_remove, +}; + +module_platform_driver(envctrl_driver); + +MODULE_DESCRIPTION("SUN environment monitoring device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sbus/char/flash.c b/drivers/sbus/char/flash.c new file mode 100644 index 000000000..6524a4a19 --- /dev/null +++ b/drivers/sbus/char/flash.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* flash.c: Allow mmap access to the OBP Flash, for OBP updates. + * + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static DEFINE_MUTEX(flash_mutex); +static DEFINE_SPINLOCK(flash_lock); +static struct { + unsigned long read_base; /* Physical read address */ + unsigned long write_base; /* Physical write address */ + unsigned long read_size; /* Size of read area */ + unsigned long write_size; /* Size of write area */ + unsigned long busy; /* In use? */ +} flash; + +static int +flash_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long addr; + unsigned long size; + + spin_lock(&flash_lock); + if (flash.read_base == flash.write_base) { + addr = flash.read_base; + size = flash.read_size; + } else { + if ((vma->vm_flags & VM_READ) && + (vma->vm_flags & VM_WRITE)) { + spin_unlock(&flash_lock); + return -EINVAL; + } + if (vma->vm_flags & VM_READ) { + addr = flash.read_base; + size = flash.read_size; + } else if (vma->vm_flags & VM_WRITE) { + addr = flash.write_base; + size = flash.write_size; + } else { + spin_unlock(&flash_lock); + return -ENXIO; + } + } + spin_unlock(&flash_lock); + + if ((vma->vm_pgoff << PAGE_SHIFT) > size) + return -ENXIO; + addr = vma->vm_pgoff + (addr >> PAGE_SHIFT); + + if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size) + size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)); + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (io_remap_pfn_range(vma, vma->vm_start, addr, size, vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static long long +flash_llseek(struct file *file, long long offset, int origin) +{ + mutex_lock(&flash_mutex); + switch (origin) { + case 0: + file->f_pos = offset; + break; + case 1: + file->f_pos += offset; + if (file->f_pos > flash.read_size) + file->f_pos = flash.read_size; + break; + case 2: + file->f_pos = flash.read_size; + break; + default: + mutex_unlock(&flash_mutex); + return -EINVAL; + } + mutex_unlock(&flash_mutex); + return file->f_pos; +} + +static ssize_t +flash_read(struct file * file, char __user * buf, + size_t count, loff_t *ppos) +{ + loff_t p = *ppos; + int i; + + if (count > flash.read_size - p) + count = flash.read_size - p; + + for (i = 0; i < count; i++) { + u8 data = upa_readb(flash.read_base + p + i); + if (put_user(data, buf)) + return -EFAULT; + buf++; + } + + *ppos += count; + return count; +} + +static int +flash_open(struct inode *inode, struct file *file) +{ + mutex_lock(&flash_mutex); + if (test_and_set_bit(0, (void *)&flash.busy) != 0) { + mutex_unlock(&flash_mutex); + return -EBUSY; + } + + mutex_unlock(&flash_mutex); + return 0; +} + +static int +flash_release(struct inode *inode, struct file *file) +{ + spin_lock(&flash_lock); + flash.busy = 0; + spin_unlock(&flash_lock); + + return 0; +} + +static const struct file_operations flash_fops = { + /* no write to the Flash, use mmap + * and play flash dependent tricks. + */ + .owner = THIS_MODULE, + .llseek = flash_llseek, + .read = flash_read, + .mmap = flash_mmap, + .open = flash_open, + .release = flash_release, +}; + +static struct miscdevice flash_dev = { SBUS_FLASH_MINOR, "flash", &flash_fops }; + +static int flash_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct device_node *parent; + + parent = dp->parent; + + if (!of_node_name_eq(parent, "sbus") && + !of_node_name_eq(parent, "sbi") && + !of_node_name_eq(parent, "ebus")) + return -ENODEV; + + flash.read_base = op->resource[0].start; + flash.read_size = resource_size(&op->resource[0]); + if (op->resource[1].flags) { + flash.write_base = op->resource[1].start; + flash.write_size = resource_size(&op->resource[1]); + } else { + flash.write_base = op->resource[0].start; + flash.write_size = resource_size(&op->resource[0]); + } + flash.busy = 0; + + printk(KERN_INFO "%pOF: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n", + op->dev.of_node, + flash.read_base, flash.read_size, + flash.write_base, flash.write_size); + + return misc_register(&flash_dev); +} + +static void flash_remove(struct platform_device *op) +{ + misc_deregister(&flash_dev); +} + +static const struct of_device_id flash_match[] = { + { + .name = "flashprom", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, flash_match); + +static struct platform_driver flash_driver = { + .driver = { + .name = "flash", + .of_match_table = flash_match, + }, + .probe = flash_probe, + .remove = flash_remove, +}; + +module_platform_driver(flash_driver); + +MODULE_DESCRIPTION("OBP Flash Device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/sbus/char/max1617.h b/drivers/sbus/char/max1617.h new file mode 100644 index 000000000..45c831878 --- /dev/null +++ b/drivers/sbus/char/max1617.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* $Id: max1617.h,v 1.1 2001/04/02 09:59:08 davem Exp $ */ +#ifndef _MAX1617_H +#define _MAX1617_H + +#define MAX1617_AMB_TEMP 0x00 /* Ambient temp in C */ +#define MAX1617_CPU_TEMP 0x01 /* Processor die temp in C */ +#define MAX1617_STATUS 0x02 /* Chip status bits */ + +/* Read-only versions of changeable registers. */ +#define MAX1617_RD_CFG_BYTE 0x03 /* Config register */ +#define MAX1617_RD_CVRATE_BYTE 0x04 /* Temp conversion rate */ +#define MAX1617_RD_AMB_HIGHLIM 0x05 /* Ambient high limit */ +#define MAX1617_RD_AMB_LOWLIM 0x06 /* Ambient low limit */ +#define MAX1617_RD_CPU_HIGHLIM 0x07 /* Processor high limit */ +#define MAX1617_RD_CPU_LOWLIM 0x08 /* Processor low limit */ + +/* Write-only versions of the same. */ +#define MAX1617_WR_CFG_BYTE 0x09 +#define MAX1617_WR_CVRATE_BYTE 0x0a +#define MAX1617_WR_AMB_HIGHLIM 0x0b +#define MAX1617_WR_AMB_LOWLIM 0x0c +#define MAX1617_WR_CPU_HIGHLIM 0x0d +#define MAX1617_WR_CPU_LOWLIM 0x0e + +#define MAX1617_ONESHOT 0x0f + +#endif /* _MAX1617_H */ diff --git a/drivers/sbus/char/openprom.c b/drivers/sbus/char/openprom.c new file mode 100644 index 000000000..0b90a2450 --- /dev/null +++ b/drivers/sbus/char/openprom.c @@ -0,0 +1,725 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Linux/SPARC PROM Configuration Driver + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * + * This character device driver allows user programs to access the + * PROM device tree. It is compatible with the SunOS /dev/openprom + * driver and the NetBSD /dev/openprom driver. The SunOS eeprom + * utility works without any modifications. + * + * The driver uses a minor number under the misc device major. The + * file read/write mode determines the type of access to the PROM. + * Interrupts are disabled whenever the driver calls into the PROM for + * sanity's sake. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PCI +#include +#endif + +MODULE_AUTHOR("Thomas K. Dyas and Eddie C. Dost "); +MODULE_DESCRIPTION("OPENPROM Configuration Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR); + +/* Private data kept by the driver for each descriptor. */ +typedef struct openprom_private_data +{ + struct device_node *current_node; /* Current node for SunOS ioctls. */ + struct device_node *lastnode; /* Last valid node used by BSD ioctls. */ +} DATA; + +/* ID of the PROM node containing all of the EEPROM options. */ +static DEFINE_MUTEX(openprom_mutex); +static struct device_node *options_node; + +/* + * Copy an openpromio structure into kernel space from user space. + * This routine does error checking to make sure that all memory + * accesses are within bounds. A pointer to the allocated openpromio + * structure will be placed in "*opp_p". Return value is the length + * of the user supplied buffer. + */ +static int copyin(struct openpromio __user *info, struct openpromio **opp_p) +{ + unsigned int bufsize; + + if (!info || !opp_p) + return -EFAULT; + + if (get_user(bufsize, &info->oprom_size)) + return -EFAULT; + + if (bufsize == 0) + return -EINVAL; + + /* If the bufsize is too large, just limit it. + * Fix from Jason Rappleye. + */ + if (bufsize > OPROMMAXPARAM) + bufsize = OPROMMAXPARAM; + + if (!(*opp_p = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(&(*opp_p)->oprom_array, + &info->oprom_array, bufsize)) { + kfree(*opp_p); + return -EFAULT; + } + return bufsize; +} + +static int getstrings(struct openpromio __user *info, struct openpromio **opp_p) +{ + int n, bufsize; + char c; + + if (!info || !opp_p) + return -EFAULT; + + if (!(*opp_p = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL))) + return -ENOMEM; + + (*opp_p)->oprom_size = 0; + + n = bufsize = 0; + while ((n < 2) && (bufsize < OPROMMAXPARAM)) { + if (get_user(c, &info->oprom_array[bufsize])) { + kfree(*opp_p); + return -EFAULT; + } + if (c == '\0') + n++; + (*opp_p)->oprom_array[bufsize++] = c; + } + if (!n) { + kfree(*opp_p); + return -EINVAL; + } + return bufsize; +} + +/* + * Copy an openpromio structure in kernel space back to user space. + */ +static int copyout(void __user *info, struct openpromio *opp, int len) +{ + if (copy_to_user(info, opp, len)) + return -EFAULT; + return 0; +} + +static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize) +{ + const void *pval; + int len; + + if (!dp || + !(pval = of_get_property(dp, op->oprom_array, &len)) || + len <= 0 || len > bufsize) + return copyout(argp, op, sizeof(int)); + + memcpy(op->oprom_array, pval, len); + op->oprom_array[len] = '\0'; + op->oprom_size = len; + + return copyout(argp, op, sizeof(int) + bufsize); +} + +static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize) +{ + struct property *prop; + int len; + + if (!dp) + return copyout(argp, op, sizeof(int)); + if (op->oprom_array[0] == '\0') { + prop = dp->properties; + if (!prop) + return copyout(argp, op, sizeof(int)); + len = strlen(prop->name); + } else { + prop = of_find_property(dp, op->oprom_array, NULL); + + if (!prop || + !prop->next || + (len = strlen(prop->next->name)) + 1 > bufsize) + return copyout(argp, op, sizeof(int)); + + prop = prop->next; + } + + memcpy(op->oprom_array, prop->name, len); + op->oprom_array[len] = '\0'; + op->oprom_size = ++len; + + return copyout(argp, op, sizeof(int) + bufsize); +} + +static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize) +{ + char *buf = op->oprom_array + strlen(op->oprom_array) + 1; + int len = op->oprom_array + bufsize - buf; + + return of_set_property(options_node, op->oprom_array, buf, len); +} + +static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +{ + phandle ph; + + BUILD_BUG_ON(sizeof(phandle) != sizeof(int)); + + if (bufsize < sizeof(phandle)) + return -EINVAL; + + ph = *((int *) op->oprom_array); + if (ph) { + dp = of_find_node_by_phandle(ph); + if (!dp) + return -EINVAL; + + switch (cmd) { + case OPROMNEXT: + dp = dp->sibling; + break; + + case OPROMCHILD: + dp = dp->child; + break; + + case OPROMSETCUR: + default: + break; + } + } else { + /* Sibling of node zero is the root node. */ + if (cmd != OPROMNEXT) + return -EINVAL; + + dp = of_find_node_by_path("/"); + } + + ph = 0; + if (dp) + ph = dp->phandle; + + data->current_node = dp; + *((int *) op->oprom_array) = ph; + op->oprom_size = sizeof(phandle); + + return copyout(argp, op, bufsize + sizeof(int)); +} + +static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +{ + int err = -EINVAL; + + if (bufsize >= 2*sizeof(int)) { +#ifdef CONFIG_PCI + struct pci_dev *pdev; + struct device_node *dp; + + pdev = pci_get_domain_bus_and_slot(0, + ((int *) op->oprom_array)[0], + ((int *) op->oprom_array)[1]); + + dp = pci_device_to_OF_node(pdev); + data->current_node = dp; + *((int *)op->oprom_array) = dp->phandle; + op->oprom_size = sizeof(int); + err = copyout(argp, op, bufsize + sizeof(int)); + + pci_dev_put(pdev); +#endif + } + + return err; +} + +static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +{ + phandle ph = 0; + + dp = of_find_node_by_path(op->oprom_array); + if (dp) + ph = dp->phandle; + data->current_node = dp; + *((int *)op->oprom_array) = ph; + op->oprom_size = sizeof(int); + + return copyout(argp, op, bufsize + sizeof(int)); +} + +static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize) +{ + char *buf = saved_command_line; + int len = strlen(buf); + + if (len > bufsize) + return -EINVAL; + + strcpy(op->oprom_array, buf); + op->oprom_size = len; + + return copyout(argp, op, bufsize + sizeof(int)); +} + +/* + * SunOS and Solaris /dev/openprom ioctl calls. + */ +static long openprom_sunos_ioctl(struct file * file, + unsigned int cmd, unsigned long arg, + struct device_node *dp) +{ + DATA *data = file->private_data; + struct openpromio *opp = NULL; + int bufsize, error = 0; + static int cnt; + void __user *argp = (void __user *)arg; + + if (cmd == OPROMSETOPT) + bufsize = getstrings(argp, &opp); + else + bufsize = copyin(argp, &opp); + + if (bufsize < 0) + return bufsize; + + mutex_lock(&openprom_mutex); + + switch (cmd) { + case OPROMGETOPT: + case OPROMGETPROP: + error = opromgetprop(argp, dp, opp, bufsize); + break; + + case OPROMNXTOPT: + case OPROMNXTPROP: + error = opromnxtprop(argp, dp, opp, bufsize); + break; + + case OPROMSETOPT: + case OPROMSETOPT2: + error = opromsetopt(dp, opp, bufsize); + break; + + case OPROMNEXT: + case OPROMCHILD: + case OPROMSETCUR: + error = opromnext(argp, cmd, dp, opp, bufsize, data); + break; + + case OPROMPCI2NODE: + error = oprompci2node(argp, dp, opp, bufsize, data); + break; + + case OPROMPATH2NODE: + error = oprompath2node(argp, dp, opp, bufsize, data); + break; + + case OPROMGETBOOTARGS: + error = opromgetbootargs(argp, opp, bufsize); + break; + + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + if (cnt++ < 10) + printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n"); + error = -EINVAL; + break; + default: + if (cnt++ < 10) + printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg); + error = -EINVAL; + break; + } + + kfree(opp); + mutex_unlock(&openprom_mutex); + + return error; +} + +static struct device_node *get_node(phandle n, DATA *data) +{ + struct device_node *dp = of_find_node_by_phandle(n); + + if (dp) + data->lastnode = dp; + + return dp; +} + +/* Copy in a whole string from userspace into kernelspace. */ +static char * copyin_string(char __user *user, size_t len) +{ + if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0) + return ERR_PTR(-EINVAL); + + return memdup_user_nul(user, len); +} + +/* + * NetBSD /dev/openprom ioctl calls. + */ +static int opiocget(void __user *argp, DATA *data) +{ + struct opiocdesc op; + struct device_node *dp; + char *str; + const void *pval; + int err, len; + + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + dp = get_node(op.op_nodeid, data); + + str = copyin_string(op.op_name, op.op_namelen); + if (IS_ERR(str)) + return PTR_ERR(str); + + pval = of_get_property(dp, str, &len); + err = 0; + if (!pval || len > op.op_buflen) { + err = -EINVAL; + } else { + op.op_buflen = len; + if (copy_to_user(argp, &op, sizeof(op)) || + copy_to_user(op.op_buf, pval, len)) + err = -EFAULT; + } + kfree(str); + + return err; +} + +static int opiocnextprop(void __user *argp, DATA *data) +{ + struct opiocdesc op; + struct device_node *dp; + struct property *prop; + char *str; + int len; + + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + dp = get_node(op.op_nodeid, data); + if (!dp) + return -EINVAL; + + str = copyin_string(op.op_name, op.op_namelen); + if (IS_ERR(str)) + return PTR_ERR(str); + + if (str[0] == '\0') { + prop = dp->properties; + } else { + prop = of_find_property(dp, str, NULL); + if (prop) + prop = prop->next; + } + kfree(str); + + if (!prop) + len = 0; + else + len = prop->length; + + if (len > op.op_buflen) + len = op.op_buflen; + + if (copy_to_user(argp, &op, sizeof(op))) + return -EFAULT; + + if (len && + copy_to_user(op.op_buf, prop->value, len)) + return -EFAULT; + + return 0; +} + +static int opiocset(void __user *argp, DATA *data) +{ + struct opiocdesc op; + struct device_node *dp; + char *str, *tmp; + int err; + + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + dp = get_node(op.op_nodeid, data); + if (!dp) + return -EINVAL; + + str = copyin_string(op.op_name, op.op_namelen); + if (IS_ERR(str)) + return PTR_ERR(str); + + tmp = copyin_string(op.op_buf, op.op_buflen); + if (IS_ERR(tmp)) { + kfree(str); + return PTR_ERR(tmp); + } + + err = of_set_property(dp, str, tmp, op.op_buflen); + + kfree(str); + kfree(tmp); + + return err; +} + +static int opiocgetnext(unsigned int cmd, void __user *argp) +{ + struct device_node *dp; + phandle nd; + + BUILD_BUG_ON(sizeof(phandle) != sizeof(int)); + + if (copy_from_user(&nd, argp, sizeof(phandle))) + return -EFAULT; + + if (nd == 0) { + if (cmd != OPIOCGETNEXT) + return -EINVAL; + dp = of_find_node_by_path("/"); + } else { + dp = of_find_node_by_phandle(nd); + nd = 0; + if (dp) { + if (cmd == OPIOCGETNEXT) + dp = dp->sibling; + else + dp = dp->child; + } + } + if (dp) + nd = dp->phandle; + if (copy_to_user(argp, &nd, sizeof(phandle))) + return -EFAULT; + + return 0; +} + +static int openprom_bsd_ioctl(struct file * file, + unsigned int cmd, unsigned long arg) +{ + DATA *data = file->private_data; + void __user *argp = (void __user *)arg; + int err; + + mutex_lock(&openprom_mutex); + switch (cmd) { + case OPIOCGET: + err = opiocget(argp, data); + break; + + case OPIOCNEXTPROP: + err = opiocnextprop(argp, data); + break; + + case OPIOCSET: + err = opiocset(argp, data); + break; + + case OPIOCGETOPTNODE: + BUILD_BUG_ON(sizeof(phandle) != sizeof(int)); + + err = 0; + if (copy_to_user(argp, &options_node->phandle, sizeof(phandle))) + err = -EFAULT; + break; + + case OPIOCGETNEXT: + case OPIOCGETCHILD: + err = opiocgetnext(cmd, argp); + break; + + default: + err = -EINVAL; + break; + } + mutex_unlock(&openprom_mutex); + + return err; +} + + +/* + * Handoff control to the correct ioctl handler. + */ +static long openprom_ioctl(struct file * file, + unsigned int cmd, unsigned long arg) +{ + DATA *data = file->private_data; + + switch (cmd) { + case OPROMGETOPT: + case OPROMNXTOPT: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, + options_node); + + case OPROMSETOPT: + case OPROMSETOPT2: + if ((file->f_mode & FMODE_WRITE) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, + options_node); + + case OPROMNEXT: + case OPROMCHILD: + case OPROMGETPROP: + case OPROMNXTPROP: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, + data->current_node); + + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + case OPROMGETBOOTARGS: + case OPROMSETCUR: + case OPROMPCI2NODE: + case OPROMPATH2NODE: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, NULL); + + case OPIOCGET: + case OPIOCNEXTPROP: + case OPIOCGETOPTNODE: + case OPIOCGETNEXT: + case OPIOCGETCHILD: + if ((file->f_mode & FMODE_READ) == 0) + return -EBADF; + return openprom_bsd_ioctl(file,cmd,arg); + + case OPIOCSET: + if ((file->f_mode & FMODE_WRITE) == 0) + return -EBADF; + return openprom_bsd_ioctl(file,cmd,arg); + + default: + return -EINVAL; + }; +} + +static long openprom_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long rval = -ENOTTY; + + /* + * SunOS/Solaris only, the NetBSD one's have embedded pointers in + * the arg which we'd need to clean up... + */ + switch (cmd) { + case OPROMGETOPT: + case OPROMSETOPT: + case OPROMNXTOPT: + case OPROMSETOPT2: + case OPROMNEXT: + case OPROMCHILD: + case OPROMGETPROP: + case OPROMNXTPROP: + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + case OPROMGETBOOTARGS: + case OPROMSETCUR: + case OPROMPCI2NODE: + case OPROMPATH2NODE: + rval = openprom_ioctl(file, cmd, arg); + break; + } + + return rval; +} + +static int openprom_open(struct inode * inode, struct file * file) +{ + DATA *data; + + data = kmalloc_obj(DATA); + if (!data) + return -ENOMEM; + + mutex_lock(&openprom_mutex); + data->current_node = of_find_node_by_path("/"); + data->lastnode = data->current_node; + file->private_data = (void *) data; + mutex_unlock(&openprom_mutex); + + return 0; +} + +static int openprom_release(struct inode * inode, struct file * file) +{ + kfree(file->private_data); + return 0; +} + +static const struct file_operations openprom_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = openprom_ioctl, + .compat_ioctl = openprom_compat_ioctl, + .open = openprom_open, + .release = openprom_release, +}; + +static struct miscdevice openprom_dev = { + .minor = SUN_OPENPROM_MINOR, + .name = "openprom", + .fops = &openprom_fops, +}; + +static int __init openprom_init(void) +{ + int err; + + err = misc_register(&openprom_dev); + if (err) + return err; + + options_node = of_get_child_by_name(of_find_node_by_path("/"), "options"); + if (!options_node) { + misc_deregister(&openprom_dev); + return -EIO; + } + + return 0; +} + +static void __exit openprom_cleanup(void) +{ + misc_deregister(&openprom_dev); +} + +module_init(openprom_init); +module_exit(openprom_cleanup); diff --git a/drivers/sbus/char/oradax.c b/drivers/sbus/char/oradax.c new file mode 100644 index 000000000..45df0b274 --- /dev/null +++ b/drivers/sbus/char/oradax.c @@ -0,0 +1,988 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * Oracle Data Analytics Accelerator (DAX) + * + * DAX is a coprocessor which resides on the SPARC M7 (DAX1) and M8 + * (DAX2) processor chips, and has direct access to the CPU's L3 + * caches as well as physical memory. It can perform several + * operations on data streams with various input and output formats. + * The driver provides a transport mechanism only and has limited + * knowledge of the various opcodes and data formats. A user space + * library provides high level services and translates these into low + * level commands which are then passed into the driver and + * subsequently the hypervisor and the coprocessor. The library is + * the recommended way for applications to use the coprocessor, and + * the driver interface is not intended for general use. + * + * See Documentation/arch/sparc/oradax/oracle-dax.rst for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for Oracle Data Analytics Accelerator"); + +#define DAX_DBG_FLG_BASIC 0x01 +#define DAX_DBG_FLG_STAT 0x02 +#define DAX_DBG_FLG_INFO 0x04 +#define DAX_DBG_FLG_ALL 0xff + +#define dax_err(fmt, ...) pr_err("%s: " fmt "\n", __func__, ##__VA_ARGS__) +#define dax_info(fmt, ...) pr_info("%s: " fmt "\n", __func__, ##__VA_ARGS__) + +#define dax_dbg(fmt, ...) do { \ + if (dax_debug & DAX_DBG_FLG_BASIC)\ + dax_info(fmt, ##__VA_ARGS__); \ + } while (0) +#define dax_stat_dbg(fmt, ...) do { \ + if (dax_debug & DAX_DBG_FLG_STAT) \ + dax_info(fmt, ##__VA_ARGS__); \ + } while (0) +#define dax_info_dbg(fmt, ...) do { \ + if (dax_debug & DAX_DBG_FLG_INFO) \ + dax_info(fmt, ##__VA_ARGS__); \ + } while (0) + +#define DAX1_MINOR 1 +#define DAX1_MAJOR 1 +#define DAX2_MINOR 0 +#define DAX2_MAJOR 2 + +#define DAX1_STR "ORCL,sun4v-dax" +#define DAX2_STR "ORCL,sun4v-dax2" + +#define DAX_CA_ELEMS (DAX_MMAP_LEN / sizeof(struct dax_cca)) + +#define DAX_CCB_USEC 100 +#define DAX_CCB_RETRIES 10000 + +/* stream types */ +enum { + OUT, + PRI, + SEC, + TBL, + NUM_STREAM_TYPES +}; + +/* completion status */ +#define CCA_STAT_NOT_COMPLETED 0 +#define CCA_STAT_COMPLETED 1 +#define CCA_STAT_FAILED 2 +#define CCA_STAT_KILLED 3 +#define CCA_STAT_NOT_RUN 4 +#define CCA_STAT_PIPE_OUT 5 +#define CCA_STAT_PIPE_SRC 6 +#define CCA_STAT_PIPE_DST 7 + +/* completion err */ +#define CCA_ERR_SUCCESS 0x0 /* no error */ +#define CCA_ERR_OVERFLOW 0x1 /* buffer overflow */ +#define CCA_ERR_DECODE 0x2 /* CCB decode error */ +#define CCA_ERR_PAGE_OVERFLOW 0x3 /* page overflow */ +#define CCA_ERR_KILLED 0x7 /* command was killed */ +#define CCA_ERR_TIMEOUT 0x8 /* Timeout */ +#define CCA_ERR_ADI 0x9 /* ADI error */ +#define CCA_ERR_DATA_FMT 0xA /* data format error */ +#define CCA_ERR_OTHER_NO_RETRY 0xE /* Other error, do not retry */ +#define CCA_ERR_OTHER_RETRY 0xF /* Other error, retry */ +#define CCA_ERR_PARTIAL_SYMBOL 0x80 /* QP partial symbol warning */ + +/* CCB address types */ +#define DAX_ADDR_TYPE_NONE 0 +#define DAX_ADDR_TYPE_VA_ALT 1 /* secondary context */ +#define DAX_ADDR_TYPE_RA 2 /* real address */ +#define DAX_ADDR_TYPE_VA 3 /* virtual address */ + +/* dax_header_t opcode */ +#define DAX_OP_SYNC_NOP 0x0 +#define DAX_OP_EXTRACT 0x1 +#define DAX_OP_SCAN_VALUE 0x2 +#define DAX_OP_SCAN_RANGE 0x3 +#define DAX_OP_TRANSLATE 0x4 +#define DAX_OP_SELECT 0x5 +#define DAX_OP_INVERT 0x10 /* OR with translate, scan opcodes */ + +struct dax_header { + u32 ccb_version:4; /* 31:28 CCB Version */ + /* 27:24 Sync Flags */ + u32 pipe:1; /* Pipeline */ + u32 longccb:1; /* Longccb. Set for scan with lu2, lu3, lu4. */ + u32 cond:1; /* Conditional */ + u32 serial:1; /* Serial */ + u32 opcode:8; /* 23:16 Opcode */ + /* 15:0 Address Type. */ + u32 reserved:3; /* 15:13 reserved */ + u32 table_addr_type:2; /* 12:11 Huffman Table Address Type */ + u32 out_addr_type:3; /* 10:8 Destination Address Type */ + u32 sec_addr_type:3; /* 7:5 Secondary Source Address Type */ + u32 pri_addr_type:3; /* 4:2 Primary Source Address Type */ + u32 cca_addr_type:2; /* 1:0 Completion Address Type */ +}; + +struct dax_control { + u32 pri_fmt:4; /* 31:28 Primary Input Format */ + u32 pri_elem_size:5; /* 27:23 Primary Input Element Size(less1) */ + u32 pri_offset:3; /* 22:20 Primary Input Starting Offset */ + u32 sec_encoding:1; /* 19 Secondary Input Encoding */ + /* (must be 0 for Select) */ + u32 sec_offset:3; /* 18:16 Secondary Input Starting Offset */ + u32 sec_elem_size:2; /* 15:14 Secondary Input Element Size */ + /* (must be 0 for Select) */ + u32 out_fmt:2; /* 13:12 Output Format */ + u32 out_elem_size:2; /* 11:10 Output Element Size */ + u32 misc:10; /* 9:0 Opcode specific info */ +}; + +struct dax_data_access { + u64 flow_ctrl:2; /* 63:62 Flow Control Type */ + u64 pipe_target:2; /* 61:60 Pipeline Target */ + u64 out_buf_size:20; /* 59:40 Output Buffer Size */ + /* (cachelines less 1) */ + u64 unused1:8; /* 39:32 Reserved, Set to 0 */ + u64 out_alloc:5; /* 31:27 Output Allocation */ + u64 unused2:1; /* 26 Reserved */ + u64 pri_len_fmt:2; /* 25:24 Input Length Format */ + u64 pri_len:24; /* 23:0 Input Element/Byte/Bit Count */ + /* (less 1) */ +}; + +struct dax_ccb { + struct dax_header hdr; /* CCB Header */ + struct dax_control ctrl;/* Control Word */ + void *ca; /* Completion Address */ + void *pri; /* Primary Input Address */ + struct dax_data_access dac; /* Data Access Control */ + void *sec; /* Secondary Input Address */ + u64 dword5; /* depends on opcode */ + void *out; /* Output Address */ + void *tbl; /* Table Address or bitmap */ +}; + +struct dax_cca { + u8 status; /* user may mwait on this address */ + u8 err; /* user visible error notification */ + u8 rsvd[2]; /* reserved */ + u32 n_remaining; /* for QP partial symbol warning */ + u32 output_sz; /* output in bytes */ + u32 rsvd2; /* reserved */ + u64 run_cycles; /* run time in OCND2 cycles */ + u64 run_stats; /* nothing reported in version 1.0 */ + u32 n_processed; /* number input elements */ + u32 rsvd3[5]; /* reserved */ + u64 retval; /* command return value */ + u64 rsvd4[8]; /* reserved */ +}; + +/* per thread CCB context */ +struct dax_ctx { + struct dax_ccb *ccb_buf; + u64 ccb_buf_ra; /* cached RA of ccb_buf */ + struct dax_cca *ca_buf; + u64 ca_buf_ra; /* cached RA of ca_buf */ + struct page *pages[DAX_CA_ELEMS][NUM_STREAM_TYPES]; + /* array of locked pages */ + struct task_struct *owner; /* thread that owns ctx */ + struct task_struct *client; /* requesting thread */ + union ccb_result result; + u32 ccb_count; + u32 fail_count; +}; + +/* driver public entry points */ +static int dax_open(struct inode *inode, struct file *file); +static ssize_t dax_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos); +static ssize_t dax_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos); +static int dax_devmap(struct file *f, struct vm_area_struct *vma); +static int dax_close(struct inode *i, struct file *f); + +static const struct file_operations dax_fops = { + .owner = THIS_MODULE, + .open = dax_open, + .read = dax_read, + .write = dax_write, + .mmap = dax_devmap, + .release = dax_close, +}; + +static int dax_ccb_exec(struct dax_ctx *ctx, const char __user *buf, + size_t count, loff_t *ppos); +static int dax_ccb_info(u64 ca, struct ccb_info_result *info); +static int dax_ccb_kill(u64 ca, u16 *kill_res); + +static struct cdev c_dev; +static dev_t first; +static const struct class cl = { + .name = DAX_NAME, +}; + +static int max_ccb_version; +static int dax_debug; +module_param(dax_debug, int, 0644); +MODULE_PARM_DESC(dax_debug, "Debug flags"); + +static int __init dax_attach(void) +{ + unsigned long dummy, hv_rv, major, minor, minor_requested, max_ccbs; + struct mdesc_handle *hp = mdesc_grab(); + char *prop, *dax_name; + bool found = false; + int len, ret = 0; + u64 pn; + + if (hp == NULL) { + dax_err("Unable to grab mdesc"); + return -ENODEV; + } + + mdesc_for_each_node_by_name(hp, pn, "virtual-device") { + prop = (char *)mdesc_get_property(hp, pn, "name", &len); + if (prop == NULL) + continue; + if (strncmp(prop, "dax", strlen("dax"))) + continue; + dax_dbg("Found node 0x%llx = %s", pn, prop); + + prop = (char *)mdesc_get_property(hp, pn, "compatible", &len); + if (prop == NULL) + continue; + dax_dbg("Found node 0x%llx = %s", pn, prop); + found = true; + break; + } + + if (!found) { + dax_err("No DAX device found"); + ret = -ENODEV; + goto done; + } + + if (strncmp(prop, DAX2_STR, strlen(DAX2_STR)) == 0) { + dax_name = DAX_NAME "2"; + major = DAX2_MAJOR; + minor_requested = DAX2_MINOR; + max_ccb_version = 1; + dax_dbg("MD indicates DAX2 coprocessor"); + } else if (strncmp(prop, DAX1_STR, strlen(DAX1_STR)) == 0) { + dax_name = DAX_NAME "1"; + major = DAX1_MAJOR; + minor_requested = DAX1_MINOR; + max_ccb_version = 0; + dax_dbg("MD indicates DAX1 coprocessor"); + } else { + dax_err("Unknown dax type: %s", prop); + ret = -ENODEV; + goto done; + } + + minor = minor_requested; + dax_dbg("Registering DAX HV api with major %ld minor %ld", major, + minor); + if (sun4v_hvapi_register(HV_GRP_DAX, major, &minor)) { + dax_err("hvapi_register failed"); + ret = -ENODEV; + goto done; + } else { + dax_dbg("Max minor supported by HV = %ld (major %ld)", minor, + major); + minor = min(minor, minor_requested); + dax_dbg("registered DAX major %ld minor %ld", major, minor); + } + + /* submit a zero length ccb array to query coprocessor queue size */ + hv_rv = sun4v_ccb_submit(0, 0, HV_CCB_QUERY_CMD, 0, &max_ccbs, &dummy); + if (hv_rv != 0) { + dax_err("get_hwqueue_size failed with status=%ld and max_ccbs=%ld", + hv_rv, max_ccbs); + ret = -ENODEV; + goto done; + } + + if (max_ccbs != DAX_MAX_CCBS) { + dax_err("HV reports unsupported max_ccbs=%ld", max_ccbs); + ret = -ENODEV; + goto done; + } + + if (alloc_chrdev_region(&first, 0, 1, DAX_NAME) < 0) { + dax_err("alloc_chrdev_region failed"); + ret = -ENXIO; + goto done; + } + + ret = class_register(&cl); + if (ret) + goto class_error; + + if (device_create(&cl, NULL, first, NULL, dax_name) == NULL) { + dax_err("device_create failed"); + ret = -ENXIO; + goto device_error; + } + + cdev_init(&c_dev, &dax_fops); + if (cdev_add(&c_dev, first, 1) == -1) { + dax_err("cdev_add failed"); + ret = -ENXIO; + goto cdev_error; + } + + pr_info("Attached DAX module\n"); + goto done; + +cdev_error: + device_destroy(&cl, first); +device_error: + class_unregister(&cl); +class_error: + unregister_chrdev_region(first, 1); +done: + mdesc_release(hp); + return ret; +} +module_init(dax_attach); + +static void __exit dax_detach(void) +{ + pr_info("Cleaning up DAX module\n"); + cdev_del(&c_dev); + device_destroy(&cl, first); + class_unregister(&cl); + unregister_chrdev_region(first, 1); +} +module_exit(dax_detach); + +/* map completion area */ +static int dax_devmap(struct file *f, struct vm_area_struct *vma) +{ + struct dax_ctx *ctx = (struct dax_ctx *)f->private_data; + size_t len = vma->vm_end - vma->vm_start; + + dax_dbg("len=0x%lx, flags=0x%lx", len, vma->vm_flags); + + if (ctx->owner != current) { + dax_dbg("devmap called from wrong thread"); + return -EINVAL; + } + + if (len != DAX_MMAP_LEN) { + dax_dbg("len(%lu) != DAX_MMAP_LEN(%d)", len, DAX_MMAP_LEN); + return -EINVAL; + } + + /* completion area is mapped read-only for user */ + if (vma->vm_flags & VM_WRITE) + return -EPERM; + vm_flags_clear(vma, VM_MAYWRITE); + + if (remap_pfn_range(vma, vma->vm_start, ctx->ca_buf_ra >> PAGE_SHIFT, + len, vma->vm_page_prot)) + return -EAGAIN; + + dax_dbg("mmapped completion area at uva 0x%lx", vma->vm_start); + return 0; +} + +/* Unlock user pages. Called during dequeue or device close */ +static void dax_unlock_pages(struct dax_ctx *ctx, int ccb_index, int nelem) +{ + int i, j; + + for (i = ccb_index; i < ccb_index + nelem; i++) { + for (j = 0; j < NUM_STREAM_TYPES; j++) { + struct page *p = ctx->pages[i][j]; + + if (p) { + dax_dbg("freeing page %p", p); + unpin_user_pages_dirty_lock(&p, 1, j == OUT); + ctx->pages[i][j] = NULL; + } + } + } +} + +static int dax_lock_page(void *va, struct page **p) +{ + int ret; + + dax_dbg("uva %p", va); + + ret = pin_user_pages_fast((unsigned long)va, 1, FOLL_WRITE, p); + if (ret == 1) { + dax_dbg("locked page %p, for VA %p", *p, va); + return 0; + } + + dax_dbg("pin_user_pages failed, va=%p, ret=%d", va, ret); + return -1; +} + +static int dax_lock_pages(struct dax_ctx *ctx, int idx, + int nelem, u64 *err_va) +{ + int i; + + for (i = 0; i < nelem; i++) { + struct dax_ccb *ccbp = &ctx->ccb_buf[i]; + + /* + * For each address in the CCB whose type is virtual, + * lock the page and change the type to virtual alternate + * context. On error, return the offending address in + * err_va. + */ + if (ccbp->hdr.out_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("output"); + if (dax_lock_page(ccbp->out, + &ctx->pages[i + idx][OUT]) != 0) { + *err_va = (u64)ccbp->out; + goto error; + } + ccbp->hdr.out_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + if (ccbp->hdr.pri_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("input"); + if (dax_lock_page(ccbp->pri, + &ctx->pages[i + idx][PRI]) != 0) { + *err_va = (u64)ccbp->pri; + goto error; + } + ccbp->hdr.pri_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + if (ccbp->hdr.sec_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("sec input"); + if (dax_lock_page(ccbp->sec, + &ctx->pages[i + idx][SEC]) != 0) { + *err_va = (u64)ccbp->sec; + goto error; + } + ccbp->hdr.sec_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + if (ccbp->hdr.table_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("tbl"); + if (dax_lock_page(ccbp->tbl, + &ctx->pages[i + idx][TBL]) != 0) { + *err_va = (u64)ccbp->tbl; + goto error; + } + ccbp->hdr.table_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + /* skip over 2nd 64 bytes of long CCB */ + if (ccbp->hdr.longccb) + i++; + } + return DAX_SUBMIT_OK; + +error: + dax_unlock_pages(ctx, idx, nelem); + return DAX_SUBMIT_ERR_NOACCESS; +} + +static void dax_ccb_wait(struct dax_ctx *ctx, int idx) +{ + int ret, nretries; + u16 kill_res; + + dax_dbg("idx=%d", idx); + + for (nretries = 0; nretries < DAX_CCB_RETRIES; nretries++) { + if (ctx->ca_buf[idx].status == CCA_STAT_NOT_COMPLETED) + udelay(DAX_CCB_USEC); + else + return; + } + dax_dbg("ctx (%p): CCB[%d] timed out, wait usec=%d, retries=%d. Killing ccb", + (void *)ctx, idx, DAX_CCB_USEC, DAX_CCB_RETRIES); + + ret = dax_ccb_kill(ctx->ca_buf_ra + idx * sizeof(struct dax_cca), + &kill_res); + dax_dbg("Kill CCB[%d] %s", idx, ret ? "failed" : "succeeded"); +} + +static int dax_close(struct inode *ino, struct file *f) +{ + struct dax_ctx *ctx = (struct dax_ctx *)f->private_data; + int i; + + f->private_data = NULL; + + for (i = 0; i < DAX_CA_ELEMS; i++) { + if (ctx->ca_buf[i].status == CCA_STAT_NOT_COMPLETED) { + dax_dbg("CCB[%d] not completed", i); + dax_ccb_wait(ctx, i); + } + dax_unlock_pages(ctx, i, 1); + } + + kfree(ctx->ccb_buf); + kfree(ctx->ca_buf); + dax_stat_dbg("CCBs: %d good, %d bad", ctx->ccb_count, ctx->fail_count); + kfree(ctx); + + return 0; +} + +static ssize_t dax_read(struct file *f, char __user *buf, + size_t count, loff_t *ppos) +{ + struct dax_ctx *ctx = f->private_data; + + if (ctx->client != current) + return -EUSERS; + + ctx->client = NULL; + + if (count != sizeof(union ccb_result)) + return -EINVAL; + if (copy_to_user(buf, &ctx->result, sizeof(union ccb_result))) + return -EFAULT; + return count; +} + +static ssize_t dax_write(struct file *f, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dax_ctx *ctx = f->private_data; + struct dax_command hdr; + unsigned long ca; + int i, idx, ret; + + if (ctx->client != NULL) + return -EINVAL; + + if (count == 0 || count > DAX_MAX_CCBS * sizeof(struct dax_ccb)) + return -EINVAL; + + if (count % sizeof(struct dax_ccb) == 0) + return dax_ccb_exec(ctx, buf, count, ppos); /* CCB EXEC */ + + if (count != sizeof(struct dax_command)) + return -EINVAL; + + /* immediate command */ + if (ctx->owner != current) + return -EUSERS; + + if (copy_from_user(&hdr, buf, sizeof(hdr))) + return -EFAULT; + + ca = ctx->ca_buf_ra + hdr.ca_offset; + + switch (hdr.command) { + case CCB_KILL: + if (hdr.ca_offset >= DAX_MMAP_LEN) { + dax_dbg("invalid ca_offset (%d) >= ca_buflen (%d)", + hdr.ca_offset, DAX_MMAP_LEN); + return -EINVAL; + } + + ret = dax_ccb_kill(ca, &ctx->result.kill.action); + if (ret != 0) { + dax_dbg("dax_ccb_kill failed (ret=%d)", ret); + return ret; + } + + dax_info_dbg("killed (ca_offset %d)", hdr.ca_offset); + idx = hdr.ca_offset / sizeof(struct dax_cca); + ctx->ca_buf[idx].status = CCA_STAT_KILLED; + ctx->ca_buf[idx].err = CCA_ERR_KILLED; + ctx->client = current; + return count; + + case CCB_INFO: + if (hdr.ca_offset >= DAX_MMAP_LEN) { + dax_dbg("invalid ca_offset (%d) >= ca_buflen (%d)", + hdr.ca_offset, DAX_MMAP_LEN); + return -EINVAL; + } + + ret = dax_ccb_info(ca, &ctx->result.info); + if (ret != 0) { + dax_dbg("dax_ccb_info failed (ret=%d)", ret); + return ret; + } + + dax_info_dbg("info succeeded on ca_offset %d", hdr.ca_offset); + ctx->client = current; + return count; + + case CCB_DEQUEUE: + for (i = 0; i < DAX_CA_ELEMS; i++) { + if (ctx->ca_buf[i].status != + CCA_STAT_NOT_COMPLETED) + dax_unlock_pages(ctx, i, 1); + } + return count; + + default: + return -EINVAL; + } +} + +static int dax_open(struct inode *inode, struct file *f) +{ + struct dax_ctx *ctx = NULL; + int i; + + ctx = kzalloc_obj(*ctx); + if (ctx == NULL) + goto done; + + ctx->ccb_buf = kzalloc_objs(struct dax_ccb, DAX_MAX_CCBS); + if (ctx->ccb_buf == NULL) + goto done; + + ctx->ccb_buf_ra = virt_to_phys(ctx->ccb_buf); + dax_dbg("ctx->ccb_buf=0x%p, ccb_buf_ra=0x%llx", + (void *)ctx->ccb_buf, ctx->ccb_buf_ra); + + /* allocate CCB completion area buffer */ + ctx->ca_buf = kzalloc(DAX_MMAP_LEN, GFP_KERNEL); + if (ctx->ca_buf == NULL) + goto alloc_error; + for (i = 0; i < DAX_CA_ELEMS; i++) + ctx->ca_buf[i].status = CCA_STAT_COMPLETED; + + ctx->ca_buf_ra = virt_to_phys(ctx->ca_buf); + dax_dbg("ctx=0x%p, ctx->ca_buf=0x%p, ca_buf_ra=0x%llx", + (void *)ctx, (void *)ctx->ca_buf, ctx->ca_buf_ra); + + ctx->owner = current; + f->private_data = ctx; + return 0; + +alloc_error: + kfree(ctx->ccb_buf); +done: + kfree(ctx); + return -ENOMEM; +} + +static char *dax_hv_errno(unsigned long hv_ret, int *ret) +{ + switch (hv_ret) { + case HV_EBADALIGN: + *ret = -EFAULT; + return "HV_EBADALIGN"; + case HV_ENORADDR: + *ret = -EFAULT; + return "HV_ENORADDR"; + case HV_EINVAL: + *ret = -EINVAL; + return "HV_EINVAL"; + case HV_EWOULDBLOCK: + *ret = -EAGAIN; + return "HV_EWOULDBLOCK"; + case HV_ENOACCESS: + *ret = -EPERM; + return "HV_ENOACCESS"; + default: + break; + } + + *ret = -EIO; + return "UNKNOWN"; +} + +static int dax_ccb_kill(u64 ca, u16 *kill_res) +{ + unsigned long hv_ret; + int count, ret = 0; + char *err_str; + + for (count = 0; count < DAX_CCB_RETRIES; count++) { + dax_dbg("attempting kill on ca_ra 0x%llx", ca); + hv_ret = sun4v_ccb_kill(ca, kill_res); + + if (hv_ret == HV_EOK) { + dax_info_dbg("HV_EOK (ca_ra 0x%llx): %d", ca, + *kill_res); + } else { + err_str = dax_hv_errno(hv_ret, &ret); + dax_dbg("%s (ca_ra 0x%llx)", err_str, ca); + } + + if (ret != -EAGAIN) + return ret; + dax_info_dbg("ccb_kill count = %d", count); + udelay(DAX_CCB_USEC); + } + + return -EAGAIN; +} + +static int dax_ccb_info(u64 ca, struct ccb_info_result *info) +{ + unsigned long hv_ret; + char *err_str; + int ret = 0; + + dax_dbg("attempting info on ca_ra 0x%llx", ca); + hv_ret = sun4v_ccb_info(ca, info); + + if (hv_ret == HV_EOK) { + dax_info_dbg("HV_EOK (ca_ra 0x%llx): %d", ca, info->state); + if (info->state == DAX_CCB_ENQUEUED) { + dax_info_dbg("dax_unit %d, queue_num %d, queue_pos %d", + info->inst_num, info->q_num, info->q_pos); + } + } else { + err_str = dax_hv_errno(hv_ret, &ret); + dax_dbg("%s (ca_ra 0x%llx)", err_str, ca); + } + + return ret; +} + +static void dax_prt_ccbs(struct dax_ccb *ccb, int nelem) +{ + int i, j; + u64 *ccbp; + + dax_dbg("ccb buffer:"); + for (i = 0; i < nelem; i++) { + ccbp = (u64 *)&ccb[i]; + dax_dbg(" %sccb[%d]", ccb[i].hdr.longccb ? "long " : "", i); + for (j = 0; j < 8; j++) + dax_dbg("\tccb[%d].dwords[%d]=0x%llx", + i, j, *(ccbp + j)); + } +} + +/* + * Validates user CCB content. Also sets completion address and address types + * for all addresses contained in CCB. + */ +static int dax_preprocess_usr_ccbs(struct dax_ctx *ctx, int idx, int nelem) +{ + int i; + + /* + * The user is not allowed to specify real address types in + * the CCB header. This must be enforced by the kernel before + * submitting the CCBs to HV. The only allowed values for all + * address fields are VA or IMM + */ + for (i = 0; i < nelem; i++) { + struct dax_ccb *ccbp = &ctx->ccb_buf[i]; + unsigned long ca_offset; + + if (ccbp->hdr.ccb_version > max_ccb_version) + return DAX_SUBMIT_ERR_CCB_INVAL; + + switch (ccbp->hdr.opcode) { + case DAX_OP_SYNC_NOP: + case DAX_OP_EXTRACT: + case DAX_OP_SCAN_VALUE: + case DAX_OP_SCAN_RANGE: + case DAX_OP_TRANSLATE: + case DAX_OP_SCAN_VALUE | DAX_OP_INVERT: + case DAX_OP_SCAN_RANGE | DAX_OP_INVERT: + case DAX_OP_TRANSLATE | DAX_OP_INVERT: + case DAX_OP_SELECT: + break; + default: + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.out_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.out_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid out_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.pri_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.pri_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid pri_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.sec_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.sec_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid sec_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.table_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.table_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid table_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + /* set completion (real) address and address type */ + ccbp->hdr.cca_addr_type = DAX_ADDR_TYPE_RA; + ca_offset = (idx + i) * sizeof(struct dax_cca); + ccbp->ca = (void *)ctx->ca_buf_ra + ca_offset; + memset(&ctx->ca_buf[idx + i], 0, sizeof(struct dax_cca)); + + dax_dbg("ccb[%d]=%p, ca_offset=0x%lx, compl RA=0x%llx", + i, ccbp, ca_offset, ctx->ca_buf_ra + ca_offset); + + /* skip over 2nd 64 bytes of long CCB */ + if (ccbp->hdr.longccb) + i++; + } + + return DAX_SUBMIT_OK; +} + +static int dax_ccb_exec(struct dax_ctx *ctx, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long accepted_len, hv_rv; + int i, idx, nccbs, naccepted; + + ctx->client = current; + idx = *ppos; + nccbs = count / sizeof(struct dax_ccb); + + if (ctx->owner != current) { + dax_dbg("wrong thread"); + ctx->result.exec.status = DAX_SUBMIT_ERR_THR_INIT; + return 0; + } + dax_dbg("args: ccb_buf_len=%ld, idx=%d", count, idx); + + /* for given index and length, verify ca_buf range exists */ + if (idx < 0 || idx > (DAX_CA_ELEMS - nccbs)) { + ctx->result.exec.status = DAX_SUBMIT_ERR_NO_CA_AVAIL; + return 0; + } + + /* + * Copy CCBs into kernel buffer to prevent modification by the + * user in between validation and submission. + */ + if (copy_from_user(ctx->ccb_buf, buf, count)) { + dax_dbg("copyin of user CCB buffer failed"); + ctx->result.exec.status = DAX_SUBMIT_ERR_CCB_ARR_MMU_MISS; + return 0; + } + + /* check to see if ca_buf[idx] .. ca_buf[idx + nccbs] are available */ + for (i = idx; i < idx + nccbs; i++) { + if (ctx->ca_buf[i].status == CCA_STAT_NOT_COMPLETED) { + dax_dbg("CA range not available, dequeue needed"); + ctx->result.exec.status = DAX_SUBMIT_ERR_NO_CA_AVAIL; + return 0; + } + } + dax_unlock_pages(ctx, idx, nccbs); + + ctx->result.exec.status = dax_preprocess_usr_ccbs(ctx, idx, nccbs); + if (ctx->result.exec.status != DAX_SUBMIT_OK) + return 0; + + ctx->result.exec.status = dax_lock_pages(ctx, idx, nccbs, + &ctx->result.exec.status_data); + if (ctx->result.exec.status != DAX_SUBMIT_OK) + return 0; + + if (dax_debug & DAX_DBG_FLG_BASIC) + dax_prt_ccbs(ctx->ccb_buf, nccbs); + + hv_rv = sun4v_ccb_submit(ctx->ccb_buf_ra, count, + HV_CCB_QUERY_CMD | HV_CCB_VA_SECONDARY, 0, + &accepted_len, &ctx->result.exec.status_data); + + switch (hv_rv) { + case HV_EOK: + /* + * Hcall succeeded with no errors but the accepted + * length may be less than the requested length. The + * only way the driver can resubmit the remainder is + * to wait for completion of the submitted CCBs since + * there is no way to guarantee the ordering semantics + * required by the client applications. Therefore we + * let the user library deal with resubmissions. + */ + ctx->result.exec.status = DAX_SUBMIT_OK; + break; + case HV_EWOULDBLOCK: + /* + * This is a transient HV API error. The user library + * can retry. + */ + dax_dbg("hcall returned HV_EWOULDBLOCK"); + ctx->result.exec.status = DAX_SUBMIT_ERR_WOULDBLOCK; + break; + case HV_ENOMAP: + /* + * HV was unable to translate a VA. The VA it could + * not translate is returned in the status_data param. + */ + dax_dbg("hcall returned HV_ENOMAP"); + ctx->result.exec.status = DAX_SUBMIT_ERR_NOMAP; + break; + case HV_EINVAL: + /* + * This is the result of an invalid user CCB as HV is + * validating some of the user CCB fields. Pass this + * error back to the user. There is no supporting info + * to isolate the invalid field. + */ + dax_dbg("hcall returned HV_EINVAL"); + ctx->result.exec.status = DAX_SUBMIT_ERR_CCB_INVAL; + break; + case HV_ENOACCESS: + /* + * HV found a VA that did not have the appropriate + * permissions (such as the w bit). The VA in question + * is returned in status_data param. + */ + dax_dbg("hcall returned HV_ENOACCESS"); + ctx->result.exec.status = DAX_SUBMIT_ERR_NOACCESS; + break; + case HV_EUNAVAILABLE: + /* + * The requested CCB operation could not be performed + * at this time. Return the specific unavailable code + * in the status_data field. + */ + dax_dbg("hcall returned HV_EUNAVAILABLE"); + ctx->result.exec.status = DAX_SUBMIT_ERR_UNAVAIL; + break; + default: + ctx->result.exec.status = DAX_SUBMIT_ERR_INTERNAL; + dax_dbg("unknown hcall return value (%ld)", hv_rv); + break; + } + + /* unlock pages associated with the unaccepted CCBs */ + naccepted = accepted_len / sizeof(struct dax_ccb); + dax_unlock_pages(ctx, idx + naccepted, nccbs - naccepted); + + /* mark unaccepted CCBs as not completed */ + for (i = idx + naccepted; i < idx + nccbs; i++) + ctx->ca_buf[i].status = CCA_STAT_COMPLETED; + + ctx->ccb_count += naccepted; + ctx->fail_count += nccbs - naccepted; + + dax_dbg("hcall rv=%ld, accepted_len=%ld, status_data=0x%llx, ret status=%d", + hv_rv, accepted_len, ctx->result.exec.status_data, + ctx->result.exec.status); + + if (count == accepted_len) + ctx->client = NULL; /* no read needed to complete protocol */ + return accepted_len; +} diff --git a/drivers/sbus/char/uctrl.c b/drivers/sbus/char/uctrl.c new file mode 100644 index 000000000..85aaec587 --- /dev/null +++ b/drivers/sbus/char/uctrl.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3 + * + * Copyright 1999 Derrick J Brashear (shadow@dementia.org) + * Copyright 2008 David S. Miller (davem@davemloft.net) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define DEBUG 1 +#ifdef DEBUG +#define dprintk(x) printk x +#else +#define dprintk(x) +#endif + +struct uctrl_regs { + u32 uctrl_intr; + u32 uctrl_data; + u32 uctrl_stat; + u32 uctrl_xxx[5]; +}; + +struct ts102_regs { + u32 card_a_intr; + u32 card_a_stat; + u32 card_a_ctrl; + u32 card_a_xxx; + u32 card_b_intr; + u32 card_b_stat; + u32 card_b_ctrl; + u32 card_b_xxx; + u32 uctrl_intr; + u32 uctrl_data; + u32 uctrl_stat; + u32 uctrl_xxx; + u32 ts102_xxx[4]; +}; + +/* Bits for uctrl_intr register */ +#define UCTRL_INTR_TXE_REQ 0x01 /* transmit FIFO empty int req */ +#define UCTRL_INTR_TXNF_REQ 0x02 /* transmit FIFO not full int req */ +#define UCTRL_INTR_RXNE_REQ 0x04 /* receive FIFO not empty int req */ +#define UCTRL_INTR_RXO_REQ 0x08 /* receive FIFO overflow int req */ +#define UCTRL_INTR_TXE_MSK 0x10 /* transmit FIFO empty mask */ +#define UCTRL_INTR_TXNF_MSK 0x20 /* transmit FIFO not full mask */ +#define UCTRL_INTR_RXNE_MSK 0x40 /* receive FIFO not empty mask */ +#define UCTRL_INTR_RXO_MSK 0x80 /* receive FIFO overflow mask */ + +/* Bits for uctrl_stat register */ +#define UCTRL_STAT_TXE_STA 0x01 /* transmit FIFO empty status */ +#define UCTRL_STAT_TXNF_STA 0x02 /* transmit FIFO not full status */ +#define UCTRL_STAT_RXNE_STA 0x04 /* receive FIFO not empty status */ +#define UCTRL_STAT_RXO_STA 0x08 /* receive FIFO overflow status */ + +static DEFINE_MUTEX(uctrl_mutex); +static const char *uctrl_extstatus[16] = { + "main power available", + "internal battery attached", + "external battery attached", + "external VGA attached", + "external keyboard attached", + "external mouse attached", + "lid down", + "internal battery currently charging", + "external battery currently charging", + "internal battery currently discharging", + "external battery currently discharging", +}; + +/* Everything required for one transaction with the uctrl */ +struct uctrl_txn { + u8 opcode; + u8 inbits; + u8 outbits; + u8 *inbuf; + u8 *outbuf; +}; + +struct uctrl_status { + u8 current_temp; /* 0x07 */ + u8 reset_status; /* 0x0b */ + u16 event_status; /* 0x0c */ + u16 error_status; /* 0x10 */ + u16 external_status; /* 0x11, 0x1b */ + u8 internal_charge; /* 0x18 */ + u8 external_charge; /* 0x19 */ + u16 control_lcd; /* 0x20 */ + u8 control_bitport; /* 0x21 */ + u8 speaker_volume; /* 0x23 */ + u8 control_tft_brightness; /* 0x24 */ + u8 control_kbd_repeat_delay; /* 0x28 */ + u8 control_kbd_repeat_period; /* 0x29 */ + u8 control_screen_contrast; /* 0x2F */ +}; + +enum uctrl_opcode { + READ_SERIAL_NUMBER=0x1, + READ_ETHERNET_ADDRESS=0x2, + READ_HARDWARE_VERSION=0x3, + READ_MICROCONTROLLER_VERSION=0x4, + READ_MAX_TEMPERATURE=0x5, + READ_MIN_TEMPERATURE=0x6, + READ_CURRENT_TEMPERATURE=0x7, + READ_SYSTEM_VARIANT=0x8, + READ_POWERON_CYCLES=0x9, + READ_POWERON_SECONDS=0xA, + READ_RESET_STATUS=0xB, + READ_EVENT_STATUS=0xC, + READ_REAL_TIME_CLOCK=0xD, + READ_EXTERNAL_VGA_PORT=0xE, + READ_MICROCONTROLLER_ROM_CHECKSUM=0xF, + READ_ERROR_STATUS=0x10, + READ_EXTERNAL_STATUS=0x11, + READ_USER_CONFIGURATION_AREA=0x12, + READ_MICROCONTROLLER_VOLTAGE=0x13, + READ_INTERNAL_BATTERY_VOLTAGE=0x14, + READ_DCIN_VOLTAGE=0x15, + READ_HORIZONTAL_POINTER_VOLTAGE=0x16, + READ_VERTICAL_POINTER_VOLTAGE=0x17, + READ_INTERNAL_BATTERY_CHARGE_LEVEL=0x18, + READ_EXTERNAL_BATTERY_CHARGE_LEVEL=0x19, + READ_REAL_TIME_CLOCK_ALARM=0x1A, + READ_EVENT_STATUS_NO_RESET=0x1B, + READ_INTERNAL_KEYBOARD_LAYOUT=0x1C, + READ_EXTERNAL_KEYBOARD_LAYOUT=0x1D, + READ_EEPROM_STATUS=0x1E, + CONTROL_LCD=0x20, + CONTROL_BITPORT=0x21, + SPEAKER_VOLUME=0x23, + CONTROL_TFT_BRIGHTNESS=0x24, + CONTROL_WATCHDOG=0x25, + CONTROL_FACTORY_EEPROM_AREA=0x26, + CONTROL_KBD_TIME_UNTIL_REPEAT=0x28, + CONTROL_KBD_TIME_BETWEEN_REPEATS=0x29, + CONTROL_TIMEZONE=0x2A, + CONTROL_MARK_SPACE_RATIO=0x2B, + CONTROL_DIAGNOSTIC_MODE=0x2E, + CONTROL_SCREEN_CONTRAST=0x2F, + RING_BELL=0x30, + SET_DIAGNOSTIC_STATUS=0x32, + CLEAR_KEY_COMBINATION_TABLE=0x33, + PERFORM_SOFTWARE_RESET=0x34, + SET_REAL_TIME_CLOCK=0x35, + RECALIBRATE_POINTING_STICK=0x36, + SET_BELL_FREQUENCY=0x37, + SET_INTERNAL_BATTERY_CHARGE_RATE=0x39, + SET_EXTERNAL_BATTERY_CHARGE_RATE=0x3A, + SET_REAL_TIME_CLOCK_ALARM=0x3B, + READ_EEPROM=0x40, + WRITE_EEPROM=0x41, + WRITE_TO_STATUS_DISPLAY=0x42, + DEFINE_SPECIAL_CHARACTER=0x43, + DEFINE_KEY_COMBINATION_ENTRY=0x50, + DEFINE_STRING_TABLE_ENTRY=0x51, + DEFINE_STATUS_SCREEN_DISPLAY=0x52, + PERFORM_EMU_COMMANDS=0x64, + READ_EMU_REGISTER=0x65, + WRITE_EMU_REGISTER=0x66, + READ_EMU_RAM=0x67, + WRITE_EMU_RAM=0x68, + READ_BQ_REGISTER=0x69, + WRITE_BQ_REGISTER=0x6A, + SET_USER_PASSWORD=0x70, + VERIFY_USER_PASSWORD=0x71, + GET_SYSTEM_PASSWORD_KEY=0x72, + VERIFY_SYSTEM_PASSWORD=0x73, + POWER_OFF=0x82, + POWER_RESTART=0x83, +}; + +static struct uctrl_driver { + struct uctrl_regs __iomem *regs; + int irq; + int pending; + struct uctrl_status status; +} *global_driver; + +static void uctrl_get_event_status(struct uctrl_driver *); +static void uctrl_get_external_status(struct uctrl_driver *); + +static long +uctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + default: + return -EINVAL; + } + return 0; +} + +static int +uctrl_open(struct inode *inode, struct file *file) +{ + mutex_lock(&uctrl_mutex); + uctrl_get_event_status(global_driver); + uctrl_get_external_status(global_driver); + mutex_unlock(&uctrl_mutex); + return 0; +} + +static irqreturn_t uctrl_interrupt(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + +static const struct file_operations uctrl_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = uctrl_ioctl, + .open = uctrl_open, +}; + +static struct miscdevice uctrl_dev = { + UCTRL_MINOR, + "uctrl", + &uctrl_fops +}; + +/* Wait for space to write, then write to it */ +#define WRITEUCTLDATA(value) \ +{ \ + unsigned int i; \ + for (i = 0; i < 10000; i++) { \ + if (UCTRL_STAT_TXNF_STA & sbus_readl(&driver->regs->uctrl_stat)) \ + break; \ + } \ + dprintk(("write data 0x%02x\n", value)); \ + sbus_writel(value, &driver->regs->uctrl_data); \ +} + +/* Wait for something to read, read it, then clear the bit */ +#define READUCTLDATA(value) \ +{ \ + unsigned int i; \ + value = 0; \ + for (i = 0; i < 10000; i++) { \ + if ((UCTRL_STAT_RXNE_STA & sbus_readl(&driver->regs->uctrl_stat)) == 0) \ + break; \ + udelay(1); \ + } \ + value = sbus_readl(&driver->regs->uctrl_data); \ + dprintk(("read data 0x%02x\n", value)); \ + sbus_writel(UCTRL_STAT_RXNE_STA, &driver->regs->uctrl_stat); \ +} + +static void uctrl_do_txn(struct uctrl_driver *driver, struct uctrl_txn *txn) +{ + int stat, incnt, outcnt, bytecnt, intr; + u32 byte; + + stat = sbus_readl(&driver->regs->uctrl_stat); + intr = sbus_readl(&driver->regs->uctrl_intr); + sbus_writel(stat, &driver->regs->uctrl_stat); + + dprintk(("interrupt stat 0x%x int 0x%x\n", stat, intr)); + + incnt = txn->inbits; + outcnt = txn->outbits; + byte = (txn->opcode << 8); + WRITEUCTLDATA(byte); + + bytecnt = 0; + while (incnt > 0) { + byte = (txn->inbuf[bytecnt] << 8); + WRITEUCTLDATA(byte); + incnt--; + bytecnt++; + } + + /* Get the ack */ + READUCTLDATA(byte); + dprintk(("ack was %x\n", (byte >> 8))); + + bytecnt = 0; + while (outcnt > 0) { + READUCTLDATA(byte); + txn->outbuf[bytecnt] = (byte >> 8); + dprintk(("set byte to %02x\n", byte)); + outcnt--; + bytecnt++; + } +} + +static void uctrl_get_event_status(struct uctrl_driver *driver) +{ + struct uctrl_txn txn; + u8 outbits[2]; + + txn.opcode = READ_EVENT_STATUS; + txn.inbits = 0; + txn.outbits = 2; + txn.inbuf = NULL; + txn.outbuf = outbits; + + uctrl_do_txn(driver, &txn); + + dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); + driver->status.event_status = + ((outbits[0] & 0xff) << 8) | (outbits[1] & 0xff); + dprintk(("ev is %x\n", driver->status.event_status)); +} + +static void uctrl_get_external_status(struct uctrl_driver *driver) +{ + struct uctrl_txn txn; + u8 outbits[2]; + int i, v; + + txn.opcode = READ_EXTERNAL_STATUS; + txn.inbits = 0; + txn.outbits = 2; + txn.inbuf = NULL; + txn.outbuf = outbits; + + uctrl_do_txn(driver, &txn); + + dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); + driver->status.external_status = + ((outbits[0] * 256) + (outbits[1])); + dprintk(("ex is %x\n", driver->status.external_status)); + v = driver->status.external_status; + for (i = 0; v != 0; i++, v >>= 1) { + if (v & 1) { + dprintk(("%s%s", " ", uctrl_extstatus[i])); + } + } + dprintk(("\n")); + +} + +static int uctrl_probe(struct platform_device *op) +{ + struct uctrl_driver *p; + int err = -ENOMEM; + + p = kzalloc_obj(*p); + if (!p) { + printk(KERN_ERR "uctrl: Unable to allocate device struct.\n"); + goto out; + } + + p->regs = of_ioremap(&op->resource[0], 0, + resource_size(&op->resource[0]), + "uctrl"); + if (!p->regs) { + printk(KERN_ERR "uctrl: Unable to map registers.\n"); + goto out_free; + } + + p->irq = op->archdata.irqs[0]; + err = request_irq(p->irq, uctrl_interrupt, 0, "uctrl", p); + if (err) { + printk(KERN_ERR "uctrl: Unable to register irq.\n"); + goto out_iounmap; + } + + err = misc_register(&uctrl_dev); + if (err) { + printk(KERN_ERR "uctrl: Unable to register misc device.\n"); + goto out_free_irq; + } + + sbus_writel(UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK, &p->regs->uctrl_intr); + printk(KERN_INFO "%pOF: uctrl regs[0x%p] (irq %d)\n", + op->dev.of_node, p->regs, p->irq); + uctrl_get_event_status(p); + uctrl_get_external_status(p); + + dev_set_drvdata(&op->dev, p); + global_driver = p; + +out: + return err; + +out_free_irq: + free_irq(p->irq, p); + +out_iounmap: + of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); + +out_free: + kfree(p); + goto out; +} + +static void uctrl_remove(struct platform_device *op) +{ + struct uctrl_driver *p = dev_get_drvdata(&op->dev); + + if (p) { + misc_deregister(&uctrl_dev); + free_irq(p->irq, p); + of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); + kfree(p); + } +} + +static const struct of_device_id uctrl_match[] = { + { + .name = "uctrl", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, uctrl_match); + +static struct platform_driver uctrl_driver = { + .driver = { + .name = "uctrl", + .of_match_table = uctrl_match, + }, + .probe = uctrl_probe, + .remove = uctrl_remove, +}; + + +module_platform_driver(uctrl_driver); + +MODULE_DESCRIPTION("Tadpole TS102 Microcontroller driver"); +MODULE_LICENSE("GPL"); -- cgit v1.3.1