Commit 48d25d38 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'parisc-for-6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux

Pull parisc architecture updates from Helge Deller:
 "PA-RISC now has a native eBPF JIT compiler for 32- and 64-bit kernels,
  the LED driver was rewritten to use the Linux LED framework and most
  of the parisc bootup code was switched to use *_initcall() functions.

  Summary:

   - add eBPF JIT compiler for 32- and 64-bit kernel

   - LCD/LED driver rewrite to utilize Linux LED subsystem

   - switch to generic mmap top-down layout and brk randomization

   - kernel startup cleanup by loading most drivers via arch_initcall()"

* tag 'parisc-for-6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/deller/parisc-linux: (31 commits)
  parisc: ccio-dma: Create private runway procfs root entry
  parisc: chassis: Do not overwrite string on LCD display
  parisc: led: Rewrite LED/LCD driver to utilizize Linux LED subsystem
  parisc: led: Fix LAN receive and transmit LEDs
  parisc: lasi: Initialize LASI driver via arch_initcall()
  parisc: asp: Initialize asp driver via arch_initcall()
  parisc: wax: Initialize wax driver via arch_initcall()
  parisc: iosapic: Convert I/O Sapic driver to use arch_initcall()
  parisc: sba_iommu: Convert SBA IOMMU driver to use arch_initcall()
  parisc: led: Move register_led_regions() to late_initcall()
  parisc: lba: Convert LBA PCI bus driver to use arch_initcall()
  parisc: gsc: Convert GSC bus driver to use arch_initcall()
  parisc: ccio: Convert CCIO driver to use arch_initcall()
  parisc: eisa: Convert HP EISA bus driver to use arch_initcall()
  parisc: hppb: Convert HP PB bus driver to use arch_initcall()
  parisc: dino: Convert dino PCI bus driver to use arch_initcall()
  parisc: Makefile: Adjust order in which drivers should be loaded
  parisc: led: Reduce CPU overhead for disk & lan LED computation
  parisc: Avoid ioremap() for same addresss in iosapic_register()
  parisc: unaligned: Simplify 32-bit assembly in emulate_std()
  ...
parents 468e28d4 77e0ddf0
# SPDX-License-Identifier: GPL-2.0-only
obj-y += mm/ kernel/ math-emu/
obj-y += mm/ kernel/ math-emu/ net/
# for cleaning
subdir- += boot
......@@ -49,6 +49,9 @@ config PARISC
select TTY # Needed for pdc_cons.c
select HAS_IOPORT if PCI || EISA
select HAVE_DEBUG_STACKOVERFLOW
select ARCH_WANT_DEFAULT_TOPDOWN_MMAP_LAYOUT
select HAVE_ARCH_MMAP_RND_COMPAT_BITS if COMPAT
select HAVE_ARCH_MMAP_RND_BITS
select HAVE_ARCH_AUDITSYSCALL
select HAVE_ARCH_HASH
select HAVE_ARCH_JUMP_LABEL
......@@ -56,6 +59,8 @@ config PARISC
select HAVE_ARCH_KFENCE
select HAVE_ARCH_SECCOMP_FILTER
select HAVE_ARCH_TRACEHOOK
select HAVE_EBPF_JIT
select ARCH_WANT_DEFAULT_BPF_JIT
select HAVE_REGS_AND_STACK_ACCESS_API
select HOTPLUG_CORE_SYNC_DEAD if HOTPLUG_CPU
select GENERIC_SCHED_CLOCK
......@@ -124,6 +129,20 @@ config TIME_LOW_RES
depends on SMP
default y
config ARCH_MMAP_RND_BITS_MIN
default 18 if 64BIT
default 8
config ARCH_MMAP_RND_COMPAT_BITS_MIN
default 8
config ARCH_MMAP_RND_BITS_MAX
default 24 if 64BIT
default 17
config ARCH_MMAP_RND_COMPAT_BITS_MAX
default 17
# unless you want to implement ACPI on PA-RISC ... ;-)
config PM
bool
......
......@@ -13,7 +13,7 @@ config LIGHTWEIGHT_SPINLOCK_CHECK
config TLB_PTLOCK
bool "Use page table locks in TLB fault handler"
depends on SMP
depends on DEBUG_KERNEL && SMP
default n
help
Select this option to enable page table locking in the TLB
......
......@@ -163,8 +163,7 @@ typedef struct elf32_fdesc {
/* Format for the Elf64 Function descriptor */
typedef struct elf64_fdesc {
__u64 dummy[2]; /* FIXME: nothing uses these, why waste
* the space */
__u64 dummy[2]; /* used by 64-bit eBPF and tracing functions */
__u64 addr;
__u64 gp;
} Elf64_Fdesc;
......
......@@ -11,8 +11,8 @@
#define LED1 0x02
#define LED0 0x01 /* bottom (or furthest left) LED */
#define LED_LAN_TX LED0 /* for LAN transmit activity */
#define LED_LAN_RCV LED1 /* for LAN receive activity */
#define LED_LAN_RCV LED0 /* for LAN receive activity */
#define LED_LAN_TX LED1 /* for LAN transmit activity */
#define LED_DISK_IO LED2 /* for disk activity */
#define LED_HEARTBEAT LED3 /* heartbeat */
......@@ -25,19 +25,13 @@
#define LED_CMD_REG_NONE 0 /* NULL == no addr for the cmd register */
/* register_led_driver() */
int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg);
/* registers the LED regions for procfs */
void __init register_led_regions(void);
int register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg);
#ifdef CONFIG_CHASSIS_LCD_LED
/* writes a string to the LCD display (if possible on this h/w) */
int lcd_print(const char *str);
void lcd_print(const char *str);
#else
#define lcd_print(str)
#define lcd_print(str) do { } while (0)
#endif
/* main LED initialization function (uses PDC) */
int __init led_init(void);
#endif /* LED_H */
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PARISC_MACHDEP_H
#define _PARISC_MACHDEP_H
#include <linux/notifier.h>
#define MACH_RESTART 1
#define MACH_HALT 2
#define MACH_POWER_ON 3
#define MACH_POWER_OFF 4
extern struct notifier_block *mach_notifier;
extern void pa7300lc_init(void);
extern void (*cpu_lpmc)(int, struct pt_regs *);
#endif
......@@ -313,15 +313,7 @@ extern void collect_boot_cpu_data(void);
extern int show_cpuinfo (struct seq_file *m, void *v);
/* driver code in driver/parisc */
extern void gsc_init(void);
extern void processor_init(void);
extern void ccio_init(void);
extern void hppb_init(void);
extern void dino_init(void);
extern void iosapic_init(void);
extern void lba_init(void);
extern void sba_init(void);
extern void parisc_eisa_init(void);
struct parisc_device;
struct resource;
extern void sba_distributed_lmmio(struct parisc_device *, struct resource *);
......
......@@ -252,7 +252,7 @@ static inline int agp_mode_mercury(void __iomem *hpa) {
** fixup_irq is to initialize PCI IRQ line support and
** virtualize pcidev->irq value. To be called by pci_fixup_bus().
*/
extern void *iosapic_register(unsigned long hpa);
extern void *iosapic_register(unsigned long hpa, void __iomem *vaddr);
extern int iosapic_fixup_irq(void *obj, struct pci_dev *pcidev);
#define LBA_FUNC_ID 0x0000 /* function id */
......
......@@ -2,9 +2,6 @@
#ifndef ASM_PARISC_RUNWAY_H
#define ASM_PARISC_RUNWAY_H
/* declared in arch/parisc/kernel/setup.c */
extern struct proc_dir_entry * proc_runway_root;
#define RUNWAY_STATUS 0x10
#define RUNWAY_DEBUG 0x40
......
......@@ -6,7 +6,7 @@
extra-y := vmlinux.lds
obj-y := head.o cache.o pacache.o setup.o pdt.o traps.o time.o irq.o \
pa7300lc.o syscall.o entry.o sys_parisc.o firmware.o \
syscall.o entry.o sys_parisc.o firmware.o \
ptrace.o hardware.o inventory.o drivers.o alternative.o \
signal.o hpmc.o real2.o parisc_ksyms.o unaligned.o \
process.o processor.o pdc_cons.o pdc_chassis.o unwind.o \
......
// SPDX-License-Identifier: GPL-2.0
/*
* linux/arch/parisc/kernel/pa7300lc.c
* - PA7300LC-specific functions
*
* Copyright (C) 2000 Philipp Rumpf */
#include <linux/sched.h>
#include <linux/sched/debug.h>
#include <linux/smp.h>
#include <linux/kernel.h>
#include <asm/io.h>
#include <asm/ptrace.h>
#include <asm/machdep.h>
/* CPU register indices */
#define MIOC_STATUS 0xf040
#define MIOC_CONTROL 0xf080
#define MDERRADD 0xf0e0
#define DMAERR 0xf0e8
#define DIOERR 0xf0ec
#define HIDMAMEM 0xf0f4
/* this returns the HPA of the CPU it was called on */
static u32 cpu_hpa(void)
{
return 0xfffb0000;
}
static void pa7300lc_lpmc(int code, struct pt_regs *regs)
{
u32 hpa;
printk(KERN_WARNING "LPMC on CPU %d\n", smp_processor_id());
show_regs(regs);
hpa = cpu_hpa();
printk(KERN_WARNING
"MIOC_CONTROL %08x\n" "MIOC_STATUS %08x\n"
"MDERRADD %08x\n" "DMAERR %08x\n"
"DIOERR %08x\n" "HIDMAMEM %08x\n",
gsc_readl(hpa+MIOC_CONTROL), gsc_readl(hpa+MIOC_STATUS),
gsc_readl(hpa+MDERRADD), gsc_readl(hpa+DMAERR),
gsc_readl(hpa+DIOERR), gsc_readl(hpa+HIDMAMEM));
}
void pa7300lc_init(void)
{
cpu_lpmc = pa7300lc_lpmc;
}
......@@ -31,6 +31,7 @@
#include <asm/processor.h>
#include <asm/pdc.h>
#include <asm/pdcpat.h>
#include <asm/led.h>
#define PDC_CHASSIS_VER "0.05"
......@@ -234,6 +235,11 @@ int pdc_chassis_send_status(int message)
} else retval = -1;
#endif /* CONFIG_64BIT */
} /* if (pdc_chassis_enabled) */
/* if system has LCD display, update current string */
if (retval != -1 && IS_ENABLED(CONFIG_CHASSIS_LCD_LED))
lcd_print(NULL);
#endif /* CONFIG_PDC_CHASSIS */
return retval;
}
......
......@@ -97,18 +97,12 @@ void machine_restart(char *cmd)
}
void (*chassis_power_off)(void);
/*
* This routine is called from sys_reboot to actually turn off the
* machine
*/
void machine_power_off(void)
{
/* If there is a registered power off handler, call it. */
if (chassis_power_off)
chassis_power_off();
/* Put the soft power button back under hardware control.
* If the user had already pressed the power button, the
* following call will immediately power off. */
......@@ -284,17 +278,3 @@ __get_wchan(struct task_struct *p)
} while (count++ < MAX_UNWIND_ENTRIES);
return 0;
}
static inline unsigned long brk_rnd(void)
{
return (get_random_u32() & BRK_RND_MASK) << PAGE_SHIFT;
}
unsigned long arch_randomize_brk(struct mm_struct *mm)
{
unsigned long ret = PAGE_ALIGN(mm->brk + brk_rnd());
if (ret < mm->brk)
return mm->brk;
return ret;
}
......@@ -378,10 +378,18 @@ int
show_cpuinfo (struct seq_file *m, void *v)
{
unsigned long cpu;
char cpu_name[60], *p;
/* strip PA path from CPU name to not confuse lscpu */
strlcpy(cpu_name, per_cpu(cpu_data, 0).dev->name, sizeof(cpu_name));
p = strrchr(cpu_name, '[');
if (p)
*(--p) = 0;
for_each_online_cpu(cpu) {
const struct cpuinfo_parisc *cpuinfo = &per_cpu(cpu_data, cpu);
#ifdef CONFIG_SMP
const struct cpuinfo_parisc *cpuinfo = &per_cpu(cpu_data, cpu);
if (0 == cpuinfo->hpa)
continue;
#endif
......@@ -426,8 +434,7 @@ show_cpuinfo (struct seq_file *m, void *v)
seq_printf(m, "model\t\t: %s - %s\n",
boot_cpu_data.pdc.sys_model_name,
cpuinfo->dev ?
cpuinfo->dev->name : "Unknown");
cpu_name);
seq_printf(m, "hversion\t: 0x%08x\n"
"sversion\t: 0x%08x\n",
......
......@@ -31,7 +31,6 @@
#include <asm/sections.h>
#include <asm/pdc.h>
#include <asm/led.h>
#include <asm/machdep.h> /* for pa7300lc_init() proto */
#include <asm/pdc_chassis.h>
#include <asm/io.h>
#include <asm/setup.h>
......@@ -93,8 +92,6 @@ static void __init dma_ops_init(void)
"the PA-RISC 1.1 or 2.0 architecture specification.\n");
case pcxl2:
pa7300lc_init();
break;
default:
break;
}
......@@ -146,11 +143,6 @@ void __init setup_arch(char **cmdline_p)
parisc_cache_init();
paging_init();
#ifdef CONFIG_CHASSIS_LCD_LED
/* initialize the LCD/LED after boot_cpu_data is available ! */
led_init(); /* LCD/LED initialization */
#endif
#ifdef CONFIG_PA11
dma_ops_init();
#endif
......@@ -281,47 +273,6 @@ static int __init parisc_init(void)
apply_alternatives_all();
parisc_setup_cache_timing();
/* These are in a non-obvious order, will fix when we have an iotree */
#if defined(CONFIG_IOSAPIC)
iosapic_init();
#endif
#if defined(CONFIG_IOMMU_SBA)
sba_init();
#endif
#if defined(CONFIG_PCI_LBA)
lba_init();
#endif
/* CCIO before any potential subdevices */
#if defined(CONFIG_IOMMU_CCIO)
ccio_init();
#endif
/*
* Need to register Asp & Wax before the EISA adapters for the IRQ
* regions. EISA must come before PCI to be sure it gets IRQ region
* 0.
*/
#if defined(CONFIG_GSC_LASI) || defined(CONFIG_GSC_WAX)
gsc_init();
#endif
#ifdef CONFIG_EISA
parisc_eisa_init();
#endif
#if defined(CONFIG_HPPB)
hppb_init();
#endif
#if defined(CONFIG_GSC_DINO)
dino_init();
#endif
#ifdef CONFIG_CHASSIS_LCD_LED
register_led_regions(); /* register LED port info in procfs */
#endif
return 0;
}
arch_initcall(parisc_init);
......
......@@ -161,7 +161,7 @@ static unsigned long arch_get_unmapped_area_common(struct file *filp,
}
info.flags = 0;
info.low_limit = mm->mmap_legacy_base;
info.low_limit = mm->mmap_base;
info.high_limit = mmap_upper_limit(NULL);
return vm_unmapped_area(&info);
}
......@@ -181,58 +181,6 @@ unsigned long arch_get_unmapped_area_topdown(struct file *filp,
addr, len, pgoff, flags, DOWN);
}
static int mmap_is_legacy(void)
{
if (current->personality & ADDR_COMPAT_LAYOUT)
return 1;
/* parisc stack always grows up - so a unlimited stack should
* not be an indicator to use the legacy memory layout.
* if (rlimit(RLIMIT_STACK) == RLIM_INFINITY)
* return 1;
*/
return sysctl_legacy_va_layout;
}
static unsigned long mmap_rnd(void)
{
unsigned long rnd = 0;
if (current->flags & PF_RANDOMIZE)
rnd = get_random_u32() & MMAP_RND_MASK;
return rnd << PAGE_SHIFT;
}
unsigned long arch_mmap_rnd(void)
{
return (get_random_u32() & MMAP_RND_MASK) << PAGE_SHIFT;
}
static unsigned long mmap_legacy_base(void)
{
return TASK_UNMAPPED_BASE + mmap_rnd();
}
/*
* This function, called very early during the creation of a new
* process VM image, sets up which VM layout function to use:
*/
void arch_pick_mmap_layout(struct mm_struct *mm, struct rlimit *rlim_stack)
{
mm->mmap_legacy_base = mmap_legacy_base();
mm->mmap_base = mmap_upper_limit(rlim_stack);
if (mmap_is_legacy()) {
mm->mmap_base = mm->mmap_legacy_base;
mm->get_unmapped_area = arch_get_unmapped_area;
} else {
mm->get_unmapped_area = arch_get_unmapped_area_topdown;
}
}
asmlinkage unsigned long sys_mmap2(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags, unsigned long fd,
unsigned long pgoff)
......
......@@ -335,9 +335,6 @@ static void default_trap(int code, struct pt_regs *regs)
show_regs(regs);
}
void (*cpu_lpmc) (int code, struct pt_regs *regs) __read_mostly = default_trap;
static void transfer_pim_to_trap_frame(struct pt_regs *regs)
{
register int i;
......@@ -557,7 +554,7 @@ void notrace handle_interruption(int code, struct pt_regs *regs)
flush_cache_all();
flush_tlb_all();
cpu_lpmc(5, regs);
default_trap(code, regs);
return;
case PARISC_ITLB_TRAP:
......
......@@ -338,25 +338,24 @@ static int emulate_std(struct pt_regs *regs, int frreg, int flop)
: "r19", "r20", "r21", "r22", "r1" );
#else
{
unsigned long valh = (val >> 32), vall = (val & 0xffffffffl);
__asm__ __volatile__ (
" mtsp %4, %%sr1\n"
" zdep %2, 29, 2, %%r19\n"
" dep %%r0, 31, 2, %3\n"
" mtsp %3, %%sr1\n"
" zdep %R1, 29, 2, %%r19\n"
" dep %%r0, 31, 2, %2\n"
" mtsar %%r19\n"
" zvdepi -2, 32, %%r19\n"
"1: ldw 0(%%sr1,%3),%%r20\n"
"2: ldw 8(%%sr1,%3),%%r21\n"
" vshd %1, %2, %%r1\n"
"1: ldw 0(%%sr1,%2),%%r20\n"
"2: ldw 8(%%sr1,%2),%%r21\n"
" vshd %1, %R1, %%r1\n"
" vshd %%r0, %1, %1\n"
" vshd %2, %%r0, %2\n"
" vshd %R1, %%r0, %R1\n"
" and %%r20, %%r19, %%r20\n"
" andcm %%r21, %%r19, %%r21\n"
" or %1, %%r20, %1\n"
" or %2, %%r21, %2\n"
"3: stw %1,0(%%sr1,%3)\n"
"4: stw %%r1,4(%%sr1,%3)\n"
"5: stw %2,8(%%sr1,%3)\n"
" or %R1, %%r21, %R1\n"
"3: stw %1,0(%%sr1,%2)\n"
"4: stw %%r1,4(%%sr1,%2)\n"
"5: stw %R1,8(%%sr1,%2)\n"
"6: \n"
ASM_EXCEPTIONTABLE_ENTRY_EFAULT(1b, 6b)
ASM_EXCEPTIONTABLE_ENTRY_EFAULT(2b, 6b)
......@@ -364,7 +363,7 @@ static int emulate_std(struct pt_regs *regs, int frreg, int flop)
ASM_EXCEPTIONTABLE_ENTRY_EFAULT(4b, 6b)
ASM_EXCEPTIONTABLE_ENTRY_EFAULT(5b, 6b)
: "+r" (ret)
: "r" (valh), "r" (vall), "r" (regs->ior), "r" (regs->isr)
: "r" (val), "r" (regs->ior), "r" (regs->isr)
: "r19", "r20", "r21", "r1" );
}
#endif
......
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_BPF_JIT) += bpf_jit_core.o
ifeq ($(CONFIG_64BIT),y)
obj-$(CONFIG_BPF_JIT) += bpf_jit_comp64.o
else
obj-$(CONFIG_BPF_JIT) += bpf_jit_comp32.o
endif
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Common functionality for PARISC32 and PARISC64 BPF JIT compilers
*
* Copyright (c) 2023 Helge Deller <deller@gmx.de>
*
*/
#ifndef _BPF_JIT_H
#define _BPF_JIT_H
#include <linux/bpf.h>
#include <linux/filter.h>
#include <asm/cacheflush.h>
#define HPPA_JIT_DEBUG 0
#define HPPA_JIT_REBOOT 0
#define HPPA_JIT_DUMP 0
#define OPTIMIZE_HPPA 1 /* enable some asm optimizations */
// echo 1 > /proc/sys/net/core/bpf_jit_enable
#define HPPA_R(nr) nr /* use HPPA register #nr */
enum {
HPPA_REG_ZERO = 0, /* The constant value 0 */
HPPA_REG_R1 = 1, /* used for addil */
HPPA_REG_RP = 2, /* Return address */
HPPA_REG_ARG7 = 19, /* ARG4-7 used in 64-bit ABI */
HPPA_REG_ARG6 = 20,
HPPA_REG_ARG5 = 21,
HPPA_REG_ARG4 = 22,
HPPA_REG_ARG3 = 23, /* ARG0-3 in 32- and 64-bit ABI */
HPPA_REG_ARG2 = 24,
HPPA_REG_ARG1 = 25,
HPPA_REG_ARG0 = 26,
HPPA_REG_GP = 27, /* Global pointer */
HPPA_REG_RET0 = 28, /* Return value, HI in 32-bit */
HPPA_REG_RET1 = 29, /* Return value, LOW in 32-bit */
HPPA_REG_SP = 30, /* Stack pointer */
HPPA_REG_R31 = 31,
#ifdef CONFIG_64BIT
HPPA_REG_TCC = 3,
HPPA_REG_TCC_SAVED = 4,
HPPA_REG_TCC_IN_INIT = HPPA_REG_R31,
#else
HPPA_REG_TCC = 18,
HPPA_REG_TCC_SAVED = 17,
HPPA_REG_TCC_IN_INIT = HPPA_REG_R31,
#endif
HPPA_REG_T0 = HPPA_REG_R1, /* Temporaries */
HPPA_REG_T1 = HPPA_REG_R31,
HPPA_REG_T2 = HPPA_REG_ARG4,
#ifndef CONFIG_64BIT
HPPA_REG_T3 = HPPA_REG_ARG5, /* not used in 64-bit */
HPPA_REG_T4 = HPPA_REG_ARG6,
HPPA_REG_T5 = HPPA_REG_ARG7,
#endif
};
struct hppa_jit_context {
struct bpf_prog *prog;
u32 *insns; /* HPPA insns */
int ninsns;
int reg_seen_collect;
int reg_seen;
int body_len;
int epilogue_offset;
int prologue_len;
int *offset; /* BPF to HPPA */
};
#define REG_SET_SEEN(ctx, nr) { if (ctx->reg_seen_collect) ctx->reg_seen |= BIT(nr); }
#define REG_SET_SEEN_ALL(ctx) { if (ctx->reg_seen_collect) ctx->reg_seen = -1; }
#define REG_FORCE_SEEN(ctx, nr) { ctx->reg_seen |= BIT(nr); }
#define REG_WAS_SEEN(ctx, nr) (ctx->reg_seen & BIT(nr))
#define REG_ALL_SEEN(ctx) (ctx->reg_seen == -1)
#define HPPA_INSN_SIZE 4 /* bytes per HPPA asm instruction */
#define REG_SIZE REG_SZ /* bytes per native "long" word */
/* subtract hppa displacement on branches which is .+8 */
#define HPPA_BRANCH_DISPLACEMENT 2 /* instructions */
/* asm statement indicator to execute delay slot */
#define EXEC_NEXT_INSTR 0
#define NOP_NEXT_INSTR 1
#define im11(val) (((u32)(val)) & 0x07ff)
#define hppa_ldil(addr, reg) \
hppa_t5_insn(0x08, reg, ((u32)(addr)) >> 11) /* ldil im21,reg */
#define hppa_addil(addr, reg) \
hppa_t5_insn(0x0a, reg, ((u32)(addr)) >> 11) /* addil im21,reg -> result in gr1 */
#define hppa_ldo(im14, reg, target) \
hppa_t1_insn(0x0d, reg, target, im14) /* ldo val14(reg),target */
#define hppa_ldi(im14, reg) \
hppa_ldo(im14, HPPA_REG_ZERO, reg) /* ldi val14,reg */
#define hppa_or(reg1, reg2, target) \
hppa_t6_insn(0x02, reg2, reg1, 0, 0, 0x09, target) /* or reg1,reg2,target */
#define hppa_or_cond(reg1, reg2, cond, f, target) \
hppa_t6_insn(0x02, reg2, reg1, cond, f, 0x09, target)
#define hppa_and(reg1, reg2, target) \
hppa_t6_insn(0x02, reg2, reg1, 0, 0, 0x08, target) /* and reg1,reg2,target */
#define hppa_and_cond(reg1, reg2, cond, f, target) \
hppa_t6_insn(0x02, reg2, reg1, cond, f, 0x08, target)
#define hppa_xor(reg1, reg2, target) \
hppa_t6_insn(0x02, reg2, reg1, 0, 0, 0x0a, target) /* xor reg1,reg2,target */
#define hppa_add(reg1, reg2, target) \
hppa_t6_insn(0x02, reg2, reg1, 0, 0, 0x18, target) /* add reg1,reg2,target */
#define hppa_addc(reg1, reg2, target) \
hppa_t6_insn(0x02, reg2, reg1, 0, 0, 0x1c, target) /* add,c reg1,reg2,target */
#define hppa_sub(reg1, reg2, target) \
hppa_t6_insn(0x02, reg2, reg1, 0, 0, 0x10, target) /* sub reg1,reg2,target */
#define hppa_subb(reg1, reg2, target) \
hppa_t6_insn(0x02, reg2, reg1, 0, 0, 0x14, target) /* sub,b reg1,reg2,target */
#define hppa_nop() \
hppa_or(0,0,0) /* nop: or 0,0,0 */
#define hppa_addi(val11, reg, target) \
hppa_t7_insn(0x2d, reg, target, val11) /* addi im11,reg,target */
#define hppa_subi(val11, reg, target) \
hppa_t7_insn(0x25, reg, target, val11) /* subi im11,reg,target */
#define hppa_copy(reg, target) \
hppa_or(reg, HPPA_REG_ZERO, target) /* copy reg,target */
#define hppa_ldw(val14, reg, target) \
hppa_t1_insn(0x12, reg, target, val14) /* ldw im14(reg),target */
#define hppa_ldb(val14, reg, target) \
hppa_t1_insn(0x10, reg, target, val14) /* ldb im14(reg),target */
#define hppa_ldh(val14, reg, target) \
hppa_t1_insn(0x11, reg, target, val14) /* ldh im14(reg),target */
#define hppa_stw(reg, val14, base) \
hppa_t1_insn(0x1a, base, reg, val14) /* stw reg,im14(base) */
#define hppa_stb(reg, val14, base) \
hppa_t1_insn(0x18, base, reg, val14) /* stb reg,im14(base) */
#define hppa_sth(reg, val14, base) \
hppa_t1_insn(0x19, base, reg, val14) /* sth reg,im14(base) */
#define hppa_stwma(reg, val14, base) \
hppa_t1_insn(0x1b, base, reg, val14) /* stw,ma reg,im14(base) */
#define hppa_bv(reg, base, nop) \
hppa_t11_insn(0x3a, base, reg, 0x06, 0, nop) /* bv(,n) reg(base) */
#define hppa_be(offset, base) \
hppa_t12_insn(0x38, base, offset, 0x00, 1) /* be,n offset(0,base) */
#define hppa_be_l(offset, base, nop) \
hppa_t12_insn(0x39, base, offset, 0x00, nop) /* ble(,nop) offset(0,base) */
#define hppa_mtctl(reg, cr) \
hppa_t21_insn(0x00, cr, reg, 0xc2, 0) /* mtctl reg,cr */
#define hppa_mtsar(reg) \
hppa_mtctl(reg, 11) /* mtsar reg */
#define hppa_zdep(r, p, len, target) \
hppa_t10_insn(0x35, target, r, 0, 2, p, len) /* zdep r,a,b,t */
#define hppa_shl(r, len, target) \
hppa_zdep(r, len, len, lo(rd))
#define hppa_depwz(r, p, len, target) \
hppa_t10_insn(0x35, target, r, 0, 3, 31-(p), 32-(len)) /* depw,z r,p,len,ret1 */
#define hppa_depwz_sar(reg, target) \
hppa_t1_insn(0x35, target, reg, 0) /* depw,z reg,sar,32,target */
#define hppa_shrpw_sar(reg, target) \
hppa_t10_insn(0x34, reg, 0, 0, 0, 0, target) /* shrpw r0,reg,sar,target */
#define hppa_shrpw(r1, r2, p, target) \
hppa_t10_insn(0x34, r2, r1, 0, 2, 31-(p), target) /* shrpw r1,r2,p,target */
#define hppa_shd(r1, r2, p, target) \
hppa_t10_insn(0x34, r2, r1, 0, 2, 31-(p), target) /* shrpw r1,r2,p,tarfer */
#define hppa_extrws_sar(reg, target) \
hppa_t10_insn(0x34, reg, target, 0, 5, 0, 0) /* extrw,s reg,sar,32,ret0 */
#define hppa_extrws(reg, p, len, target) \
hppa_t10_insn(0x34, reg, target, 0, 7, p, len) /* extrw,s reg,p,len,target */
#define hppa_extru(r, p, len, target) \
hppa_t10_insn(0x34, r, target, 0, 6, p, 32-(len))
#define hppa_shr(r, len, target) \
hppa_extru(r, 31-(len), 32-(len), target)
#define hppa_bl(imm17, rp) \
hppa_t12_insn(0x3a, rp, imm17, 0x00, 1) /* bl,n target_addr,rp */
#define hppa_sh2add(r1, r2, target) \
hppa_t6_insn(0x02, r2, r1, 0, 0, 0x1a, target) /* sh2add r1,r2,target */
#define hppa_combt(r1, r2, target_addr, condition, nop) \
hppa_t11_insn(IS_ENABLED(CONFIG_64BIT) ? 0x27 : 0x20, \
r2, r1, condition, target_addr, nop) /* combt,cond,n r1,r2,addr */
#define hppa_beq(r1, r2, target_addr) \
hppa_combt(r1, r2, target_addr, 1, NOP_NEXT_INSTR)
#define hppa_blt(r1, r2, target_addr) \
hppa_combt(r1, r2, target_addr, 2, NOP_NEXT_INSTR)
#define hppa_ble(r1, r2, target_addr) \
hppa_combt(r1, r2, target_addr, 3, NOP_NEXT_INSTR)
#define hppa_bltu(r1, r2, target_addr) \
hppa_combt(r1, r2, target_addr, 4, NOP_NEXT_INSTR)
#define hppa_bleu(r1, r2, target_addr) \
hppa_combt(r1, r2, target_addr, 5, NOP_NEXT_INSTR)
#define hppa_combf(r1, r2, target_addr, condition, nop) \
hppa_t11_insn(IS_ENABLED(CONFIG_64BIT) ? 0x2f : 0x22, \
r2, r1, condition, target_addr, nop) /* combf,cond,n r1,r2,addr */
#define hppa_bne(r1, r2, target_addr) \
hppa_combf(r1, r2, target_addr, 1, NOP_NEXT_INSTR)
#define hppa_bge(r1, r2, target_addr) \
hppa_combf(r1, r2, target_addr, 2, NOP_NEXT_INSTR)
#define hppa_bgt(r1, r2, target_addr) \
hppa_combf(r1, r2, target_addr, 3, NOP_NEXT_INSTR)
#define hppa_bgeu(r1, r2, target_addr) \
hppa_combf(r1, r2, target_addr, 4, NOP_NEXT_INSTR)
#define hppa_bgtu(r1, r2, target_addr) \
hppa_combf(r1, r2, target_addr, 5, NOP_NEXT_INSTR)
/* 64-bit instructions */
#ifdef CONFIG_64BIT
#define hppa64_ldd_reg(reg, b, target) \
hppa_t10_insn(0x03, b, reg, 0, 0, 3<<1, target)
#define hppa64_ldd_im5(im5, b, target) \
hppa_t10_insn(0x03, b, low_sign_unext(im5,5), 0, 1<<2, 3<<1, target)
#define hppa64_ldd_im16(im16, b, target) \
hppa_t10_insn(0x14, b, target, 0, 0, 0, 0) | re_assemble_16(im16)
#define hppa64_std_im5(src, im5, b) \
hppa_t10_insn(0x03, b, src, 0, 1<<2, 0xB<<1, low_sign_unext(im5,5))
#define hppa64_std_im16(src, im16, b) \
hppa_t10_insn(0x1c, b, src, 0, 0, 0, 0) | re_assemble_16(im16)
#define hppa64_bl_long(offs22) \
hppa_t12_L_insn(0x3a, offs22, 1)
#define hppa64_mtsarcm(reg) \
hppa_t21_insn(0x00, 11, reg, 0xc6, 0)
#define hppa64_shrpd_sar(reg, target) \
hppa_t10_insn(0x34, reg, 0, 0, 0, 1<<4, target)
#define hppa64_shladd(r1, sa, r2, target) \
hppa_t6_insn(0x02, r2, r1, 0, 0, 1<<4|1<<3|sa, target)
#define hppa64_depdz_sar(reg, target) \
hppa_t21_insn(0x35, target, reg, 3<<3, 0)
#define hppa_extrd_sar(reg, target, se) \
hppa_t10_insn(0x34, reg, target, 0, 0, 0, 0) | 2<<11 | (se&1)<<10 | 1<<9 | 1<<8
#define hppa64_bve_l_rp(base) \
(0x3a << 26) | (base << 21) | 0xf000
#define hppa64_permh_3210(r, target) \
(0x3e << 26) | (r << 21) | (r << 16) | (target) | 0x00006900
#define hppa64_hshl(r, sa, target) \
(0x3e << 26) | (0 << 21) | (r << 16) | (sa << 6) | (target) | 0x00008800
#define hppa64_hshr_u(r, sa, target) \
(0x3e << 26) | (r << 21) | (0 << 16) | (sa << 6) | (target) | 0x0000c800
#endif
struct hppa_jit_data {
struct bpf_binary_header *header;
u8 *image;
struct hppa_jit_context ctx;
};
static inline void bpf_fill_ill_insns(void *area, unsigned int size)
{
memset(area, 0, size);
}
static inline void bpf_flush_icache(void *start, void *end)
{
flush_icache_range((unsigned long)start, (unsigned long)end);
}
/* Emit a 4-byte HPPA instruction. */
static inline void emit(const u32 insn, struct hppa_jit_context *ctx)
{
if (ctx->insns) {
ctx->insns[ctx->ninsns] = insn;
}
ctx->ninsns++;
}
static inline int epilogue_offset(struct hppa_jit_context *ctx)
{
int to = ctx->epilogue_offset, from = ctx->ninsns;
return (to - from);
}
/* Return -1 or inverted cond. */
static inline int invert_bpf_cond(u8 cond)
{
switch (cond) {
case BPF_JEQ:
return BPF_JNE;
case BPF_JGT:
return BPF_JLE;
case BPF_JLT:
return BPF_JGE;
case BPF_JGE:
return BPF_JLT;
case BPF_JLE:
return BPF_JGT;
case BPF_JNE:
return BPF_JEQ;
case BPF_JSGT:
return BPF_JSLE;
case BPF_JSLT:
return BPF_JSGE;
case BPF_JSGE:
return BPF_JSLT;
case BPF_JSLE:
return BPF_JSGT;
}
return -1;
}
static inline signed long hppa_offset(int insn, int off, struct hppa_jit_context *ctx)
{
signed long from, to;
off++; /* BPF branch is from PC+1 */
from = (insn > 0) ? ctx->offset[insn - 1] : 0;
to = (insn + off > 0) ? ctx->offset[insn + off - 1] : 0;
return (to - from);
}
/* does the signed value fits into a given number of bits ? */
static inline int check_bits_int(signed long val, int bits)
{
return ((val >= 0) && ((val >> bits) == 0)) ||
((val < 0) && (((~((u32)val)) >> (bits-1)) == 0));
}
/* can the signed value be used in relative code ? */
static inline int relative_bits_ok(signed long val, int bits)
{
return ((val >= 0) && (val < (1UL << (bits-1)))) || /* XXX */
((val < 0) && (((~((unsigned long)val)) >> (bits-1)) == 0)
&& (val & (1UL << (bits-1))));
}
/* can the signed value be used in relative branches ? */
static inline int relative_branch_ok(signed long val, int bits)
{
return ((val >= 0) && (val < (1UL << (bits-2)))) || /* XXX */
((val < 0) && (((~((unsigned long)val)) < (1UL << (bits-2))))
&& (val & (1UL << (bits-1))));
}
#define is_5b_int(val) check_bits_int(val, 5)
static inline unsigned sign_unext(unsigned x, unsigned len)
{
unsigned len_ones;
len_ones = (1 << len) - 1;
return x & len_ones;
}
static inline unsigned low_sign_unext(unsigned x, unsigned len)
{
unsigned temp;
unsigned sign;
sign = (x >> (len-1)) & 1;
temp = sign_unext (x, len-1);
return (temp << 1) | sign;
}
static inline unsigned re_assemble_12(unsigned as12)
{
return (( (as12 & 0x800) >> 11)
| ((as12 & 0x400) >> (10 - 2))
| ((as12 & 0x3ff) << (1 + 2)));
}
static inline unsigned re_assemble_14(unsigned as14)
{
return (( (as14 & 0x1fff) << 1)
| ((as14 & 0x2000) >> 13));
}
#ifdef CONFIG_64BIT
static inline unsigned re_assemble_16(unsigned as16)
{
unsigned s, t;
/* Unusual 16-bit encoding, for wide mode only. */
t = (as16 << 1) & 0xffff;
s = (as16 & 0x8000);
return (t ^ s ^ (s >> 1)) | (s >> 15);
}
#endif
static inline unsigned re_assemble_17(unsigned as17)
{
return (( (as17 & 0x10000) >> 16)
| ((as17 & 0x0f800) << (16 - 11))
| ((as17 & 0x00400) >> (10 - 2))
| ((as17 & 0x003ff) << (1 + 2)));
}
static inline unsigned re_assemble_21(unsigned as21)
{
return (( (as21 & 0x100000) >> 20)
| ((as21 & 0x0ffe00) >> 8)
| ((as21 & 0x000180) << 7)
| ((as21 & 0x00007c) << 14)
| ((as21 & 0x000003) << 12));
}
static inline unsigned re_assemble_22(unsigned as22)
{
return (( (as22 & 0x200000) >> 21)
| ((as22 & 0x1f0000) << (21 - 16))
| ((as22 & 0x00f800) << (16 - 11))
| ((as22 & 0x000400) >> (10 - 2))
| ((as22 & 0x0003ff) << (1 + 2)));
}
/* Various HPPA instruction formats. */
/* see https://parisc.wiki.kernel.org/images-parisc/6/68/Pa11_acd.pdf, appendix C */
static inline u32 hppa_t1_insn(u8 opcode, u8 b, u8 r, s16 im14)
{
return ((opcode << 26) | (b << 21) | (r << 16) | re_assemble_14(im14));
}
static inline u32 hppa_t5_insn(u8 opcode, u8 tr, u32 val21)
{
return ((opcode << 26) | (tr << 21) | re_assemble_21(val21));
}
static inline u32 hppa_t6_insn(u8 opcode, u8 r2, u8 r1, u8 c, u8 f, u8 ext6, u16 t)
{
return ((opcode << 26) | (r2 << 21) | (r1 << 16) | (c << 13) | (f << 12) |
(ext6 << 6) | t);
}
/* 7. Arithmetic immediate */
static inline u32 hppa_t7_insn(u8 opcode, u8 r, u8 t, u32 im11)
{
return ((opcode << 26) | (r << 21) | (t << 16) | low_sign_unext(im11, 11));
}
/* 10. Shift instructions */
static inline u32 hppa_t10_insn(u8 opcode, u8 r2, u8 r1, u8 c, u8 ext3, u8 cp, u8 t)
{
return ((opcode << 26) | (r2 << 21) | (r1 << 16) | (c << 13) |
(ext3 << 10) | (cp << 5) | t);
}
/* 11. Conditional branch instructions */
static inline u32 hppa_t11_insn(u8 opcode, u8 r2, u8 r1, u8 c, u32 w, u8 nop)
{
u32 ra = re_assemble_12(w);
// ra = low_sign_unext(w,11) | (w & (1<<10)
return ((opcode << 26) | (r2 << 21) | (r1 << 16) | (c << 13) | (nop << 1) | ra);
}
/* 12. Branch instructions */
static inline u32 hppa_t12_insn(u8 opcode, u8 rp, u32 w, u8 ext3, u8 nop)
{
return ((opcode << 26) | (rp << 21) | (ext3 << 13) | (nop << 1) | re_assemble_17(w));
}
static inline u32 hppa_t12_L_insn(u8 opcode, u32 w, u8 nop)
{
return ((opcode << 26) | (0x05 << 13) | (nop << 1) | re_assemble_22(w));
}
/* 21. Move to control register */
static inline u32 hppa_t21_insn(u8 opcode, u8 r2, u8 r1, u8 ext8, u8 t)
{
return ((opcode << 26) | (r2 << 21) | (r1 << 16) | (ext8 << 5) | t);
}
/* Helper functions called by jit code on HPPA32 and HPPA64. */
u64 hppa_div64(u64 div, u64 divisor);
u64 hppa_div64_rem(u64 div, u64 divisor);
/* Helper functions that emit HPPA instructions when possible. */
void bpf_jit_build_prologue(struct hppa_jit_context *ctx);
void bpf_jit_build_epilogue(struct hppa_jit_context *ctx);
int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,
bool extra_pass);
#endif /* _BPF_JIT_H */
// SPDX-License-Identifier: GPL-2.0
/*
* BPF JIT compiler for PA-RISC (32-bit)
*
* Copyright (c) 2023 Helge Deller <deller@gmx.de>
*
* The code is based on the BPF JIT compiler for RV64 by Björn Töpel and
* the BPF JIT compiler for 32-bit ARM by Shubham Bansal and Mircea Gherzan.
*/
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/libgcc.h>
#include "bpf_jit.h"
/*
* Stack layout during BPF program execution (note: stack grows up):
*
* high
* HPPA32 sp => +----------+ <= HPPA32 fp
* | saved sp |
* | saved rp |
* | ... | HPPA32 callee-saved registers
* | curr args|
* | local var|
* +----------+ <= (sp - 4 * NR_SAVED_REGISTERS)
* | lo(R9) |
* | hi(R9) |
* | lo(FP) | JIT scratch space for BPF registers
* | hi(FP) |
* | ... |
* +----------+ <= (sp - 4 * NR_SAVED_REGISTERS
* | | - 4 * BPF_JIT_SCRATCH_REGS)
* | |
* | ... | BPF program stack
* | |
* | ... | Function call stack
* | |
* +----------+
* low
*/
enum {
/* Stack layout - these are offsets from top of JIT scratch space. */
BPF_R8_HI,
BPF_R8_LO,
BPF_R9_HI,
BPF_R9_LO,
BPF_FP_HI,
BPF_FP_LO,
BPF_AX_HI,
BPF_AX_LO,
BPF_R0_TEMP_HI,
BPF_R0_TEMP_LO,
BPF_JIT_SCRATCH_REGS,
};
/* Number of callee-saved registers stored to stack: rp, r3-r18. */
#define NR_SAVED_REGISTERS (18 - 3 + 1 + 8)
/* Offset from fp for BPF registers stored on stack. */
#define STACK_OFFSET(k) (- (NR_SAVED_REGISTERS + k + 1))
#define STACK_ALIGN FRAME_SIZE
#define EXIT_PTR_LOAD(reg) hppa_ldw(-0x08, HPPA_REG_SP, reg)
#define EXIT_PTR_STORE(reg) hppa_stw(reg, -0x08, HPPA_REG_SP)
#define EXIT_PTR_JUMP(reg, nop) hppa_bv(HPPA_REG_ZERO, reg, nop)
#define TMP_REG_1 (MAX_BPF_JIT_REG + 0)
#define TMP_REG_2 (MAX_BPF_JIT_REG + 1)
#define TMP_REG_R0 (MAX_BPF_JIT_REG + 2)
static const s8 regmap[][2] = {
/* Return value from in-kernel function, and exit value from eBPF. */
[BPF_REG_0] = {HPPA_REG_RET0, HPPA_REG_RET1}, /* HI/LOW */
/* Arguments from eBPF program to in-kernel function. */
[BPF_REG_1] = {HPPA_R(3), HPPA_R(4)},
[BPF_REG_2] = {HPPA_R(5), HPPA_R(6)},
[BPF_REG_3] = {HPPA_R(7), HPPA_R(8)},
[BPF_REG_4] = {HPPA_R(9), HPPA_R(10)},
[BPF_REG_5] = {HPPA_R(11), HPPA_R(12)},
[BPF_REG_6] = {HPPA_R(13), HPPA_R(14)},
[BPF_REG_7] = {HPPA_R(15), HPPA_R(16)},
/*
* Callee-saved registers that in-kernel function will preserve.
* Stored on the stack.
*/
[BPF_REG_8] = {STACK_OFFSET(BPF_R8_HI), STACK_OFFSET(BPF_R8_LO)},
[BPF_REG_9] = {STACK_OFFSET(BPF_R9_HI), STACK_OFFSET(BPF_R9_LO)},
/* Read-only frame pointer to access BPF stack. Not needed. */
[BPF_REG_FP] = {STACK_OFFSET(BPF_FP_HI), STACK_OFFSET(BPF_FP_LO)},
/* Temporary register for blinding constants. Stored on the stack. */
[BPF_REG_AX] = {STACK_OFFSET(BPF_AX_HI), STACK_OFFSET(BPF_AX_LO)},
/*
* Temporary registers used by the JIT to operate on registers stored
* on the stack. Save t0 and t1 to be used as temporaries in generated
* code.
*/
[TMP_REG_1] = {HPPA_REG_T3, HPPA_REG_T2},
[TMP_REG_2] = {HPPA_REG_T5, HPPA_REG_T4},
/* temporary space for BPF_R0 during libgcc and millicode calls */
[TMP_REG_R0] = {STACK_OFFSET(BPF_R0_TEMP_HI), STACK_OFFSET(BPF_R0_TEMP_LO)},
};
static s8 hi(const s8 *r)
{
return r[0];
}
static s8 lo(const s8 *r)
{
return r[1];
}
static void emit_hppa_copy(const s8 rs, const s8 rd, struct hppa_jit_context *ctx)
{
REG_SET_SEEN(ctx, rd);
if (OPTIMIZE_HPPA && (rs == rd))
return;
REG_SET_SEEN(ctx, rs);
emit(hppa_copy(rs, rd), ctx);
}
static void emit_hppa_xor(const s8 r1, const s8 r2, const s8 r3, struct hppa_jit_context *ctx)
{
REG_SET_SEEN(ctx, r1);
REG_SET_SEEN(ctx, r2);
REG_SET_SEEN(ctx, r3);
if (OPTIMIZE_HPPA && (r1 == r2)) {
emit(hppa_copy(HPPA_REG_ZERO, r3), ctx);
} else {
emit(hppa_xor(r1, r2, r3), ctx);
}
}
static void emit_imm(const s8 rd, s32 imm, struct hppa_jit_context *ctx)
{
u32 lower = im11(imm);
REG_SET_SEEN(ctx, rd);
if (OPTIMIZE_HPPA && relative_bits_ok(imm, 14)) {
emit(hppa_ldi(imm, rd), ctx);
return;
}
emit(hppa_ldil(imm, rd), ctx);
if (OPTIMIZE_HPPA && (lower == 0))
return;
emit(hppa_ldo(lower, rd, rd), ctx);
}
static void emit_imm32(const s8 *rd, s32 imm, struct hppa_jit_context *ctx)
{
/* Emit immediate into lower bits. */
REG_SET_SEEN(ctx, lo(rd));
emit_imm(lo(rd), imm, ctx);
/* Sign-extend into upper bits. */
REG_SET_SEEN(ctx, hi(rd));
if (imm >= 0)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
else
emit(hppa_ldi(-1, hi(rd)), ctx);
}
static void emit_imm64(const s8 *rd, s32 imm_hi, s32 imm_lo,
struct hppa_jit_context *ctx)
{
emit_imm(hi(rd), imm_hi, ctx);
emit_imm(lo(rd), imm_lo, ctx);
}
static void __build_epilogue(bool is_tail_call, struct hppa_jit_context *ctx)
{
const s8 *r0 = regmap[BPF_REG_0];
int i;
if (is_tail_call) {
/*
* goto *(t0 + 4);
* Skips first instruction of prologue which initializes tail
* call counter. Assumes t0 contains address of target program,
* see emit_bpf_tail_call.
*/
emit(hppa_ldo(1 * HPPA_INSN_SIZE, HPPA_REG_T0, HPPA_REG_T0), ctx);
emit(hppa_bv(HPPA_REG_ZERO, HPPA_REG_T0, EXEC_NEXT_INSTR), ctx);
/* in delay slot: */
emit(hppa_copy(HPPA_REG_TCC, HPPA_REG_TCC_IN_INIT), ctx);
return;
}
/* load epilogue function pointer and jump to it. */
/* exit point is either directly below, or the outest TCC exit function */
emit(EXIT_PTR_LOAD(HPPA_REG_RP), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/* NOTE: we are 32-bit and big-endian, so return lower 32-bit value */
emit_hppa_copy(lo(r0), HPPA_REG_RET0, ctx);
/* Restore callee-saved registers. */
for (i = 3; i <= 18; i++) {
if (OPTIMIZE_HPPA && !REG_WAS_SEEN(ctx, HPPA_R(i)))
continue;
emit(hppa_ldw(-REG_SIZE * (8 + (i-3)), HPPA_REG_SP, HPPA_R(i)), ctx);
}
/* load original return pointer (stored by outest TCC function) */
emit(hppa_ldw(-0x14, HPPA_REG_SP, HPPA_REG_RP), ctx);
emit(hppa_bv(HPPA_REG_ZERO, HPPA_REG_RP, EXEC_NEXT_INSTR), ctx);
/* in delay slot: */
emit(hppa_ldw(-0x04, HPPA_REG_SP, HPPA_REG_SP), ctx);
}
static bool is_stacked(s8 reg)
{
return reg < 0;
}
static const s8 *bpf_get_reg64_offset(const s8 *reg, const s8 *tmp,
u16 offset_sp, struct hppa_jit_context *ctx)
{
if (is_stacked(hi(reg))) {
emit(hppa_ldw(REG_SIZE * hi(reg) - offset_sp, HPPA_REG_SP, hi(tmp)), ctx);
emit(hppa_ldw(REG_SIZE * lo(reg) - offset_sp, HPPA_REG_SP, lo(tmp)), ctx);
reg = tmp;
}
REG_SET_SEEN(ctx, hi(reg));
REG_SET_SEEN(ctx, lo(reg));
return reg;
}
static const s8 *bpf_get_reg64(const s8 *reg, const s8 *tmp,
struct hppa_jit_context *ctx)
{
return bpf_get_reg64_offset(reg, tmp, 0, ctx);
}
static const s8 *bpf_get_reg64_ref(const s8 *reg, const s8 *tmp,
bool must_load, struct hppa_jit_context *ctx)
{
if (!OPTIMIZE_HPPA)
return bpf_get_reg64(reg, tmp, ctx);
if (is_stacked(hi(reg))) {
if (must_load)
emit(hppa_ldw(REG_SIZE * hi(reg), HPPA_REG_SP, hi(tmp)), ctx);
reg = tmp;
}
REG_SET_SEEN(ctx, hi(reg));
REG_SET_SEEN(ctx, lo(reg));
return reg;
}
static void bpf_put_reg64(const s8 *reg, const s8 *src,
struct hppa_jit_context *ctx)
{
if (is_stacked(hi(reg))) {
emit(hppa_stw(hi(src), REG_SIZE * hi(reg), HPPA_REG_SP), ctx);
emit(hppa_stw(lo(src), REG_SIZE * lo(reg), HPPA_REG_SP), ctx);
}
}
static void bpf_save_R0(struct hppa_jit_context *ctx)
{
bpf_put_reg64(regmap[TMP_REG_R0], regmap[BPF_REG_0], ctx);
}
static void bpf_restore_R0(struct hppa_jit_context *ctx)
{
bpf_get_reg64(regmap[TMP_REG_R0], regmap[BPF_REG_0], ctx);
}
static const s8 *bpf_get_reg32(const s8 *reg, const s8 *tmp,
struct hppa_jit_context *ctx)
{
if (is_stacked(lo(reg))) {
emit(hppa_ldw(REG_SIZE * lo(reg), HPPA_REG_SP, lo(tmp)), ctx);
reg = tmp;
}
REG_SET_SEEN(ctx, lo(reg));
return reg;
}
static const s8 *bpf_get_reg32_ref(const s8 *reg, const s8 *tmp,
struct hppa_jit_context *ctx)
{
if (!OPTIMIZE_HPPA)
return bpf_get_reg32(reg, tmp, ctx);
if (is_stacked(hi(reg))) {
reg = tmp;
}
REG_SET_SEEN(ctx, lo(reg));
return reg;
}
static void bpf_put_reg32(const s8 *reg, const s8 *src,
struct hppa_jit_context *ctx)
{
if (is_stacked(lo(reg))) {
REG_SET_SEEN(ctx, lo(src));
emit(hppa_stw(lo(src), REG_SIZE * lo(reg), HPPA_REG_SP), ctx);
if (1 && !ctx->prog->aux->verifier_zext) {
REG_SET_SEEN(ctx, hi(reg));
emit(hppa_stw(HPPA_REG_ZERO, REG_SIZE * hi(reg), HPPA_REG_SP), ctx);
}
} else if (1 && !ctx->prog->aux->verifier_zext) {
REG_SET_SEEN(ctx, hi(reg));
emit_hppa_copy(HPPA_REG_ZERO, hi(reg), ctx);
}
}
/* extern hppa millicode functions */
extern void $$mulI(void);
extern void $$divU(void);
extern void $$remU(void);
static void emit_call_millicode(void *func, const s8 arg0,
const s8 arg1, u8 opcode, struct hppa_jit_context *ctx)
{
u32 func_addr;
emit_hppa_copy(arg0, HPPA_REG_ARG0, ctx);
emit_hppa_copy(arg1, HPPA_REG_ARG1, ctx);
/* libcgcc overwrites HPPA_REG_RET0/1, save temp. in dest. */
if (arg0 != HPPA_REG_RET1)
bpf_save_R0(ctx);
func_addr = (uintptr_t) dereference_function_descriptor(func);
emit(hppa_ldil(func_addr, HPPA_REG_R31), ctx);
/* skip the following be_l instruction if divisor is zero. */
if (BPF_OP(opcode) == BPF_DIV || BPF_OP(opcode) == BPF_MOD) {
if (BPF_OP(opcode) == BPF_DIV)
emit_hppa_copy(HPPA_REG_ZERO, HPPA_REG_RET1, ctx);
else
emit_hppa_copy(HPPA_REG_ARG0, HPPA_REG_RET1, ctx);
emit(hppa_or_cond(HPPA_REG_ARG1, HPPA_REG_ZERO, 1, 0, HPPA_REG_ZERO), ctx);
}
/* Note: millicode functions use r31 as return pointer instead of rp */
emit(hppa_be_l(im11(func_addr) >> 2, HPPA_REG_R31, NOP_NEXT_INSTR), ctx);
emit(hppa_nop(), ctx); /* this nop is needed here for delay slot */
/* Note: millicode functions return result in RET1, not RET0 */
emit_hppa_copy(HPPA_REG_RET1, arg0, ctx);
/* restore HPPA_REG_RET0/1, temp. save in dest. */
if (arg0 != HPPA_REG_RET1)
bpf_restore_R0(ctx);
}
static void emit_call_libgcc_ll(void *func, const s8 *arg0,
const s8 *arg1, u8 opcode, struct hppa_jit_context *ctx)
{
u32 func_addr;
emit_hppa_copy(lo(arg0), HPPA_REG_ARG0, ctx);
emit_hppa_copy(hi(arg0), HPPA_REG_ARG1, ctx);
emit_hppa_copy(lo(arg1), HPPA_REG_ARG2, ctx);
emit_hppa_copy(hi(arg1), HPPA_REG_ARG3, ctx);
/* libcgcc overwrites HPPA_REG_RET0/_RET1, so keep copy of R0 on stack */
if (hi(arg0) != HPPA_REG_RET0)
bpf_save_R0(ctx);
/* prepare stack */
emit(hppa_ldo(2 * FRAME_SIZE, HPPA_REG_SP, HPPA_REG_SP), ctx);
func_addr = (uintptr_t) dereference_function_descriptor(func);
emit(hppa_ldil(func_addr, HPPA_REG_R31), ctx);
/* zero out the following be_l instruction if divisor is 0 (and set default values) */
if (BPF_OP(opcode) == BPF_DIV || BPF_OP(opcode) == BPF_MOD) {
emit_hppa_copy(HPPA_REG_ZERO, HPPA_REG_RET0, ctx);
if (BPF_OP(opcode) == BPF_DIV)
emit_hppa_copy(HPPA_REG_ZERO, HPPA_REG_RET1, ctx);
else
emit_hppa_copy(HPPA_REG_ARG0, HPPA_REG_RET1, ctx);
emit(hppa_or_cond(HPPA_REG_ARG2, HPPA_REG_ARG3, 1, 0, HPPA_REG_ZERO), ctx);
}
emit(hppa_be_l(im11(func_addr) >> 2, HPPA_REG_R31, EXEC_NEXT_INSTR), ctx);
emit_hppa_copy(HPPA_REG_R31, HPPA_REG_RP, ctx);
/* restore stack */
emit(hppa_ldo(-2 * FRAME_SIZE, HPPA_REG_SP, HPPA_REG_SP), ctx);
emit_hppa_copy(HPPA_REG_RET0, hi(arg0), ctx);
emit_hppa_copy(HPPA_REG_RET1, lo(arg0), ctx);
/* restore HPPA_REG_RET0/_RET1 */
if (hi(arg0) != HPPA_REG_RET0)
bpf_restore_R0(ctx);
}
static void emit_jump(s32 paoff, bool force_far,
struct hppa_jit_context *ctx)
{
unsigned long pc, addr;
/* Note: allocate 2 instructions for jumps if force_far is set. */
if (relative_bits_ok(paoff - HPPA_BRANCH_DISPLACEMENT, 17)) {
/* use BL,short branch followed by nop() */
emit(hppa_bl(paoff - HPPA_BRANCH_DISPLACEMENT, HPPA_REG_ZERO), ctx);
if (force_far)
emit(hppa_nop(), ctx);
return;
}
pc = (uintptr_t) &ctx->insns[ctx->ninsns];
addr = pc + (paoff * HPPA_INSN_SIZE);
emit(hppa_ldil(addr, HPPA_REG_R31), ctx);
emit(hppa_be_l(im11(addr) >> 2, HPPA_REG_R31, NOP_NEXT_INSTR), ctx); // be,l,n addr(sr4,r31), %sr0, %r31
}
static void emit_alu_i64(const s8 *dst, s32 imm,
struct hppa_jit_context *ctx, const u8 op)
{
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *rd;
if (0 && op == BPF_MOV)
rd = bpf_get_reg64_ref(dst, tmp1, false, ctx);
else
rd = bpf_get_reg64(dst, tmp1, ctx);
/* dst = dst OP imm */
switch (op) {
case BPF_MOV:
emit_imm32(rd, imm, ctx);
break;
case BPF_AND:
emit_imm(HPPA_REG_T0, imm, ctx);
emit(hppa_and(lo(rd), HPPA_REG_T0, lo(rd)), ctx);
if (imm >= 0)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
break;
case BPF_OR:
emit_imm(HPPA_REG_T0, imm, ctx);
emit(hppa_or(lo(rd), HPPA_REG_T0, lo(rd)), ctx);
if (imm < 0)
emit_imm(hi(rd), -1, ctx);
break;
case BPF_XOR:
emit_imm(HPPA_REG_T0, imm, ctx);
emit_hppa_xor(lo(rd), HPPA_REG_T0, lo(rd), ctx);
if (imm < 0) {
emit_imm(HPPA_REG_T0, -1, ctx);
emit_hppa_xor(hi(rd), HPPA_REG_T0, hi(rd), ctx);
}
break;
case BPF_LSH:
if (imm == 0)
break;
if (imm > 32) {
imm -= 32;
emit(hppa_zdep(lo(rd), imm, imm, hi(rd)), ctx);
emit_hppa_copy(HPPA_REG_ZERO, lo(rd), ctx);
} else if (imm == 32) {
emit_hppa_copy(lo(rd), hi(rd), ctx);
emit_hppa_copy(HPPA_REG_ZERO, lo(rd), ctx);
} else {
emit(hppa_shd(hi(rd), lo(rd), 32 - imm, hi(rd)), ctx);
emit(hppa_zdep(lo(rd), imm, imm, lo(rd)), ctx);
}
break;
case BPF_RSH:
if (imm == 0)
break;
if (imm > 32) {
imm -= 32;
emit(hppa_shr(hi(rd), imm, lo(rd)), ctx);
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
} else if (imm == 32) {
emit_hppa_copy(hi(rd), lo(rd), ctx);
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
} else {
emit(hppa_shrpw(hi(rd), lo(rd), imm, lo(rd)), ctx);
emit(hppa_shr(hi(rd), imm, hi(rd)), ctx);
}
break;
case BPF_ARSH:
if (imm == 0)
break;
if (imm > 32) {
imm -= 32;
emit(hppa_extrws(hi(rd), 31 - imm, imm, lo(rd)), ctx);
emit(hppa_extrws(hi(rd), 0, 31, hi(rd)), ctx);
} else if (imm == 32) {
emit_hppa_copy(hi(rd), lo(rd), ctx);
emit(hppa_extrws(hi(rd), 0, 31, hi(rd)), ctx);
} else {
emit(hppa_shrpw(hi(rd), lo(rd), imm, lo(rd)), ctx);
emit(hppa_extrws(hi(rd), 31 - imm, imm, hi(rd)), ctx);
}
break;
default:
WARN_ON(1);
}
bpf_put_reg64(dst, rd, ctx);
}
static void emit_alu_i32(const s8 *dst, s32 imm,
struct hppa_jit_context *ctx, const u8 op)
{
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *rd = bpf_get_reg32(dst, tmp1, ctx);
if (op == BPF_MOV)
rd = bpf_get_reg32_ref(dst, tmp1, ctx);
else
rd = bpf_get_reg32(dst, tmp1, ctx);
/* dst = dst OP imm */
switch (op) {
case BPF_MOV:
emit_imm(lo(rd), imm, ctx);
break;
case BPF_ADD:
emit_imm(HPPA_REG_T0, imm, ctx);
emit(hppa_add(lo(rd), HPPA_REG_T0, lo(rd)), ctx);
break;
case BPF_SUB:
emit_imm(HPPA_REG_T0, imm, ctx);
emit(hppa_sub(lo(rd), HPPA_REG_T0, lo(rd)), ctx);
break;
case BPF_AND:
emit_imm(HPPA_REG_T0, imm, ctx);
emit(hppa_and(lo(rd), HPPA_REG_T0, lo(rd)), ctx);
break;
case BPF_OR:
emit_imm(HPPA_REG_T0, imm, ctx);
emit(hppa_or(lo(rd), HPPA_REG_T0, lo(rd)), ctx);
break;
case BPF_XOR:
emit_imm(HPPA_REG_T0, imm, ctx);
emit_hppa_xor(lo(rd), HPPA_REG_T0, lo(rd), ctx);
break;
case BPF_LSH:
if (imm != 0)
emit(hppa_zdep(lo(rd), imm, imm, lo(rd)), ctx);
break;
case BPF_RSH:
if (imm != 0)
emit(hppa_shr(lo(rd), imm, lo(rd)), ctx);
break;
case BPF_ARSH:
if (imm != 0)
emit(hppa_extrws(lo(rd), 31 - imm, imm, lo(rd)), ctx);
break;
default:
WARN_ON(1);
}
bpf_put_reg32(dst, rd, ctx);
}
static void emit_alu_r64(const s8 *dst, const s8 *src,
struct hppa_jit_context *ctx, const u8 op)
{
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *tmp2 = regmap[TMP_REG_2];
const s8 *rd;
const s8 *rs = bpf_get_reg64(src, tmp2, ctx);
if (op == BPF_MOV)
rd = bpf_get_reg64_ref(dst, tmp1, false, ctx);
else
rd = bpf_get_reg64(dst, tmp1, ctx);
/* dst = dst OP src */
switch (op) {
case BPF_MOV:
emit_hppa_copy(lo(rs), lo(rd), ctx);
emit_hppa_copy(hi(rs), hi(rd), ctx);
break;
case BPF_ADD:
emit(hppa_add(lo(rd), lo(rs), lo(rd)), ctx);
emit(hppa_addc(hi(rd), hi(rs), hi(rd)), ctx);
break;
case BPF_SUB:
emit(hppa_sub(lo(rd), lo(rs), lo(rd)), ctx);
emit(hppa_subb(hi(rd), hi(rs), hi(rd)), ctx);
break;
case BPF_AND:
emit(hppa_and(lo(rd), lo(rs), lo(rd)), ctx);
emit(hppa_and(hi(rd), hi(rs), hi(rd)), ctx);
break;
case BPF_OR:
emit(hppa_or(lo(rd), lo(rs), lo(rd)), ctx);
emit(hppa_or(hi(rd), hi(rs), hi(rd)), ctx);
break;
case BPF_XOR:
emit_hppa_xor(lo(rd), lo(rs), lo(rd), ctx);
emit_hppa_xor(hi(rd), hi(rs), hi(rd), ctx);
break;
case BPF_MUL:
emit_call_libgcc_ll(__muldi3, rd, rs, op, ctx);
break;
case BPF_DIV:
emit_call_libgcc_ll(&hppa_div64, rd, rs, op, ctx);
break;
case BPF_MOD:
emit_call_libgcc_ll(&hppa_div64_rem, rd, rs, op, ctx);
break;
case BPF_LSH:
emit_call_libgcc_ll(__ashldi3, rd, rs, op, ctx);
break;
case BPF_RSH:
emit_call_libgcc_ll(__lshrdi3, rd, rs, op, ctx);
break;
case BPF_ARSH:
emit_call_libgcc_ll(__ashrdi3, rd, rs, op, ctx);
break;
case BPF_NEG:
emit(hppa_sub(HPPA_REG_ZERO, lo(rd), lo(rd)), ctx);
emit(hppa_subb(HPPA_REG_ZERO, hi(rd), hi(rd)), ctx);
break;
default:
WARN_ON(1);
}
bpf_put_reg64(dst, rd, ctx);
}
static void emit_alu_r32(const s8 *dst, const s8 *src,
struct hppa_jit_context *ctx, const u8 op)
{
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *tmp2 = regmap[TMP_REG_2];
const s8 *rd;
const s8 *rs = bpf_get_reg32(src, tmp2, ctx);
if (op == BPF_MOV)
rd = bpf_get_reg32_ref(dst, tmp1, ctx);
else
rd = bpf_get_reg32(dst, tmp1, ctx);
/* dst = dst OP src */
switch (op) {
case BPF_MOV:
emit_hppa_copy(lo(rs), lo(rd), ctx);
break;
case BPF_ADD:
emit(hppa_add(lo(rd), lo(rs), lo(rd)), ctx);
break;
case BPF_SUB:
emit(hppa_sub(lo(rd), lo(rs), lo(rd)), ctx);
break;
case BPF_AND:
emit(hppa_and(lo(rd), lo(rs), lo(rd)), ctx);
break;
case BPF_OR:
emit(hppa_or(lo(rd), lo(rs), lo(rd)), ctx);
break;
case BPF_XOR:
emit_hppa_xor(lo(rd), lo(rs), lo(rd), ctx);
break;
case BPF_MUL:
emit_call_millicode($$mulI, lo(rd), lo(rs), op, ctx);
break;
case BPF_DIV:
emit_call_millicode($$divU, lo(rd), lo(rs), op, ctx);
break;
case BPF_MOD:
emit_call_millicode($$remU, lo(rd), lo(rs), op, ctx);
break;
case BPF_LSH:
emit(hppa_subi(0x1f, lo(rs), HPPA_REG_T0), ctx);
emit(hppa_mtsar(HPPA_REG_T0), ctx);
emit(hppa_depwz_sar(lo(rd), lo(rd)), ctx);
break;
case BPF_RSH:
emit(hppa_mtsar(lo(rs)), ctx);
emit(hppa_shrpw_sar(lo(rd), lo(rd)), ctx);
break;
case BPF_ARSH: /* sign extending arithmetic shift right */
// emit(hppa_beq(lo(rs), HPPA_REG_ZERO, 2), ctx);
emit(hppa_subi(0x1f, lo(rs), HPPA_REG_T0), ctx);
emit(hppa_mtsar(HPPA_REG_T0), ctx);
emit(hppa_extrws_sar(lo(rd), lo(rd)), ctx);
break;
case BPF_NEG:
emit(hppa_sub(HPPA_REG_ZERO, lo(rd), lo(rd)), ctx); // sub r0,rd,rd
break;
default:
WARN_ON(1);
}
bpf_put_reg32(dst, rd, ctx);
}
static int emit_branch_r64(const s8 *src1, const s8 *src2, s32 paoff,
struct hppa_jit_context *ctx, const u8 op)
{
int e, s = ctx->ninsns;
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *tmp2 = regmap[TMP_REG_2];
const s8 *rs1 = bpf_get_reg64(src1, tmp1, ctx);
const s8 *rs2 = bpf_get_reg64(src2, tmp2, ctx);
/*
* NO_JUMP skips over the rest of the instructions and the
* emit_jump, meaning the BPF branch is not taken.
* JUMP skips directly to the emit_jump, meaning
* the BPF branch is taken.
*
* The fallthrough case results in the BPF branch being taken.
*/
#define NO_JUMP(idx) (2 + (idx) - 1)
#define JUMP(idx) (0 + (idx) - 1)
switch (op) {
case BPF_JEQ:
emit(hppa_bne(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bne(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JGT:
emit(hppa_bgtu(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_bltu(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bleu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JLT:
emit(hppa_bltu(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_bgtu(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bgeu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JGE:
emit(hppa_bgtu(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_bltu(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bltu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JLE:
emit(hppa_bltu(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_bgtu(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bgtu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JNE:
emit(hppa_bne(hi(rs1), hi(rs2), JUMP(1)), ctx);
emit(hppa_beq(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JSGT:
emit(hppa_bgt(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_blt(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bleu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JSLT:
emit(hppa_blt(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_bgt(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bgeu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JSGE:
emit(hppa_bgt(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_blt(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bltu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JSLE:
emit(hppa_blt(hi(rs1), hi(rs2), JUMP(2)), ctx);
emit(hppa_bgt(hi(rs1), hi(rs2), NO_JUMP(1)), ctx);
emit(hppa_bgtu(lo(rs1), lo(rs2), NO_JUMP(0)), ctx);
break;
case BPF_JSET:
emit(hppa_and(hi(rs1), hi(rs2), HPPA_REG_T0), ctx);
emit(hppa_and(lo(rs1), lo(rs2), HPPA_REG_T1), ctx);
emit(hppa_bne(HPPA_REG_T0, HPPA_REG_ZERO, JUMP(1)), ctx);
emit(hppa_beq(HPPA_REG_T1, HPPA_REG_ZERO, NO_JUMP(0)), ctx);
break;
default:
WARN_ON(1);
}
#undef NO_JUMP
#undef JUMP
e = ctx->ninsns;
/* Adjust for extra insns. */
paoff -= (e - s);
emit_jump(paoff, true, ctx);
return 0;
}
static int emit_bcc(u8 op, u8 rd, u8 rs, int paoff, struct hppa_jit_context *ctx)
{
int e, s;
bool far = false;
int off;
if (op == BPF_JSET) {
/*
* BPF_JSET is a special case: it has no inverse so we always
* treat it as a far branch.
*/
emit(hppa_and(rd, rs, HPPA_REG_T0), ctx);
paoff -= 1; /* reduce offset due to hppa_and() above */
rd = HPPA_REG_T0;
rs = HPPA_REG_ZERO;
op = BPF_JNE;
}
s = ctx->ninsns;
if (!relative_bits_ok(paoff - HPPA_BRANCH_DISPLACEMENT, 12)) {
op = invert_bpf_cond(op);
far = true;
}
/*
* For a far branch, the condition is negated and we jump over the
* branch itself, and the three instructions from emit_jump.
* For a near branch, just use paoff.
*/
off = far ? (HPPA_BRANCH_DISPLACEMENT - 1) : paoff - HPPA_BRANCH_DISPLACEMENT;
switch (op) {
/* IF (dst COND src) JUMP off */
case BPF_JEQ:
emit(hppa_beq(rd, rs, off), ctx);
break;
case BPF_JGT:
emit(hppa_bgtu(rd, rs, off), ctx);
break;
case BPF_JLT:
emit(hppa_bltu(rd, rs, off), ctx);
break;
case BPF_JGE:
emit(hppa_bgeu(rd, rs, off), ctx);
break;
case BPF_JLE:
emit(hppa_bleu(rd, rs, off), ctx);
break;
case BPF_JNE:
emit(hppa_bne(rd, rs, off), ctx);
break;
case BPF_JSGT:
emit(hppa_bgt(rd, rs, off), ctx);
break;
case BPF_JSLT:
emit(hppa_blt(rd, rs, off), ctx);
break;
case BPF_JSGE:
emit(hppa_bge(rd, rs, off), ctx);
break;
case BPF_JSLE:
emit(hppa_ble(rd, rs, off), ctx);
break;
default:
WARN_ON(1);
}
if (far) {
e = ctx->ninsns;
/* Adjust for extra insns. */
paoff -= (e - s);
emit_jump(paoff, true, ctx);
}
return 0;
}
static int emit_branch_r32(const s8 *src1, const s8 *src2, s32 paoff,
struct hppa_jit_context *ctx, const u8 op)
{
int e, s = ctx->ninsns;
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *tmp2 = regmap[TMP_REG_2];
const s8 *rs1 = bpf_get_reg32(src1, tmp1, ctx);
const s8 *rs2 = bpf_get_reg32(src2, tmp2, ctx);
e = ctx->ninsns;
/* Adjust for extra insns. */
paoff -= (e - s);
if (emit_bcc(op, lo(rs1), lo(rs2), paoff, ctx))
return -1;
return 0;
}
static void emit_call(bool fixed, u64 addr, struct hppa_jit_context *ctx)
{
const s8 *tmp = regmap[TMP_REG_1];
const s8 *r0 = regmap[BPF_REG_0];
const s8 *reg;
const int offset_sp = 2 * STACK_ALIGN;
/* prepare stack */
emit(hppa_ldo(offset_sp, HPPA_REG_SP, HPPA_REG_SP), ctx);
/* load R1 & R2 in registers, R3-R5 to stack. */
reg = bpf_get_reg64_offset(regmap[BPF_REG_5], tmp, offset_sp, ctx);
emit(hppa_stw(hi(reg), -0x48, HPPA_REG_SP), ctx);
emit(hppa_stw(lo(reg), -0x44, HPPA_REG_SP), ctx);
reg = bpf_get_reg64_offset(regmap[BPF_REG_4], tmp, offset_sp, ctx);
emit(hppa_stw(hi(reg), -0x40, HPPA_REG_SP), ctx);
emit(hppa_stw(lo(reg), -0x3c, HPPA_REG_SP), ctx);
reg = bpf_get_reg64_offset(regmap[BPF_REG_3], tmp, offset_sp, ctx);
emit(hppa_stw(hi(reg), -0x38, HPPA_REG_SP), ctx);
emit(hppa_stw(lo(reg), -0x34, HPPA_REG_SP), ctx);
reg = bpf_get_reg64_offset(regmap[BPF_REG_2], tmp, offset_sp, ctx);
emit_hppa_copy(hi(reg), HPPA_REG_ARG3, ctx);
emit_hppa_copy(lo(reg), HPPA_REG_ARG2, ctx);
reg = bpf_get_reg64_offset(regmap[BPF_REG_1], tmp, offset_sp, ctx);
emit_hppa_copy(hi(reg), HPPA_REG_ARG1, ctx);
emit_hppa_copy(lo(reg), HPPA_REG_ARG0, ctx);
/* backup TCC */
if (REG_WAS_SEEN(ctx, HPPA_REG_TCC))
emit(hppa_copy(HPPA_REG_TCC, HPPA_REG_TCC_SAVED), ctx);
/*
* Use ldil() to load absolute address. Don't use emit_imm as the
* number of emitted instructions should not depend on the value of
* addr.
*/
emit(hppa_ldil(addr, HPPA_REG_R31), ctx);
emit(hppa_be_l(im11(addr) >> 2, HPPA_REG_R31, EXEC_NEXT_INSTR), ctx);
/* set return address in delay slot */
emit_hppa_copy(HPPA_REG_R31, HPPA_REG_RP, ctx);
/* restore TCC */
if (REG_WAS_SEEN(ctx, HPPA_REG_TCC))
emit(hppa_copy(HPPA_REG_TCC_SAVED, HPPA_REG_TCC), ctx);
/* restore stack */
emit(hppa_ldo(-offset_sp, HPPA_REG_SP, HPPA_REG_SP), ctx);
/* set return value. */
emit_hppa_copy(HPPA_REG_RET0, hi(r0), ctx);
emit_hppa_copy(HPPA_REG_RET1, lo(r0), ctx);
}
static int emit_bpf_tail_call(int insn, struct hppa_jit_context *ctx)
{
/*
* R1 -> &ctx
* R2 -> &array
* R3 -> index
*/
int off;
const s8 *arr_reg = regmap[BPF_REG_2];
const s8 *idx_reg = regmap[BPF_REG_3];
struct bpf_array bpfa;
struct bpf_prog bpfp;
/* get address of TCC main exit function for error case into rp */
emit(EXIT_PTR_LOAD(HPPA_REG_RP), ctx);
/* max_entries = array->map.max_entries; */
off = offsetof(struct bpf_array, map.max_entries);
BUILD_BUG_ON(sizeof(bpfa.map.max_entries) != 4);
emit(hppa_ldw(off, lo(arr_reg), HPPA_REG_T1), ctx);
/*
* if (index >= max_entries)
* goto out;
*/
emit(hppa_bltu(lo(idx_reg), HPPA_REG_T1, 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/*
* if (--tcc < 0)
* goto out;
*/
REG_FORCE_SEEN(ctx, HPPA_REG_TCC);
emit(hppa_ldo(-1, HPPA_REG_TCC, HPPA_REG_TCC), ctx);
emit(hppa_bge(HPPA_REG_TCC, HPPA_REG_ZERO, 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/*
* prog = array->ptrs[index];
* if (!prog)
* goto out;
*/
BUILD_BUG_ON(sizeof(bpfa.ptrs[0]) != 4);
emit(hppa_sh2add(lo(idx_reg), lo(arr_reg), HPPA_REG_T0), ctx);
off = offsetof(struct bpf_array, ptrs);
BUILD_BUG_ON(!relative_bits_ok(off, 11));
emit(hppa_ldw(off, HPPA_REG_T0, HPPA_REG_T0), ctx);
emit(hppa_bne(HPPA_REG_T0, HPPA_REG_ZERO, 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/*
* tcc = temp_tcc;
* goto *(prog->bpf_func + 4);
*/
off = offsetof(struct bpf_prog, bpf_func);
BUILD_BUG_ON(!relative_bits_ok(off, 11));
BUILD_BUG_ON(sizeof(bpfp.bpf_func) != 4);
emit(hppa_ldw(off, HPPA_REG_T0, HPPA_REG_T0), ctx);
/* Epilogue jumps to *(t0 + 4). */
__build_epilogue(true, ctx);
return 0;
}
static int emit_load_r64(const s8 *dst, const s8 *src, s16 off,
struct hppa_jit_context *ctx, const u8 size)
{
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *tmp2 = regmap[TMP_REG_2];
const s8 *rd = bpf_get_reg64_ref(dst, tmp1, ctx->prog->aux->verifier_zext, ctx);
const s8 *rs = bpf_get_reg64(src, tmp2, ctx);
s8 srcreg;
/* need to calculate address since offset does not fit in 14 bits? */
if (relative_bits_ok(off, 14))
srcreg = lo(rs);
else {
/* need to use R1 here, since addil puts result into R1 */
srcreg = HPPA_REG_R1;
emit(hppa_addil(off, lo(rs)), ctx);
off = im11(off);
}
/* LDX: dst = *(size *)(src + off) */
switch (size) {
case BPF_B:
emit(hppa_ldb(off + 0, srcreg, lo(rd)), ctx);
if (!ctx->prog->aux->verifier_zext)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
break;
case BPF_H:
emit(hppa_ldh(off + 0, srcreg, lo(rd)), ctx);
if (!ctx->prog->aux->verifier_zext)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
break;
case BPF_W:
emit(hppa_ldw(off + 0, srcreg, lo(rd)), ctx);
if (!ctx->prog->aux->verifier_zext)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
break;
case BPF_DW:
emit(hppa_ldw(off + 0, srcreg, hi(rd)), ctx);
emit(hppa_ldw(off + 4, srcreg, lo(rd)), ctx);
break;
}
bpf_put_reg64(dst, rd, ctx);
return 0;
}
static int emit_store_r64(const s8 *dst, const s8 *src, s16 off,
struct hppa_jit_context *ctx, const u8 size,
const u8 mode)
{
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *tmp2 = regmap[TMP_REG_2];
const s8 *rd = bpf_get_reg64(dst, tmp1, ctx);
const s8 *rs = bpf_get_reg64(src, tmp2, ctx);
s8 dstreg;
/* need to calculate address since offset does not fit in 14 bits? */
if (relative_bits_ok(off, 14))
dstreg = lo(rd);
else {
/* need to use R1 here, since addil puts result into R1 */
dstreg = HPPA_REG_R1;
emit(hppa_addil(off, lo(rd)), ctx);
off = im11(off);
}
/* ST: *(size *)(dst + off) = imm */
switch (size) {
case BPF_B:
emit(hppa_stb(lo(rs), off + 0, dstreg), ctx);
break;
case BPF_H:
emit(hppa_sth(lo(rs), off + 0, dstreg), ctx);
break;
case BPF_W:
emit(hppa_stw(lo(rs), off + 0, dstreg), ctx);
break;
case BPF_DW:
emit(hppa_stw(hi(rs), off + 0, dstreg), ctx);
emit(hppa_stw(lo(rs), off + 4, dstreg), ctx);
break;
}
return 0;
}
static void emit_rev16(const s8 rd, struct hppa_jit_context *ctx)
{
emit(hppa_extru(rd, 23, 8, HPPA_REG_T1), ctx);
emit(hppa_depwz(rd, 23, 8, HPPA_REG_T1), ctx);
emit(hppa_extru(HPPA_REG_T1, 31, 16, rd), ctx);
}
static void emit_rev32(const s8 rs, const s8 rd, struct hppa_jit_context *ctx)
{
emit(hppa_shrpw(rs, rs, 16, HPPA_REG_T1), ctx);
emit(hppa_depwz(HPPA_REG_T1, 15, 8, HPPA_REG_T1), ctx);
emit(hppa_shrpw(rs, HPPA_REG_T1, 8, rd), ctx);
}
static void emit_zext64(const s8 *dst, struct hppa_jit_context *ctx)
{
const s8 *rd;
const s8 *tmp1 = regmap[TMP_REG_1];
rd = bpf_get_reg64(dst, tmp1, ctx);
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
bpf_put_reg64(dst, rd, ctx);
}
int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,
bool extra_pass)
{
bool is64 = BPF_CLASS(insn->code) == BPF_ALU64 ||
BPF_CLASS(insn->code) == BPF_JMP;
int s, e, paoff, i = insn - ctx->prog->insnsi;
u8 code = insn->code;
s16 off = insn->off;
s32 imm = insn->imm;
const s8 *dst = regmap[insn->dst_reg];
const s8 *src = regmap[insn->src_reg];
const s8 *tmp1 = regmap[TMP_REG_1];
const s8 *tmp2 = regmap[TMP_REG_2];
if (0) printk("CLASS %03d CODE %#02x ALU64:%d BPF_SIZE %#02x "
"BPF_CODE %#02x src_reg %d dst_reg %d\n",
BPF_CLASS(code), code, (code & BPF_ALU64) ? 1:0, BPF_SIZE(code),
BPF_OP(code), insn->src_reg, insn->dst_reg);
switch (code) {
/* dst = src */
case BPF_ALU64 | BPF_MOV | BPF_X:
case BPF_ALU64 | BPF_ADD | BPF_X:
case BPF_ALU64 | BPF_ADD | BPF_K:
case BPF_ALU64 | BPF_SUB | BPF_X:
case BPF_ALU64 | BPF_SUB | BPF_K:
case BPF_ALU64 | BPF_AND | BPF_X:
case BPF_ALU64 | BPF_OR | BPF_X:
case BPF_ALU64 | BPF_XOR | BPF_X:
case BPF_ALU64 | BPF_MUL | BPF_X:
case BPF_ALU64 | BPF_MUL | BPF_K:
case BPF_ALU64 | BPF_DIV | BPF_X:
case BPF_ALU64 | BPF_DIV | BPF_K:
case BPF_ALU64 | BPF_MOD | BPF_X:
case BPF_ALU64 | BPF_MOD | BPF_K:
case BPF_ALU64 | BPF_LSH | BPF_X:
case BPF_ALU64 | BPF_RSH | BPF_X:
case BPF_ALU64 | BPF_ARSH | BPF_X:
if (BPF_SRC(code) == BPF_K) {
emit_imm32(tmp2, imm, ctx);
src = tmp2;
}
emit_alu_r64(dst, src, ctx, BPF_OP(code));
break;
/* dst = -dst */
case BPF_ALU64 | BPF_NEG:
emit_alu_r64(dst, tmp2, ctx, BPF_OP(code));
break;
case BPF_ALU64 | BPF_MOV | BPF_K:
case BPF_ALU64 | BPF_AND | BPF_K:
case BPF_ALU64 | BPF_OR | BPF_K:
case BPF_ALU64 | BPF_XOR | BPF_K:
case BPF_ALU64 | BPF_LSH | BPF_K:
case BPF_ALU64 | BPF_RSH | BPF_K:
case BPF_ALU64 | BPF_ARSH | BPF_K:
emit_alu_i64(dst, imm, ctx, BPF_OP(code));
break;
case BPF_ALU | BPF_MOV | BPF_X:
if (imm == 1) {
/* Special mov32 for zext. */
emit_zext64(dst, ctx);
break;
}
fallthrough;
/* dst = dst OP src */
case BPF_ALU | BPF_ADD | BPF_X:
case BPF_ALU | BPF_SUB | BPF_X:
case BPF_ALU | BPF_AND | BPF_X:
case BPF_ALU | BPF_OR | BPF_X:
case BPF_ALU | BPF_XOR | BPF_X:
case BPF_ALU | BPF_MUL | BPF_X:
case BPF_ALU | BPF_MUL | BPF_K:
case BPF_ALU | BPF_DIV | BPF_X:
case BPF_ALU | BPF_DIV | BPF_K:
case BPF_ALU | BPF_MOD | BPF_X:
case BPF_ALU | BPF_MOD | BPF_K:
case BPF_ALU | BPF_LSH | BPF_X:
case BPF_ALU | BPF_RSH | BPF_X:
case BPF_ALU | BPF_ARSH | BPF_X:
if (BPF_SRC(code) == BPF_K) {
emit_imm32(tmp2, imm, ctx);
src = tmp2;
}
emit_alu_r32(dst, src, ctx, BPF_OP(code));
break;
/* dst = dst OP imm */
case BPF_ALU | BPF_MOV | BPF_K:
case BPF_ALU | BPF_ADD | BPF_K:
case BPF_ALU | BPF_SUB | BPF_K:
case BPF_ALU | BPF_AND | BPF_K:
case BPF_ALU | BPF_OR | BPF_K:
case BPF_ALU | BPF_XOR | BPF_K:
case BPF_ALU | BPF_LSH | BPF_K:
case BPF_ALU | BPF_RSH | BPF_K:
case BPF_ALU | BPF_ARSH | BPF_K:
/*
* mul,div,mod are handled in the BPF_X case.
*/
emit_alu_i32(dst, imm, ctx, BPF_OP(code));
break;
/* dst = -dst */
case BPF_ALU | BPF_NEG:
/*
* src is ignored---choose tmp2 as a dummy register since it
* is not on the stack.
*/
emit_alu_r32(dst, tmp2, ctx, BPF_OP(code));
break;
/* dst = BSWAP##imm(dst) */
case BPF_ALU | BPF_END | BPF_FROM_BE:
{
const s8 *rd = bpf_get_reg64(dst, tmp1, ctx);
switch (imm) {
case 16:
/* zero-extend 16 bits into 64 bits */
emit(hppa_extru(lo(rd), 31, 16, lo(rd)), ctx);
fallthrough;
case 32:
/* zero-extend 32 bits into 64 bits */
if (!ctx->prog->aux->verifier_zext)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
break;
case 64:
/* Do nothing. */
break;
default:
pr_err("bpf-jit: BPF_END imm %d invalid\n", imm);
return -1;
}
bpf_put_reg64(dst, rd, ctx);
break;
}
case BPF_ALU | BPF_END | BPF_FROM_LE:
{
const s8 *rd = bpf_get_reg64(dst, tmp1, ctx);
switch (imm) {
case 16:
emit_rev16(lo(rd), ctx);
if (!ctx->prog->aux->verifier_zext)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
break;
case 32:
emit_rev32(lo(rd), lo(rd), ctx);
if (!ctx->prog->aux->verifier_zext)
emit_hppa_copy(HPPA_REG_ZERO, hi(rd), ctx);
break;
case 64:
/* Swap upper and lower halves, then each half. */
emit_hppa_copy(hi(rd), HPPA_REG_T0, ctx);
emit_rev32(lo(rd), hi(rd), ctx);
emit_rev32(HPPA_REG_T0, lo(rd), ctx);
break;
default:
pr_err("bpf-jit: BPF_END imm %d invalid\n", imm);
return -1;
}
bpf_put_reg64(dst, rd, ctx);
break;
}
/* JUMP off */
case BPF_JMP | BPF_JA:
paoff = hppa_offset(i, off, ctx);
emit_jump(paoff, false, ctx);
break;
/* function call */
case BPF_JMP | BPF_CALL:
{
bool fixed;
int ret;
u64 addr;
ret = bpf_jit_get_func_addr(ctx->prog, insn, extra_pass, &addr,
&fixed);
if (ret < 0)
return ret;
emit_call(fixed, addr, ctx);
break;
}
/* tail call */
case BPF_JMP | BPF_TAIL_CALL:
REG_SET_SEEN_ALL(ctx);
if (emit_bpf_tail_call(i, ctx))
return -1;
break;
/* IF (dst COND imm) JUMP off */
case BPF_JMP | BPF_JEQ | BPF_X:
case BPF_JMP | BPF_JEQ | BPF_K:
case BPF_JMP32 | BPF_JEQ | BPF_X:
case BPF_JMP32 | BPF_JEQ | BPF_K:
case BPF_JMP | BPF_JNE | BPF_X:
case BPF_JMP | BPF_JNE | BPF_K:
case BPF_JMP32 | BPF_JNE | BPF_X:
case BPF_JMP32 | BPF_JNE | BPF_K:
case BPF_JMP | BPF_JLE | BPF_X:
case BPF_JMP | BPF_JLE | BPF_K:
case BPF_JMP32 | BPF_JLE | BPF_X:
case BPF_JMP32 | BPF_JLE | BPF_K:
case BPF_JMP | BPF_JLT | BPF_X:
case BPF_JMP | BPF_JLT | BPF_K:
case BPF_JMP32 | BPF_JLT | BPF_X:
case BPF_JMP32 | BPF_JLT | BPF_K:
case BPF_JMP | BPF_JGE | BPF_X:
case BPF_JMP | BPF_JGE | BPF_K:
case BPF_JMP32 | BPF_JGE | BPF_X:
case BPF_JMP32 | BPF_JGE | BPF_K:
case BPF_JMP | BPF_JGT | BPF_X:
case BPF_JMP | BPF_JGT | BPF_K:
case BPF_JMP32 | BPF_JGT | BPF_X:
case BPF_JMP32 | BPF_JGT | BPF_K:
case BPF_JMP | BPF_JSLE | BPF_X:
case BPF_JMP | BPF_JSLE | BPF_K:
case BPF_JMP32 | BPF_JSLE | BPF_X:
case BPF_JMP32 | BPF_JSLE | BPF_K:
case BPF_JMP | BPF_JSLT | BPF_X:
case BPF_JMP | BPF_JSLT | BPF_K:
case BPF_JMP32 | BPF_JSLT | BPF_X:
case BPF_JMP32 | BPF_JSLT | BPF_K:
case BPF_JMP | BPF_JSGE | BPF_X:
case BPF_JMP | BPF_JSGE | BPF_K:
case BPF_JMP32 | BPF_JSGE | BPF_X:
case BPF_JMP32 | BPF_JSGE | BPF_K:
case BPF_JMP | BPF_JSGT | BPF_X:
case BPF_JMP | BPF_JSGT | BPF_K:
case BPF_JMP32 | BPF_JSGT | BPF_X:
case BPF_JMP32 | BPF_JSGT | BPF_K:
case BPF_JMP | BPF_JSET | BPF_X:
case BPF_JMP | BPF_JSET | BPF_K:
case BPF_JMP32 | BPF_JSET | BPF_X:
case BPF_JMP32 | BPF_JSET | BPF_K:
paoff = hppa_offset(i, off, ctx);
if (BPF_SRC(code) == BPF_K) {
s = ctx->ninsns;
emit_imm32(tmp2, imm, ctx);
src = tmp2;
e = ctx->ninsns;
paoff -= (e - s);
}
if (is64)
emit_branch_r64(dst, src, paoff, ctx, BPF_OP(code));
else
emit_branch_r32(dst, src, paoff, ctx, BPF_OP(code));
break;
/* function return */
case BPF_JMP | BPF_EXIT:
if (i == ctx->prog->len - 1)
break;
/* load epilogue function pointer and jump to it. */
emit(EXIT_PTR_LOAD(HPPA_REG_RP), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
break;
/* dst = imm64 */
case BPF_LD | BPF_IMM | BPF_DW:
{
struct bpf_insn insn1 = insn[1];
u32 upper = insn1.imm;
u32 lower = imm;
const s8 *rd = bpf_get_reg64_ref(dst, tmp1, false, ctx);
if (0 && bpf_pseudo_func(insn)) {
WARN_ON(upper); /* we are 32-bit! */
upper = 0;
lower = (uintptr_t) dereference_function_descriptor(lower);
}
emit_imm64(rd, upper, lower, ctx);
bpf_put_reg64(dst, rd, ctx);
return 1;
}
/* LDX: dst = *(size *)(src + off) */
case BPF_LDX | BPF_MEM | BPF_B:
case BPF_LDX | BPF_MEM | BPF_H:
case BPF_LDX | BPF_MEM | BPF_W:
case BPF_LDX | BPF_MEM | BPF_DW:
if (emit_load_r64(dst, src, off, ctx, BPF_SIZE(code)))
return -1;
break;
/* speculation barrier */
case BPF_ST | BPF_NOSPEC:
break;
/* ST: *(size *)(dst + off) = imm */
case BPF_ST | BPF_MEM | BPF_B:
case BPF_ST | BPF_MEM | BPF_H:
case BPF_ST | BPF_MEM | BPF_W:
case BPF_ST | BPF_MEM | BPF_DW:
case BPF_STX | BPF_MEM | BPF_B:
case BPF_STX | BPF_MEM | BPF_H:
case BPF_STX | BPF_MEM | BPF_W:
case BPF_STX | BPF_MEM | BPF_DW:
if (BPF_CLASS(code) == BPF_ST) {
emit_imm32(tmp2, imm, ctx);
src = tmp2;
}
if (emit_store_r64(dst, src, off, ctx, BPF_SIZE(code),
BPF_MODE(code)))
return -1;
break;
case BPF_STX | BPF_ATOMIC | BPF_W:
case BPF_STX | BPF_ATOMIC | BPF_DW:
pr_info_once(
"bpf-jit: not supported: atomic operation %02x ***\n",
insn->imm);
return -EFAULT;
default:
pr_err("bpf-jit: unknown opcode %02x\n", code);
return -EINVAL;
}
return 0;
}
void bpf_jit_build_prologue(struct hppa_jit_context *ctx)
{
const s8 *tmp = regmap[TMP_REG_1];
const s8 *dst, *reg;
int stack_adjust = 0;
int i;
unsigned long addr;
int bpf_stack_adjust;
/*
* stack on hppa grows up, so if tail calls are used we need to
* allocate the maximum stack size
*/
if (REG_ALL_SEEN(ctx))
bpf_stack_adjust = MAX_BPF_STACK;
else
bpf_stack_adjust = ctx->prog->aux->stack_depth;
bpf_stack_adjust = round_up(bpf_stack_adjust, STACK_ALIGN);
/* make space for callee-saved registers. */
stack_adjust += NR_SAVED_REGISTERS * REG_SIZE;
/* make space for BPF registers on stack. */
stack_adjust += BPF_JIT_SCRATCH_REGS * REG_SIZE;
/* make space for BPF stack. */
stack_adjust += bpf_stack_adjust;
/* round up for stack alignment. */
stack_adjust = round_up(stack_adjust, STACK_ALIGN);
/*
* The first instruction sets the tail-call-counter (TCC) register.
* This instruction is skipped by tail calls.
* Use a temporary register instead of a caller-saved register initially.
*/
emit(hppa_ldi(MAX_TAIL_CALL_CNT, HPPA_REG_TCC_IN_INIT), ctx);
/*
* skip all initializations when called as BPF TAIL call.
*/
emit(hppa_ldi(MAX_TAIL_CALL_CNT, HPPA_REG_R1), ctx);
emit(hppa_bne(HPPA_REG_TCC_IN_INIT, HPPA_REG_R1, ctx->prologue_len - 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
/* set up hppa stack frame. */
emit_hppa_copy(HPPA_REG_SP, HPPA_REG_R1, ctx); // copy sp,r1 (=prev_sp)
emit(hppa_ldo(stack_adjust, HPPA_REG_SP, HPPA_REG_SP), ctx); // ldo stack_adjust(sp),sp (increase stack)
emit(hppa_stw(HPPA_REG_R1, -REG_SIZE, HPPA_REG_SP), ctx); // stw prev_sp,-0x04(sp)
emit(hppa_stw(HPPA_REG_RP, -0x14, HPPA_REG_SP), ctx); // stw rp,-0x14(sp)
REG_FORCE_SEEN(ctx, HPPA_REG_T0);
REG_FORCE_SEEN(ctx, HPPA_REG_T1);
REG_FORCE_SEEN(ctx, HPPA_REG_T2);
REG_FORCE_SEEN(ctx, HPPA_REG_T3);
REG_FORCE_SEEN(ctx, HPPA_REG_T4);
REG_FORCE_SEEN(ctx, HPPA_REG_T5);
/* save callee-save registers. */
for (i = 3; i <= 18; i++) {
if (OPTIMIZE_HPPA && !REG_WAS_SEEN(ctx, HPPA_R(i)))
continue;
emit(hppa_stw(HPPA_R(i), -REG_SIZE * (8 + (i-3)), HPPA_REG_SP), ctx); // stw ri,-save_area(sp)
}
/*
* now really set the tail call counter (TCC) register.
*/
if (REG_WAS_SEEN(ctx, HPPA_REG_TCC))
emit(hppa_ldi(MAX_TAIL_CALL_CNT, HPPA_REG_TCC), ctx);
/*
* save epilogue function pointer for outer TCC call chain.
* The main TCC call stores the final RP on stack.
*/
addr = (uintptr_t) &ctx->insns[ctx->epilogue_offset];
/* skip first two instructions of exit function, which jump to exit */
addr += 2 * HPPA_INSN_SIZE;
emit(hppa_ldil(addr, HPPA_REG_T2), ctx);
emit(hppa_ldo(im11(addr), HPPA_REG_T2, HPPA_REG_T2), ctx);
emit(EXIT_PTR_STORE(HPPA_REG_T2), ctx);
/* load R1 & R2 from registers, R3-R5 from stack. */
/* use HPPA_REG_R1 which holds the old stack value */
dst = regmap[BPF_REG_5];
reg = bpf_get_reg64_ref(dst, tmp, false, ctx);
if (REG_WAS_SEEN(ctx, lo(reg)) | REG_WAS_SEEN(ctx, hi(reg))) {
if (REG_WAS_SEEN(ctx, hi(reg)))
emit(hppa_ldw(-0x48, HPPA_REG_R1, hi(reg)), ctx);
if (REG_WAS_SEEN(ctx, lo(reg)))
emit(hppa_ldw(-0x44, HPPA_REG_R1, lo(reg)), ctx);
bpf_put_reg64(dst, tmp, ctx);
}
dst = regmap[BPF_REG_4];
reg = bpf_get_reg64_ref(dst, tmp, false, ctx);
if (REG_WAS_SEEN(ctx, lo(reg)) | REG_WAS_SEEN(ctx, hi(reg))) {
if (REG_WAS_SEEN(ctx, hi(reg)))
emit(hppa_ldw(-0x40, HPPA_REG_R1, hi(reg)), ctx);
if (REG_WAS_SEEN(ctx, lo(reg)))
emit(hppa_ldw(-0x3c, HPPA_REG_R1, lo(reg)), ctx);
bpf_put_reg64(dst, tmp, ctx);
}
dst = regmap[BPF_REG_3];
reg = bpf_get_reg64_ref(dst, tmp, false, ctx);
if (REG_WAS_SEEN(ctx, lo(reg)) | REG_WAS_SEEN(ctx, hi(reg))) {
if (REG_WAS_SEEN(ctx, hi(reg)))
emit(hppa_ldw(-0x38, HPPA_REG_R1, hi(reg)), ctx);
if (REG_WAS_SEEN(ctx, lo(reg)))
emit(hppa_ldw(-0x34, HPPA_REG_R1, lo(reg)), ctx);
bpf_put_reg64(dst, tmp, ctx);
}
dst = regmap[BPF_REG_2];
reg = bpf_get_reg64_ref(dst, tmp, false, ctx);
if (REG_WAS_SEEN(ctx, lo(reg)) | REG_WAS_SEEN(ctx, hi(reg))) {
if (REG_WAS_SEEN(ctx, hi(reg)))
emit_hppa_copy(HPPA_REG_ARG3, hi(reg), ctx);
if (REG_WAS_SEEN(ctx, lo(reg)))
emit_hppa_copy(HPPA_REG_ARG2, lo(reg), ctx);
bpf_put_reg64(dst, tmp, ctx);
}
dst = regmap[BPF_REG_1];
reg = bpf_get_reg64_ref(dst, tmp, false, ctx);
if (REG_WAS_SEEN(ctx, lo(reg)) | REG_WAS_SEEN(ctx, hi(reg))) {
if (REG_WAS_SEEN(ctx, hi(reg)))
emit_hppa_copy(HPPA_REG_ARG1, hi(reg), ctx);
if (REG_WAS_SEEN(ctx, lo(reg)))
emit_hppa_copy(HPPA_REG_ARG0, lo(reg), ctx);
bpf_put_reg64(dst, tmp, ctx);
}
/* Set up BPF frame pointer. */
dst = regmap[BPF_REG_FP];
reg = bpf_get_reg64_ref(dst, tmp, false, ctx);
if (REG_WAS_SEEN(ctx, lo(reg)) | REG_WAS_SEEN(ctx, hi(reg))) {
if (REG_WAS_SEEN(ctx, lo(reg)))
emit(hppa_ldo(-REG_SIZE * (NR_SAVED_REGISTERS + BPF_JIT_SCRATCH_REGS),
HPPA_REG_SP, lo(reg)), ctx);
if (REG_WAS_SEEN(ctx, hi(reg)))
emit_hppa_copy(HPPA_REG_ZERO, hi(reg), ctx);
bpf_put_reg64(dst, tmp, ctx);
}
emit(hppa_nop(), ctx);
}
void bpf_jit_build_epilogue(struct hppa_jit_context *ctx)
{
__build_epilogue(false, ctx);
}
// SPDX-License-Identifier: GPL-2.0
/*
* BPF JIT compiler for PA-RISC (64-bit)
*
* Copyright(c) 2023 Helge Deller <deller@gmx.de>
*
* The code is based on the BPF JIT compiler for RV64 by Björn Töpel.
*
* TODO:
* - check if bpf_jit_needs_zext() is needed (currently enabled)
* - implement arch_prepare_bpf_trampoline(), poke(), ...
*/
#include <linux/bitfield.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/libgcc.h>
#include "bpf_jit.h"
static const int regmap[] = {
[BPF_REG_0] = HPPA_REG_RET0,
[BPF_REG_1] = HPPA_R(5),
[BPF_REG_2] = HPPA_R(6),
[BPF_REG_3] = HPPA_R(7),
[BPF_REG_4] = HPPA_R(8),
[BPF_REG_5] = HPPA_R(9),
[BPF_REG_6] = HPPA_R(10),
[BPF_REG_7] = HPPA_R(11),
[BPF_REG_8] = HPPA_R(12),
[BPF_REG_9] = HPPA_R(13),
[BPF_REG_FP] = HPPA_R(14),
[BPF_REG_AX] = HPPA_R(15),
};
/*
* Stack layout during BPF program execution (note: stack grows up):
*
* high
* HPPA64 sp => +----------+ <= HPPA64 fp
* | saved sp |
* | saved rp |
* | ... | HPPA64 callee-saved registers
* | curr args|
* | local var|
* +----------+ <= (BPF FP)
* | |
* | ... | BPF program stack
* | |
* | ... | Function call stack
* | |
* +----------+
* low
*/
/* Offset from fp for BPF registers stored on stack. */
#define STACK_ALIGN FRAME_SIZE
#define EXIT_PTR_LOAD(reg) hppa64_ldd_im16(-FRAME_SIZE, HPPA_REG_SP, reg)
#define EXIT_PTR_STORE(reg) hppa64_std_im16(reg, -FRAME_SIZE, HPPA_REG_SP)
#define EXIT_PTR_JUMP(reg, nop) hppa_bv(HPPA_REG_ZERO, reg, nop)
static u8 bpf_to_hppa_reg(int bpf_reg, struct hppa_jit_context *ctx)
{
u8 reg = regmap[bpf_reg];
REG_SET_SEEN(ctx, reg);
return reg;
};
static void emit_hppa_copy(const s8 rs, const s8 rd, struct hppa_jit_context *ctx)
{
REG_SET_SEEN(ctx, rd);
if (OPTIMIZE_HPPA && (rs == rd))
return;
REG_SET_SEEN(ctx, rs);
emit(hppa_copy(rs, rd), ctx);
}
static void emit_hppa64_depd(u8 src, u8 pos, u8 len, u8 target, bool no_zero, struct hppa_jit_context *ctx)
{
int c;
pos &= (BITS_PER_LONG - 1);
pos = 63 - pos;
len = 64 - len;
c = (len < 32) ? 0x4 : 0;
c |= (pos >= 32) ? 0x2 : 0;
c |= (no_zero) ? 0x1 : 0;
emit(hppa_t10_insn(0x3c, target, src, 0, c, pos & 0x1f, len & 0x1f), ctx);
}
static void emit_hppa64_shld(u8 src, int num, u8 target, struct hppa_jit_context *ctx)
{
emit_hppa64_depd(src, 63-num, 64-num, target, 0, ctx);
}
static void emit_hppa64_extrd(u8 src, u8 pos, u8 len, u8 target, bool signed_op, struct hppa_jit_context *ctx)
{
int c;
pos &= (BITS_PER_LONG - 1);
len = 64 - len;
c = (len < 32) ? 0x4 : 0;
c |= (pos >= 32) ? 0x2 : 0;
c |= signed_op ? 0x1 : 0;
emit(hppa_t10_insn(0x36, src, target, 0, c, pos & 0x1f, len & 0x1f), ctx);
}
static void emit_hppa64_extrw(u8 src, u8 pos, u8 len, u8 target, bool signed_op, struct hppa_jit_context *ctx)
{
int c;
pos &= (32 - 1);
len = 32 - len;
c = 0x06 | (signed_op ? 1 : 0);
emit(hppa_t10_insn(0x34, src, target, 0, c, pos, len), ctx);
}
#define emit_hppa64_zext32(r, target, ctx) \
emit_hppa64_extrd(r, 63, 32, target, false, ctx)
#define emit_hppa64_sext32(r, target, ctx) \
emit_hppa64_extrd(r, 63, 32, target, true, ctx)
static void emit_hppa64_shrd(u8 src, int num, u8 target, bool signed_op, struct hppa_jit_context *ctx)
{
emit_hppa64_extrd(src, 63-num, 64-num, target, signed_op, ctx);
}
static void emit_hppa64_shrw(u8 src, int num, u8 target, bool signed_op, struct hppa_jit_context *ctx)
{
emit_hppa64_extrw(src, 31-num, 32-num, target, signed_op, ctx);
}
/* Emit variable-length instructions for 32-bit imm */
static void emit_imm32(u8 rd, s32 imm, struct hppa_jit_context *ctx)
{
u32 lower = im11(imm);
REG_SET_SEEN(ctx, rd);
if (OPTIMIZE_HPPA && relative_bits_ok(imm, 14)) {
emit(hppa_ldi(imm, rd), ctx);
return;
}
if (OPTIMIZE_HPPA && lower == imm) {
emit(hppa_ldo(lower, HPPA_REG_ZERO, rd), ctx);
return;
}
emit(hppa_ldil(imm, rd), ctx);
if (OPTIMIZE_HPPA && (lower == 0))
return;
emit(hppa_ldo(lower, rd, rd), ctx);
}
static bool is_32b_int(s64 val)
{
return val == (s32) val;
}
/* Emit variable-length instructions for 64-bit imm */
static void emit_imm(u8 rd, s64 imm, u8 tmpreg, struct hppa_jit_context *ctx)
{
u32 upper32;
/* get lower 32-bits into rd, sign extended */
emit_imm32(rd, imm, ctx);
/* do we have upper 32-bits too ? */
if (OPTIMIZE_HPPA && is_32b_int(imm))
return;
/* load upper 32-bits into lower tmpreg and deposit into rd */
upper32 = imm >> 32;
if (upper32 || !OPTIMIZE_HPPA) {
emit_imm32(tmpreg, upper32, ctx);
emit_hppa64_depd(tmpreg, 31, 32, rd, 1, ctx);
} else
emit_hppa64_depd(HPPA_REG_ZERO, 31, 32, rd, 1, ctx);
}
static int emit_jump(signed long paoff, bool force_far,
struct hppa_jit_context *ctx)
{
unsigned long pc, addr;
/* Note: Use 2 instructions for jumps if force_far is set. */
if (relative_bits_ok(paoff - HPPA_BRANCH_DISPLACEMENT, 22)) {
/* use BL,long branch followed by nop() */
emit(hppa64_bl_long(paoff - HPPA_BRANCH_DISPLACEMENT), ctx);
if (force_far)
emit(hppa_nop(), ctx);
return 0;
}
pc = (uintptr_t) &ctx->insns[ctx->ninsns];
addr = pc + (paoff * HPPA_INSN_SIZE);
/* even the 64-bit kernel runs in memory below 4GB */
if (WARN_ON_ONCE(addr >> 32))
return -E2BIG;
emit(hppa_ldil(addr, HPPA_REG_R31), ctx);
emit(hppa_be_l(im11(addr) >> 2, HPPA_REG_R31, NOP_NEXT_INSTR), ctx);
return 0;
}
static void __build_epilogue(bool is_tail_call, struct hppa_jit_context *ctx)
{
int i;
if (is_tail_call) {
/*
* goto *(t0 + 4);
* Skips first instruction of prologue which initializes tail
* call counter. Assumes t0 contains address of target program,
* see emit_bpf_tail_call.
*/
emit(hppa_ldo(1 * HPPA_INSN_SIZE, HPPA_REG_T0, HPPA_REG_T0), ctx);
emit(hppa_bv(HPPA_REG_ZERO, HPPA_REG_T0, EXEC_NEXT_INSTR), ctx);
/* in delay slot: */
emit(hppa_copy(HPPA_REG_TCC, HPPA_REG_TCC_IN_INIT), ctx);
return;
}
/* load epilogue function pointer and jump to it. */
/* exit point is either at next instruction, or the outest TCC exit function */
emit(EXIT_PTR_LOAD(HPPA_REG_RP), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/* NOTE: we are 64-bit and big-endian, so return lower sign-extended 32-bit value */
emit_hppa64_sext32(regmap[BPF_REG_0], HPPA_REG_RET0, ctx);
/* Restore callee-saved registers. */
for (i = 3; i <= 15; i++) {
if (OPTIMIZE_HPPA && !REG_WAS_SEEN(ctx, HPPA_R(i)))
continue;
emit(hppa64_ldd_im16(-REG_SIZE * i, HPPA_REG_SP, HPPA_R(i)), ctx);
}
/* load original return pointer (stored by outest TCC function) */
emit(hppa64_ldd_im16(-2*REG_SIZE, HPPA_REG_SP, HPPA_REG_RP), ctx);
emit(hppa_bv(HPPA_REG_ZERO, HPPA_REG_RP, EXEC_NEXT_INSTR), ctx);
/* in delay slot: */
emit(hppa64_ldd_im5(-REG_SIZE, HPPA_REG_SP, HPPA_REG_SP), ctx);
emit(hppa_nop(), ctx); // XXX WARUM einer zu wenig ??
}
static int emit_branch(u8 op, u8 rd, u8 rs, signed long paoff,
struct hppa_jit_context *ctx)
{
int e, s;
bool far = false;
int off;
if (op == BPF_JSET) {
/*
* BPF_JSET is a special case: it has no inverse so translate
* to and() function and compare against zero
*/
emit(hppa_and(rd, rs, HPPA_REG_T0), ctx);
paoff -= 1; /* reduce offset due to hppa_and() above */
rd = HPPA_REG_T0;
rs = HPPA_REG_ZERO;
op = BPF_JNE;
}
/* set start after BPF_JSET */
s = ctx->ninsns;
if (!relative_branch_ok(paoff - HPPA_BRANCH_DISPLACEMENT + 1, 12)) {
op = invert_bpf_cond(op);
far = true;
}
/*
* For a far branch, the condition is negated and we jump over the
* branch itself, and the two instructions from emit_jump.
* For a near branch, just use paoff.
*/
off = far ? (2 - HPPA_BRANCH_DISPLACEMENT) : paoff - HPPA_BRANCH_DISPLACEMENT;
switch (op) {
/* IF (dst COND src) JUMP off */
case BPF_JEQ:
emit(hppa_beq(rd, rs, off), ctx);
break;
case BPF_JGT:
emit(hppa_bgtu(rd, rs, off), ctx);
break;
case BPF_JLT:
emit(hppa_bltu(rd, rs, off), ctx);
break;
case BPF_JGE:
emit(hppa_bgeu(rd, rs, off), ctx);
break;
case BPF_JLE:
emit(hppa_bleu(rd, rs, off), ctx);
break;
case BPF_JNE:
emit(hppa_bne(rd, rs, off), ctx);
break;
case BPF_JSGT:
emit(hppa_bgt(rd, rs, off), ctx);
break;
case BPF_JSLT:
emit(hppa_blt(rd, rs, off), ctx);
break;
case BPF_JSGE:
emit(hppa_bge(rd, rs, off), ctx);
break;
case BPF_JSLE:
emit(hppa_ble(rd, rs, off), ctx);
break;
default:
WARN_ON(1);
}
if (far) {
int ret;
e = ctx->ninsns;
/* Adjust for extra insns. */
paoff -= (e - s);
ret = emit_jump(paoff, true, ctx);
if (ret)
return ret;
} else {
/*
* always allocate 2 nops instead of the far branch to
* reduce translation loops
*/
emit(hppa_nop(), ctx);
emit(hppa_nop(), ctx);
}
return 0;
}
static void emit_zext_32(u8 reg, struct hppa_jit_context *ctx)
{
emit_hppa64_zext32(reg, reg, ctx);
}
static void emit_bpf_tail_call(int insn, struct hppa_jit_context *ctx)
{
/*
* R1 -> &ctx
* R2 -> &array
* R3 -> index
*/
int off;
const s8 arr_reg = regmap[BPF_REG_2];
const s8 idx_reg = regmap[BPF_REG_3];
struct bpf_array bpfa;
struct bpf_prog bpfp;
/* if there is any tail call, we need to save & restore all registers */
REG_SET_SEEN_ALL(ctx);
/* get address of TCC main exit function for error case into rp */
emit(EXIT_PTR_LOAD(HPPA_REG_RP), ctx);
/* max_entries = array->map.max_entries; */
off = offsetof(struct bpf_array, map.max_entries);
BUILD_BUG_ON(sizeof(bpfa.map.max_entries) != 4);
emit(hppa_ldw(off, arr_reg, HPPA_REG_T1), ctx);
/*
* if (index >= max_entries)
* goto out;
*/
emit(hppa_bltu(idx_reg, HPPA_REG_T1, 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/*
* if (--tcc < 0)
* goto out;
*/
REG_FORCE_SEEN(ctx, HPPA_REG_TCC);
emit(hppa_ldo(-1, HPPA_REG_TCC, HPPA_REG_TCC), ctx);
emit(hppa_bge(HPPA_REG_TCC, HPPA_REG_ZERO, 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/*
* prog = array->ptrs[index];
* if (!prog)
* goto out;
*/
BUILD_BUG_ON(sizeof(bpfa.ptrs[0]) != 8);
emit(hppa64_shladd(idx_reg, 3, arr_reg, HPPA_REG_T0), ctx);
off = offsetof(struct bpf_array, ptrs);
BUILD_BUG_ON(off < 16);
emit(hppa64_ldd_im16(off, HPPA_REG_T0, HPPA_REG_T0), ctx);
emit(hppa_bne(HPPA_REG_T0, HPPA_REG_ZERO, 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
emit(EXIT_PTR_JUMP(HPPA_REG_RP, NOP_NEXT_INSTR), ctx);
/*
* tcc = temp_tcc;
* goto *(prog->bpf_func + 4);
*/
off = offsetof(struct bpf_prog, bpf_func);
BUILD_BUG_ON(off < 16);
BUILD_BUG_ON(sizeof(bpfp.bpf_func) != 8);
emit(hppa64_ldd_im16(off, HPPA_REG_T0, HPPA_REG_T0), ctx);
/* Epilogue jumps to *(t0 + 4). */
__build_epilogue(true, ctx);
}
static void init_regs(u8 *rd, u8 *rs, const struct bpf_insn *insn,
struct hppa_jit_context *ctx)
{
u8 code = insn->code;
switch (code) {
case BPF_JMP | BPF_JA:
case BPF_JMP | BPF_CALL:
case BPF_JMP | BPF_EXIT:
case BPF_JMP | BPF_TAIL_CALL:
break;
default:
*rd = bpf_to_hppa_reg(insn->dst_reg, ctx);
}
if (code & (BPF_ALU | BPF_X) || code & (BPF_ALU64 | BPF_X) ||
code & (BPF_JMP | BPF_X) || code & (BPF_JMP32 | BPF_X) ||
code & BPF_LDX || code & BPF_STX)
*rs = bpf_to_hppa_reg(insn->src_reg, ctx);
}
static void emit_zext_32_rd_rs(u8 *rd, u8 *rs, struct hppa_jit_context *ctx)
{
emit_hppa64_zext32(*rd, HPPA_REG_T2, ctx);
*rd = HPPA_REG_T2;
emit_hppa64_zext32(*rs, HPPA_REG_T1, ctx);
*rs = HPPA_REG_T1;
}
static void emit_sext_32_rd_rs(u8 *rd, u8 *rs, struct hppa_jit_context *ctx)
{
emit_hppa64_sext32(*rd, HPPA_REG_T2, ctx);
*rd = HPPA_REG_T2;
emit_hppa64_sext32(*rs, HPPA_REG_T1, ctx);
*rs = HPPA_REG_T1;
}
static void emit_zext_32_rd_t1(u8 *rd, struct hppa_jit_context *ctx)
{
emit_hppa64_zext32(*rd, HPPA_REG_T2, ctx);
*rd = HPPA_REG_T2;
emit_zext_32(HPPA_REG_T1, ctx);
}
static void emit_sext_32_rd(u8 *rd, struct hppa_jit_context *ctx)
{
emit_hppa64_sext32(*rd, HPPA_REG_T2, ctx);
*rd = HPPA_REG_T2;
}
static bool is_signed_bpf_cond(u8 cond)
{
return cond == BPF_JSGT || cond == BPF_JSLT ||
cond == BPF_JSGE || cond == BPF_JSLE;
}
static void emit_call(u64 addr, bool fixed, struct hppa_jit_context *ctx)
{
const int offset_sp = 2*FRAME_SIZE;
emit(hppa_ldo(offset_sp, HPPA_REG_SP, HPPA_REG_SP), ctx);
emit_hppa_copy(regmap[BPF_REG_1], HPPA_REG_ARG0, ctx);
emit_hppa_copy(regmap[BPF_REG_2], HPPA_REG_ARG1, ctx);
emit_hppa_copy(regmap[BPF_REG_3], HPPA_REG_ARG2, ctx);
emit_hppa_copy(regmap[BPF_REG_4], HPPA_REG_ARG3, ctx);
emit_hppa_copy(regmap[BPF_REG_5], HPPA_REG_ARG4, ctx);
/* Backup TCC. */
REG_FORCE_SEEN(ctx, HPPA_REG_TCC_SAVED);
if (REG_WAS_SEEN(ctx, HPPA_REG_TCC))
emit(hppa_copy(HPPA_REG_TCC, HPPA_REG_TCC_SAVED), ctx);
/*
* Use ldil() to load absolute address. Don't use emit_imm as the
* number of emitted instructions should not depend on the value of
* addr.
*/
WARN_ON(addr >> 32);
/* load function address and gp from Elf64_Fdesc descriptor */
emit(hppa_ldil(addr, HPPA_REG_R31), ctx);
emit(hppa_ldo(im11(addr), HPPA_REG_R31, HPPA_REG_R31), ctx);
emit(hppa64_ldd_im16(offsetof(struct elf64_fdesc, addr),
HPPA_REG_R31, HPPA_REG_RP), ctx);
emit(hppa64_bve_l_rp(HPPA_REG_RP), ctx);
emit(hppa64_ldd_im16(offsetof(struct elf64_fdesc, gp),
HPPA_REG_R31, HPPA_REG_GP), ctx);
/* Restore TCC. */
if (REG_WAS_SEEN(ctx, HPPA_REG_TCC))
emit(hppa_copy(HPPA_REG_TCC_SAVED, HPPA_REG_TCC), ctx);
emit(hppa_ldo(-offset_sp, HPPA_REG_SP, HPPA_REG_SP), ctx);
/* Set return value. */
emit_hppa_copy(HPPA_REG_RET0, regmap[BPF_REG_0], ctx);
}
static void emit_call_libgcc_ll(void *func, const s8 arg0,
const s8 arg1, u8 opcode, struct hppa_jit_context *ctx)
{
u64 func_addr;
if (BPF_CLASS(opcode) == BPF_ALU) {
emit_hppa64_zext32(arg0, HPPA_REG_ARG0, ctx);
emit_hppa64_zext32(arg1, HPPA_REG_ARG1, ctx);
} else {
emit_hppa_copy(arg0, HPPA_REG_ARG0, ctx);
emit_hppa_copy(arg1, HPPA_REG_ARG1, ctx);
}
/* libcgcc overwrites HPPA_REG_RET0, so keep copy in HPPA_REG_TCC_SAVED */
if (arg0 != HPPA_REG_RET0) {
REG_SET_SEEN(ctx, HPPA_REG_TCC_SAVED);
emit(hppa_copy(HPPA_REG_RET0, HPPA_REG_TCC_SAVED), ctx);
}
/* set up stack */
emit(hppa_ldo(FRAME_SIZE, HPPA_REG_SP, HPPA_REG_SP), ctx);
func_addr = (uintptr_t) func;
/* load function func_address and gp from Elf64_Fdesc descriptor */
emit_imm(HPPA_REG_R31, func_addr, arg0, ctx);
emit(hppa64_ldd_im16(offsetof(struct elf64_fdesc, addr),
HPPA_REG_R31, HPPA_REG_RP), ctx);
/* skip the following bve_l instruction if divisor is 0. */
if (BPF_OP(opcode) == BPF_DIV || BPF_OP(opcode) == BPF_MOD) {
if (BPF_OP(opcode) == BPF_DIV)
emit_hppa_copy(HPPA_REG_ZERO, HPPA_REG_RET0, ctx);
else {
emit_hppa_copy(HPPA_REG_ARG0, HPPA_REG_RET0, ctx);
}
emit(hppa_beq(HPPA_REG_ARG1, HPPA_REG_ZERO, 2 - HPPA_BRANCH_DISPLACEMENT), ctx);
}
emit(hppa64_bve_l_rp(HPPA_REG_RP), ctx);
emit(hppa64_ldd_im16(offsetof(struct elf64_fdesc, gp),
HPPA_REG_R31, HPPA_REG_GP), ctx);
emit(hppa_ldo(-FRAME_SIZE, HPPA_REG_SP, HPPA_REG_SP), ctx);
emit_hppa_copy(HPPA_REG_RET0, arg0, ctx);
/* restore HPPA_REG_RET0 */
if (arg0 != HPPA_REG_RET0)
emit(hppa_copy(HPPA_REG_TCC_SAVED, HPPA_REG_RET0), ctx);
}
static void emit_store(const s8 rd, const s8 rs, s16 off,
struct hppa_jit_context *ctx, const u8 size,
const u8 mode)
{
s8 dstreg;
/* need to calculate address since offset does not fit in 14 bits? */
if (relative_bits_ok(off, 14))
dstreg = rd;
else {
/* need to use R1 here, since addil puts result into R1 */
dstreg = HPPA_REG_R1;
emit(hppa_addil(off, rd), ctx);
off = im11(off);
}
switch (size) {
case BPF_B:
emit(hppa_stb(rs, off, dstreg), ctx);
break;
case BPF_H:
emit(hppa_sth(rs, off, dstreg), ctx);
break;
case BPF_W:
emit(hppa_stw(rs, off, dstreg), ctx);
break;
case BPF_DW:
if (off & 7) {
emit(hppa_ldo(off, dstreg, HPPA_REG_R1), ctx);
emit(hppa64_std_im5(rs, 0, HPPA_REG_R1), ctx);
} else if (off >= -16 && off <= 15)
emit(hppa64_std_im5(rs, off, dstreg), ctx);
else
emit(hppa64_std_im16(rs, off, dstreg), ctx);
break;
}
}
int bpf_jit_emit_insn(const struct bpf_insn *insn, struct hppa_jit_context *ctx,
bool extra_pass)
{
bool is64 = BPF_CLASS(insn->code) == BPF_ALU64 ||
BPF_CLASS(insn->code) == BPF_JMP;
int s, e, ret, i = insn - ctx->prog->insnsi;
s64 paoff;
struct bpf_prog_aux *aux = ctx->prog->aux;
u8 rd = -1, rs = -1, code = insn->code;
s16 off = insn->off;
s32 imm = insn->imm;
init_regs(&rd, &rs, insn, ctx);
switch (code) {
/* dst = src */
case BPF_ALU | BPF_MOV | BPF_X:
case BPF_ALU64 | BPF_MOV | BPF_X:
if (imm == 1) {
/* Special mov32 for zext */
emit_zext_32(rd, ctx);
break;
}
if (!is64 && !aux->verifier_zext)
emit_hppa64_zext32(rs, rd, ctx);
else
emit_hppa_copy(rs, rd, ctx);
break;
/* dst = dst OP src */
case BPF_ALU | BPF_ADD | BPF_X:
case BPF_ALU64 | BPF_ADD | BPF_X:
emit(hppa_add(rd, rs, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_SUB | BPF_X:
case BPF_ALU64 | BPF_SUB | BPF_X:
emit(hppa_sub(rd, rs, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_AND | BPF_X:
case BPF_ALU64 | BPF_AND | BPF_X:
emit(hppa_and(rd, rs, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_OR | BPF_X:
case BPF_ALU64 | BPF_OR | BPF_X:
emit(hppa_or(rd, rs, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_XOR | BPF_X:
case BPF_ALU64 | BPF_XOR | BPF_X:
emit(hppa_xor(rd, rs, rd), ctx);
if (!is64 && !aux->verifier_zext && rs != rd)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_MUL | BPF_K:
case BPF_ALU64 | BPF_MUL | BPF_K:
emit_imm(HPPA_REG_T1, is64 ? (s64)(s32)imm : (u32)imm, HPPA_REG_T2, ctx);
rs = HPPA_REG_T1;
fallthrough;
case BPF_ALU | BPF_MUL | BPF_X:
case BPF_ALU64 | BPF_MUL | BPF_X:
emit_call_libgcc_ll(__muldi3, rd, rs, code, ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_DIV | BPF_K:
case BPF_ALU64 | BPF_DIV | BPF_K:
emit_imm(HPPA_REG_T1, is64 ? (s64)(s32)imm : (u32)imm, HPPA_REG_T2, ctx);
rs = HPPA_REG_T1;
fallthrough;
case BPF_ALU | BPF_DIV | BPF_X:
case BPF_ALU64 | BPF_DIV | BPF_X:
emit_call_libgcc_ll(&hppa_div64, rd, rs, code, ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_MOD | BPF_K:
case BPF_ALU64 | BPF_MOD | BPF_K:
emit_imm(HPPA_REG_T1, is64 ? (s64)(s32)imm : (u32)imm, HPPA_REG_T2, ctx);
rs = HPPA_REG_T1;
fallthrough;
case BPF_ALU | BPF_MOD | BPF_X:
case BPF_ALU64 | BPF_MOD | BPF_X:
emit_call_libgcc_ll(&hppa_div64_rem, rd, rs, code, ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_LSH | BPF_X:
case BPF_ALU64 | BPF_LSH | BPF_X:
emit_hppa64_sext32(rs, HPPA_REG_T0, ctx);
emit(hppa64_mtsarcm(HPPA_REG_T0), ctx);
if (is64)
emit(hppa64_depdz_sar(rd, rd), ctx);
else
emit(hppa_depwz_sar(rd, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_RSH | BPF_X:
case BPF_ALU64 | BPF_RSH | BPF_X:
emit(hppa_mtsar(rs), ctx);
if (is64)
emit(hppa64_shrpd_sar(rd, rd), ctx);
else
emit(hppa_shrpw_sar(rd, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_ARSH | BPF_X:
case BPF_ALU64 | BPF_ARSH | BPF_X:
emit_hppa64_sext32(rs, HPPA_REG_T0, ctx);
emit(hppa64_mtsarcm(HPPA_REG_T0), ctx);
if (is64)
emit(hppa_extrd_sar(rd, rd, 1), ctx);
else
emit(hppa_extrws_sar(rd, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
/* dst = -dst */
case BPF_ALU | BPF_NEG:
case BPF_ALU64 | BPF_NEG:
emit(hppa_sub(HPPA_REG_ZERO, rd, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
/* dst = BSWAP##imm(dst) */
case BPF_ALU | BPF_END | BPF_FROM_BE:
switch (imm) {
case 16:
/* zero-extend 16 bits into 64 bits */
emit_hppa64_depd(HPPA_REG_ZERO, 63-16, 64-16, rd, 1, ctx);
break;
case 32:
if (!aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case 64:
/* Do nothing */
break;
}
break;
case BPF_ALU | BPF_END | BPF_FROM_LE:
switch (imm) {
case 16:
emit(hppa_extru(rd, 31 - 8, 8, HPPA_REG_T1), ctx);
emit(hppa_depwz(rd, 23, 8, HPPA_REG_T1), ctx);
emit(hppa_extru(HPPA_REG_T1, 31, 16, rd), ctx);
emit_hppa64_extrd(HPPA_REG_T1, 63, 16, rd, 0, ctx);
break;
case 32:
emit(hppa_shrpw(rd, rd, 16, HPPA_REG_T1), ctx);
emit_hppa64_depd(HPPA_REG_T1, 63-16, 8, HPPA_REG_T1, 1, ctx);
emit(hppa_shrpw(rd, HPPA_REG_T1, 8, HPPA_REG_T1), ctx);
emit_hppa64_extrd(HPPA_REG_T1, 63, 32, rd, 0, ctx);
break;
case 64:
emit(hppa64_permh_3210(rd, HPPA_REG_T1), ctx);
emit(hppa64_hshl(HPPA_REG_T1, 8, HPPA_REG_T2), ctx);
emit(hppa64_hshr_u(HPPA_REG_T1, 8, HPPA_REG_T1), ctx);
emit(hppa_or(HPPA_REG_T2, HPPA_REG_T1, rd), ctx);
break;
default:
pr_err("bpf-jit: BPF_END imm %d invalid\n", imm);
return -1;
}
break;
/* dst = imm */
case BPF_ALU | BPF_MOV | BPF_K:
case BPF_ALU64 | BPF_MOV | BPF_K:
emit_imm(rd, imm, HPPA_REG_T2, ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
/* dst = dst OP imm */
case BPF_ALU | BPF_ADD | BPF_K:
case BPF_ALU64 | BPF_ADD | BPF_K:
if (relative_bits_ok(imm, 14)) {
emit(hppa_ldo(imm, rd, rd), ctx);
} else {
emit_imm(HPPA_REG_T1, imm, HPPA_REG_T2, ctx);
emit(hppa_add(rd, HPPA_REG_T1, rd), ctx);
}
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_SUB | BPF_K:
case BPF_ALU64 | BPF_SUB | BPF_K:
if (relative_bits_ok(-imm, 14)) {
emit(hppa_ldo(-imm, rd, rd), ctx);
} else {
emit_imm(HPPA_REG_T1, imm, HPPA_REG_T2, ctx);
emit(hppa_sub(rd, HPPA_REG_T1, rd), ctx);
}
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_AND | BPF_K:
case BPF_ALU64 | BPF_AND | BPF_K:
emit_imm(HPPA_REG_T1, imm, HPPA_REG_T2, ctx);
emit(hppa_and(rd, HPPA_REG_T1, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_OR | BPF_K:
case BPF_ALU64 | BPF_OR | BPF_K:
emit_imm(HPPA_REG_T1, imm, HPPA_REG_T2, ctx);
emit(hppa_or(rd, HPPA_REG_T1, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_XOR | BPF_K:
case BPF_ALU64 | BPF_XOR | BPF_K:
emit_imm(HPPA_REG_T1, imm, HPPA_REG_T2, ctx);
emit(hppa_xor(rd, HPPA_REG_T1, rd), ctx);
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_LSH | BPF_K:
case BPF_ALU64 | BPF_LSH | BPF_K:
if (imm != 0) {
emit_hppa64_shld(rd, imm, rd, ctx);
}
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_RSH | BPF_K:
case BPF_ALU64 | BPF_RSH | BPF_K:
if (imm != 0) {
if (is64)
emit_hppa64_shrd(rd, imm, rd, false, ctx);
else
emit_hppa64_shrw(rd, imm, rd, false, ctx);
}
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
case BPF_ALU | BPF_ARSH | BPF_K:
case BPF_ALU64 | BPF_ARSH | BPF_K:
if (imm != 0) {
if (is64)
emit_hppa64_shrd(rd, imm, rd, true, ctx);
else
emit_hppa64_shrw(rd, imm, rd, true, ctx);
}
if (!is64 && !aux->verifier_zext)
emit_zext_32(rd, ctx);
break;
/* JUMP off */
case BPF_JMP | BPF_JA:
paoff = hppa_offset(i, off, ctx);
ret = emit_jump(paoff, false, ctx);
if (ret)
return ret;
break;
/* IF (dst COND src) JUMP off */
case BPF_JMP | BPF_JEQ | BPF_X:
case BPF_JMP32 | BPF_JEQ | BPF_X:
case BPF_JMP | BPF_JGT | BPF_X:
case BPF_JMP32 | BPF_JGT | BPF_X:
case BPF_JMP | BPF_JLT | BPF_X:
case BPF_JMP32 | BPF_JLT | BPF_X:
case BPF_JMP | BPF_JGE | BPF_X:
case BPF_JMP32 | BPF_JGE | BPF_X:
case BPF_JMP | BPF_JLE | BPF_X:
case BPF_JMP32 | BPF_JLE | BPF_X:
case BPF_JMP | BPF_JNE | BPF_X:
case BPF_JMP32 | BPF_JNE | BPF_X:
case BPF_JMP | BPF_JSGT | BPF_X:
case BPF_JMP32 | BPF_JSGT | BPF_X:
case BPF_JMP | BPF_JSLT | BPF_X:
case BPF_JMP32 | BPF_JSLT | BPF_X:
case BPF_JMP | BPF_JSGE | BPF_X:
case BPF_JMP32 | BPF_JSGE | BPF_X:
case BPF_JMP | BPF_JSLE | BPF_X:
case BPF_JMP32 | BPF_JSLE | BPF_X:
case BPF_JMP | BPF_JSET | BPF_X:
case BPF_JMP32 | BPF_JSET | BPF_X:
paoff = hppa_offset(i, off, ctx);
if (!is64) {
s = ctx->ninsns;
if (is_signed_bpf_cond(BPF_OP(code)))
emit_sext_32_rd_rs(&rd, &rs, ctx);
else
emit_zext_32_rd_rs(&rd, &rs, ctx);
e = ctx->ninsns;
/* Adjust for extra insns */
paoff -= (e - s);
}
if (BPF_OP(code) == BPF_JSET) {
/* Adjust for and */
paoff -= 1;
emit(hppa_and(rs, rd, HPPA_REG_T1), ctx);
emit_branch(BPF_JNE, HPPA_REG_T1, HPPA_REG_ZERO, paoff,
ctx);
} else {
emit_branch(BPF_OP(code), rd, rs, paoff, ctx);
}
break;
/* IF (dst COND imm) JUMP off */
case BPF_JMP | BPF_JEQ | BPF_K:
case BPF_JMP32 | BPF_JEQ | BPF_K:
case BPF_JMP | BPF_JGT | BPF_K:
case BPF_JMP32 | BPF_JGT | BPF_K:
case BPF_JMP | BPF_JLT | BPF_K:
case BPF_JMP32 | BPF_JLT | BPF_K:
case BPF_JMP | BPF_JGE | BPF_K:
case BPF_JMP32 | BPF_JGE | BPF_K:
case BPF_JMP | BPF_JLE | BPF_K:
case BPF_JMP32 | BPF_JLE | BPF_K:
case BPF_JMP | BPF_JNE | BPF_K:
case BPF_JMP32 | BPF_JNE | BPF_K:
case BPF_JMP | BPF_JSGT | BPF_K:
case BPF_JMP32 | BPF_JSGT | BPF_K:
case BPF_JMP | BPF_JSLT | BPF_K:
case BPF_JMP32 | BPF_JSLT | BPF_K:
case BPF_JMP | BPF_JSGE | BPF_K:
case BPF_JMP32 | BPF_JSGE | BPF_K:
case BPF_JMP | BPF_JSLE | BPF_K:
case BPF_JMP32 | BPF_JSLE | BPF_K:
paoff = hppa_offset(i, off, ctx);
s = ctx->ninsns;
if (imm) {
emit_imm(HPPA_REG_T1, imm, HPPA_REG_T2, ctx);
rs = HPPA_REG_T1;
} else {
rs = HPPA_REG_ZERO;
}
if (!is64) {
if (is_signed_bpf_cond(BPF_OP(code)))
emit_sext_32_rd(&rd, ctx);
else
emit_zext_32_rd_t1(&rd, ctx);
}
e = ctx->ninsns;
/* Adjust for extra insns */
paoff -= (e - s);
emit_branch(BPF_OP(code), rd, rs, paoff, ctx);
break;
case BPF_JMP | BPF_JSET | BPF_K:
case BPF_JMP32 | BPF_JSET | BPF_K:
paoff = hppa_offset(i, off, ctx);
s = ctx->ninsns;
emit_imm(HPPA_REG_T1, imm, HPPA_REG_T2, ctx);
emit(hppa_and(HPPA_REG_T1, rd, HPPA_REG_T1), ctx);
/* For jset32, we should clear the upper 32 bits of t1, but
* sign-extension is sufficient here and saves one instruction,
* as t1 is used only in comparison against zero.
*/
if (!is64 && imm < 0)
emit_hppa64_sext32(HPPA_REG_T1, HPPA_REG_T1, ctx);
e = ctx->ninsns;
paoff -= (e - s);
emit_branch(BPF_JNE, HPPA_REG_T1, HPPA_REG_ZERO, paoff, ctx);
break;
/* function call */
case BPF_JMP | BPF_CALL:
{
bool fixed_addr;
u64 addr;
ret = bpf_jit_get_func_addr(ctx->prog, insn, extra_pass,
&addr, &fixed_addr);
if (ret < 0)
return ret;
REG_SET_SEEN_ALL(ctx);
emit_call(addr, fixed_addr, ctx);
break;
}
/* tail call */
case BPF_JMP | BPF_TAIL_CALL:
emit_bpf_tail_call(i, ctx);
break;
/* function return */
case BPF_JMP | BPF_EXIT:
if (i == ctx->prog->len - 1)
break;
paoff = epilogue_offset(ctx);
ret = emit_jump(paoff, false, ctx);
if (ret)
return ret;
break;
/* dst = imm64 */
case BPF_LD | BPF_IMM | BPF_DW:
{
struct bpf_insn insn1 = insn[1];
u64 imm64 = (u64)insn1.imm << 32 | (u32)imm;
if (bpf_pseudo_func(insn))
imm64 = (uintptr_t)dereference_function_descriptor((void*)imm64);
emit_imm(rd, imm64, HPPA_REG_T2, ctx);
return 1;
}
/* LDX: dst = *(size *)(src + off) */
case BPF_LDX | BPF_MEM | BPF_B:
case BPF_LDX | BPF_MEM | BPF_H:
case BPF_LDX | BPF_MEM | BPF_W:
case BPF_LDX | BPF_MEM | BPF_DW:
case BPF_LDX | BPF_PROBE_MEM | BPF_B:
case BPF_LDX | BPF_PROBE_MEM | BPF_H:
case BPF_LDX | BPF_PROBE_MEM | BPF_W:
case BPF_LDX | BPF_PROBE_MEM | BPF_DW:
{
u8 srcreg;
/* need to calculate address since offset does not fit in 14 bits? */
if (relative_bits_ok(off, 14))
srcreg = rs;
else {
/* need to use R1 here, since addil puts result into R1 */
srcreg = HPPA_REG_R1;
BUG_ON(rs == HPPA_REG_R1);
BUG_ON(rd == HPPA_REG_R1);
emit(hppa_addil(off, rs), ctx);
off = im11(off);
}
switch (BPF_SIZE(code)) {
case BPF_B:
emit(hppa_ldb(off, srcreg, rd), ctx);
if (insn_is_zext(&insn[1]))
return 1;
break;
case BPF_H:
emit(hppa_ldh(off, srcreg, rd), ctx);
if (insn_is_zext(&insn[1]))
return 1;
break;
case BPF_W:
emit(hppa_ldw(off, srcreg, rd), ctx);
if (insn_is_zext(&insn[1]))
return 1;
break;
case BPF_DW:
if (off & 7) {
emit(hppa_ldo(off, srcreg, HPPA_REG_R1), ctx);
emit(hppa64_ldd_reg(HPPA_REG_ZERO, HPPA_REG_R1, rd), ctx);
} else if (off >= -16 && off <= 15)
emit(hppa64_ldd_im5(off, srcreg, rd), ctx);
else
emit(hppa64_ldd_im16(off, srcreg, rd), ctx);
break;
}
break;
}
/* speculation barrier */
case BPF_ST | BPF_NOSPEC:
break;
/* ST: *(size *)(dst + off) = imm */
/* STX: *(size *)(dst + off) = src */
case BPF_ST | BPF_MEM | BPF_B:
case BPF_ST | BPF_MEM | BPF_H:
case BPF_ST | BPF_MEM | BPF_W:
case BPF_ST | BPF_MEM | BPF_DW:
case BPF_STX | BPF_MEM | BPF_B:
case BPF_STX | BPF_MEM | BPF_H:
case BPF_STX | BPF_MEM | BPF_W:
case BPF_STX | BPF_MEM | BPF_DW:
if (BPF_CLASS(code) == BPF_ST) {
emit_imm(HPPA_REG_T2, imm, HPPA_REG_T1, ctx);
rs = HPPA_REG_T2;
}
emit_store(rd, rs, off, ctx, BPF_SIZE(code), BPF_MODE(code));
break;
case BPF_STX | BPF_ATOMIC | BPF_W:
case BPF_STX | BPF_ATOMIC | BPF_DW:
pr_info_once(
"bpf-jit: not supported: atomic operation %02x ***\n",
insn->imm);
return -EFAULT;
default:
pr_err("bpf-jit: unknown opcode %02x\n", code);
return -EINVAL;
}
return 0;
}
void bpf_jit_build_prologue(struct hppa_jit_context *ctx)
{
int bpf_stack_adjust, stack_adjust, i;
unsigned long addr;
s8 reg;
/*
* stack on hppa grows up, so if tail calls are used we need to
* allocate the maximum stack size
*/
if (REG_ALL_SEEN(ctx))
bpf_stack_adjust = MAX_BPF_STACK;
else
bpf_stack_adjust = ctx->prog->aux->stack_depth;
bpf_stack_adjust = round_up(bpf_stack_adjust, STACK_ALIGN);
stack_adjust = FRAME_SIZE + bpf_stack_adjust;
stack_adjust = round_up(stack_adjust, STACK_ALIGN);
/*
* NOTE: We construct an Elf64_Fdesc descriptor here.
* The first 4 words initialize the TCC and compares them.
* Then follows the virtual address of the eBPF function,
* and the gp for this function.
*
* The first instruction sets the tail-call-counter (TCC) register.
* This instruction is skipped by tail calls.
* Use a temporary register instead of a caller-saved register initially.
*/
REG_FORCE_SEEN(ctx, HPPA_REG_TCC_IN_INIT);
emit(hppa_ldi(MAX_TAIL_CALL_CNT, HPPA_REG_TCC_IN_INIT), ctx);
/*
* Skip all initializations when called as BPF TAIL call.
*/
emit(hppa_ldi(MAX_TAIL_CALL_CNT, HPPA_REG_R1), ctx);
emit(hppa_beq(HPPA_REG_TCC_IN_INIT, HPPA_REG_R1, 6 - HPPA_BRANCH_DISPLACEMENT), ctx);
emit(hppa64_bl_long(ctx->prologue_len - 3 - HPPA_BRANCH_DISPLACEMENT), ctx);
/* store entry address of this eBPF function */
addr = (uintptr_t) &ctx->insns[0];
emit(addr >> 32, ctx);
emit(addr & 0xffffffff, ctx);
/* store gp of this eBPF function */
asm("copy %%r27,%0" : "=r" (addr) );
emit(addr >> 32, ctx);
emit(addr & 0xffffffff, ctx);
/* Set up hppa stack frame. */
emit_hppa_copy(HPPA_REG_SP, HPPA_REG_R1, ctx);
emit(hppa_ldo(stack_adjust, HPPA_REG_SP, HPPA_REG_SP), ctx);
emit(hppa64_std_im5 (HPPA_REG_R1, -REG_SIZE, HPPA_REG_SP), ctx);
emit(hppa64_std_im16(HPPA_REG_RP, -2*REG_SIZE, HPPA_REG_SP), ctx);
/* Save callee-save registers. */
for (i = 3; i <= 15; i++) {
if (OPTIMIZE_HPPA && !REG_WAS_SEEN(ctx, HPPA_R(i)))
continue;
emit(hppa64_std_im16(HPPA_R(i), -REG_SIZE * i, HPPA_REG_SP), ctx);
}
/* load function parameters; load all if we use tail functions */
#define LOAD_PARAM(arg, dst) \
if (REG_WAS_SEEN(ctx, regmap[dst]) || \
REG_WAS_SEEN(ctx, HPPA_REG_TCC)) \
emit_hppa_copy(arg, regmap[dst], ctx)
LOAD_PARAM(HPPA_REG_ARG0, BPF_REG_1);
LOAD_PARAM(HPPA_REG_ARG1, BPF_REG_2);
LOAD_PARAM(HPPA_REG_ARG2, BPF_REG_3);
LOAD_PARAM(HPPA_REG_ARG3, BPF_REG_4);
LOAD_PARAM(HPPA_REG_ARG4, BPF_REG_5);
#undef LOAD_PARAM
REG_FORCE_SEEN(ctx, HPPA_REG_T0);
REG_FORCE_SEEN(ctx, HPPA_REG_T1);
REG_FORCE_SEEN(ctx, HPPA_REG_T2);
/*
* Now really set the tail call counter (TCC) register.
*/
if (REG_WAS_SEEN(ctx, HPPA_REG_TCC))
emit(hppa_ldi(MAX_TAIL_CALL_CNT, HPPA_REG_TCC), ctx);
/*
* Save epilogue function pointer for outer TCC call chain.
* The main TCC call stores the final RP on stack.
*/
addr = (uintptr_t) &ctx->insns[ctx->epilogue_offset];
/* skip first two instructions which jump to exit */
addr += 2 * HPPA_INSN_SIZE;
emit_imm(HPPA_REG_T2, addr, HPPA_REG_T1, ctx);
emit(EXIT_PTR_STORE(HPPA_REG_T2), ctx);
/* Set up BPF frame pointer. */
reg = regmap[BPF_REG_FP]; /* -> HPPA_REG_FP */
if (REG_WAS_SEEN(ctx, reg)) {
emit(hppa_ldo(-FRAME_SIZE, HPPA_REG_SP, reg), ctx);
}
}
void bpf_jit_build_epilogue(struct hppa_jit_context *ctx)
{
__build_epilogue(false, ctx);
}
bool bpf_jit_supports_kfunc_call(void)
{
return true;
}
// SPDX-License-Identifier: GPL-2.0
/*
* Common functionality for HPPA32 and HPPA64 BPF JIT compilers
*
* Copyright (c) 2023 Helge Deller <deller@gmx.de>
*
*/
#include <linux/bpf.h>
#include <linux/filter.h>
#include "bpf_jit.h"
/* Number of iterations to try until offsets converge. */
#define NR_JIT_ITERATIONS 35
static int build_body(struct hppa_jit_context *ctx, bool extra_pass, int *offset)
{
const struct bpf_prog *prog = ctx->prog;
int i;
ctx->reg_seen_collect = true;
for (i = 0; i < prog->len; i++) {
const struct bpf_insn *insn = &prog->insnsi[i];
int ret;
ret = bpf_jit_emit_insn(insn, ctx, extra_pass);
/* BPF_LD | BPF_IMM | BPF_DW: skip the next instruction. */
if (ret > 0)
i++;
if (offset)
offset[i] = ctx->ninsns;
if (ret < 0)
return ret;
}
ctx->reg_seen_collect = false;
return 0;
}
bool bpf_jit_needs_zext(void)
{
return true;
}
struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
{
unsigned int prog_size = 0, extable_size = 0;
bool tmp_blinded = false, extra_pass = false;
struct bpf_prog *tmp, *orig_prog = prog;
int pass = 0, prev_ninsns = 0, prologue_len, i;
struct hppa_jit_data *jit_data;
struct hppa_jit_context *ctx;
if (!prog->jit_requested)
return orig_prog;
tmp = bpf_jit_blind_constants(prog);
if (IS_ERR(tmp))
return orig_prog;
if (tmp != prog) {
tmp_blinded = true;
prog = tmp;
}
jit_data = prog->aux->jit_data;
if (!jit_data) {
jit_data = kzalloc(sizeof(*jit_data), GFP_KERNEL);
if (!jit_data) {
prog = orig_prog;
goto out;
}
prog->aux->jit_data = jit_data;
}
ctx = &jit_data->ctx;
if (ctx->offset) {
extra_pass = true;
prog_size = sizeof(*ctx->insns) * ctx->ninsns;
goto skip_init_ctx;
}
ctx->prog = prog;
ctx->offset = kcalloc(prog->len, sizeof(int), GFP_KERNEL);
if (!ctx->offset) {
prog = orig_prog;
goto out_offset;
}
for (i = 0; i < prog->len; i++) {
prev_ninsns += 20;
ctx->offset[i] = prev_ninsns;
}
for (i = 0; i < NR_JIT_ITERATIONS; i++) {
pass++;
ctx->ninsns = 0;
if (build_body(ctx, extra_pass, ctx->offset)) {
prog = orig_prog;
goto out_offset;
}
ctx->body_len = ctx->ninsns;
bpf_jit_build_prologue(ctx);
ctx->prologue_len = ctx->ninsns - ctx->body_len;
ctx->epilogue_offset = ctx->ninsns;
bpf_jit_build_epilogue(ctx);
if (ctx->ninsns == prev_ninsns) {
if (jit_data->header)
break;
/* obtain the actual image size */
extable_size = prog->aux->num_exentries *
sizeof(struct exception_table_entry);
prog_size = sizeof(*ctx->insns) * ctx->ninsns;
jit_data->header =
bpf_jit_binary_alloc(prog_size + extable_size,
&jit_data->image,
sizeof(u32),
bpf_fill_ill_insns);
if (!jit_data->header) {
prog = orig_prog;
goto out_offset;
}
ctx->insns = (u32 *)jit_data->image;
/*
* Now, when the image is allocated, the image can
* potentially shrink more (auipc/jalr -> jal).
*/
}
prev_ninsns = ctx->ninsns;
}
if (i == NR_JIT_ITERATIONS) {
pr_err("bpf-jit: image did not converge in <%d passes!\n", i);
if (jit_data->header)
bpf_jit_binary_free(jit_data->header);
prog = orig_prog;
goto out_offset;
}
if (extable_size)
prog->aux->extable = (void *)ctx->insns + prog_size;
skip_init_ctx:
pass++;
ctx->ninsns = 0;
bpf_jit_build_prologue(ctx);
if (build_body(ctx, extra_pass, NULL)) {
bpf_jit_binary_free(jit_data->header);
prog = orig_prog;
goto out_offset;
}
bpf_jit_build_epilogue(ctx);
if (HPPA_JIT_DEBUG || bpf_jit_enable > 1) {
if (HPPA_JIT_DUMP)
bpf_jit_dump(prog->len, prog_size, pass, ctx->insns);
if (HPPA_JIT_REBOOT)
{ extern int machine_restart(char *); machine_restart(""); }
}
prog->bpf_func = (void *)ctx->insns;
prog->jited = 1;
prog->jited_len = prog_size;
bpf_flush_icache(jit_data->header, ctx->insns + ctx->ninsns);
if (!prog->is_func || extra_pass) {
bpf_jit_binary_lock_ro(jit_data->header);
prologue_len = ctx->epilogue_offset - ctx->body_len;
for (i = 0; i < prog->len; i++)
ctx->offset[i] += prologue_len;
bpf_prog_fill_jited_linfo(prog, ctx->offset);
out_offset:
kfree(ctx->offset);
kfree(jit_data);
prog->aux->jit_data = NULL;
}
out:
if (HPPA_JIT_REBOOT)
{ extern int machine_restart(char *); machine_restart(""); }
if (tmp_blinded)
bpf_jit_prog_release_other(prog, prog == orig_prog ?
tmp : orig_prog);
return prog;
}
u64 hppa_div64(u64 div, u64 divisor)
{
div = div64_u64(div, divisor);
return div;
}
u64 hppa_div64_rem(u64 div, u64 divisor)
{
u64 rem;
div64_u64_rem(div, divisor, &rem);
return rem;
}
......@@ -100,8 +100,9 @@ config SUPERIO
config CHASSIS_LCD_LED
bool "Chassis LCD and LED support"
depends on LEDS_CLASS=y
default y
select VM_EVENT_COUNTERS
select LEDS_TRIGGERS
help
Say Y here if you want to enable support for the Heartbeat,
Disk/Network activities LEDs on some PA-RISC machines,
......
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for most of the non-PCI devices in PA-RISC machines
# Makefile PCI and non-PCI devices in PA-RISC machines
#
# Keep the order below, e.g.
# - ccio before any potential subdevices
# - gsc is required before lasi and wax
# - asp and wax before the EISA adapters for the IRQ regions
# - EISA must come before PCI to be sure it gets IRQ region
#
# I/O SAPIC is also on IA64 platforms.
# The two could be merged into a common source some day.
obj-$(CONFIG_IOSAPIC) += iosapic.o
obj-$(CONFIG_IOMMU_SBA) += sba_iommu.o
obj-$(CONFIG_PCI_LBA) += lba_pci.o
obj-$(CONFIG_IOMMU_CCIO) += ccio-dma.o
obj-$(CONFIG_GSC) += gsc.o
obj-$(CONFIG_HPPB) += hppb.o
obj-$(CONFIG_GSC_DINO) += dino.o
obj-$(CONFIG_GSC_LASI) += lasi.o asp.o
obj-$(CONFIG_GSC_WAX) += wax.o
obj-$(CONFIG_EISA) += eisa.o eisa_enumerator.o eisa_eeprom.o
obj-$(CONFIG_HPPB) += hppb.o
obj-$(CONFIG_GSC_DINO) += dino.o
obj-$(CONFIG_SUPERIO) += superio.o
obj-$(CONFIG_CHASSIS_LCD_LED) += led.o
obj-$(CONFIG_PDC_STABLE) += pdc_stable.o
obj-y += power.o
......@@ -4,7 +4,7 @@
*
* (c) Copyright 2000 The Puffin Group Inc.
*
* by Helge Deller <deller@gmx.de>
* (c) 2000-2023 by Helge Deller <deller@gmx.de>
*/
#include <linux/errno.h>
......@@ -118,9 +118,16 @@ static const struct parisc_device_id asp_tbl[] __initconst = {
{ HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00070 },
{ 0, }
};
MODULE_DEVICE_TABLE(parisc, asp_tbl);
struct parisc_driver asp_driver __refdata = {
static struct parisc_driver asp_driver __refdata = {
.name = "asp",
.id_table = asp_tbl,
.probe = asp_init_chip,
};
static int __init asp_init(void)
{
return register_parisc_driver(&asp_driver);
}
arch_initcall(asp_init);
......@@ -8,18 +8,10 @@
** (c) Copyright 2000 Ryan Bradetich
** (c) Copyright 2000 Hewlett-Packard Company
**
**
**
** "Real Mode" operation refers to U2/Uturn chip operation.
** U2/Uturn were designed to perform coherency checks w/o using
** the I/O MMU - basically what x86 does.
**
** Philipp Rumpf has a "Real Mode" driver for PCX-W machines at:
** CVSROOT=:pserver:anonymous@198.186.203.37:/cvsroot/linux-parisc
** cvs -z3 co linux/arch/parisc/kernel/dma-rm.c
**
** I've rewritten his code to work under TPG's tree. See ccio-rm-dma.c.
**
** Drawbacks of using Real Mode are:
** o outbound DMA is slower - U2 won't prefetch data (GSC+ XQL signal).
** o Inbound DMA less efficient - U2 can't use DMA_FAST attribute.
......@@ -71,8 +63,6 @@
#undef CCIO_COLLECT_STATS
#endif
#include <asm/runway.h> /* for proc_runway_root */
#ifdef DEBUG_CCIO_INIT
#define DBG_INIT(x...) printk(x)
#else
......@@ -1567,10 +1557,15 @@ static int __init ccio_probe(struct parisc_device *dev)
#ifdef CONFIG_PROC_FS
if (ioc_count == 0) {
proc_create_single(MODULE_NAME, 0, proc_runway_root,
struct proc_dir_entry *runway;
runway = proc_mkdir("bus/runway", NULL);
if (runway) {
proc_create_single(MODULE_NAME, 0, runway,
ccio_proc_info);
proc_create_single(MODULE_NAME"-bitmap", 0, proc_runway_root,
proc_create_single(MODULE_NAME"-bitmap", 0, runway,
ccio_proc_bitmap_info);
}
}
#endif
ioc_count++;
......@@ -1582,8 +1577,8 @@ static int __init ccio_probe(struct parisc_device *dev)
*
* Register this driver.
*/
void __init ccio_init(void)
static int __init ccio_init(void)
{
register_parisc_driver(&ccio_driver);
return register_parisc_driver(&ccio_driver);
}
arch_initcall(ccio_init);
......@@ -1084,8 +1084,8 @@ static struct parisc_driver dino_driver __refdata = {
* This is the only routine which is NOT static.
* Must be called exactly once before pci_init().
*/
void __init dino_init(void)
static int __init dino_init(void)
{
register_parisc_driver(&dino_driver);
return register_parisc_driver(&dino_driver);
}
arch_initcall(dino_init);
......@@ -400,10 +400,11 @@ static struct parisc_driver eisa_driver __refdata = {
.probe = eisa_probe,
};
void __init parisc_eisa_init(void)
static int __init parisc_eisa_init(void)
{
register_parisc_driver(&eisa_driver);
return register_parisc_driver(&eisa_driver);
}
arch_initcall(parisc_eisa_init);
static unsigned int eisa_irq_configured;
......
......@@ -258,18 +258,3 @@ int gsc_common_setup(struct parisc_device *parent, struct gsc_asic *gsc_asic)
return 0;
}
extern struct parisc_driver lasi_driver;
extern struct parisc_driver asp_driver;
extern struct parisc_driver wax_driver;
void __init gsc_init(void)
{
#ifdef CONFIG_GSC_LASI
register_parisc_driver(&lasi_driver);
register_parisc_driver(&asp_driver);
#endif
#ifdef CONFIG_GSC_WAX
register_parisc_driver(&wax_driver);
#endif
}
......@@ -96,9 +96,10 @@ static struct parisc_driver hppb_driver __refdata = {
/**
* hppb_init - HP-PB bus initialization procedure.
*
* Register this driver.
* Register this driver.
*/
void __init hppb_init(void)
static int __init hppb_init(void)
{
register_parisc_driver(&hppb_driver);
return register_parisc_driver(&hppb_driver);
}
arch_initcall(hppb_init);
......@@ -348,13 +348,10 @@ iosapic_load_irt(unsigned long cell_num, struct irt_entry **irt)
}
void __init iosapic_init(void)
static int __init iosapic_init(void)
{
unsigned long cell = 0;
DBG("iosapic_init()\n");
#ifdef __LP64__
if (is_pdc_pat()) {
int status;
......@@ -371,7 +368,10 @@ void __init iosapic_init(void)
irt_num_entry = iosapic_load_irt(cell, &irt_cell);
if (irt_num_entry == 0)
irt_cell = NULL; /* old PDC w/o iosapic */
return 0;
}
arch_initcall(iosapic_init);
/*
......@@ -890,7 +890,7 @@ iosapic_rd_version(struct iosapic_info *isi)
** o allocate and initialize isi_vector[]
** o allocate irq region
*/
void *iosapic_register(unsigned long hpa)
void *iosapic_register(unsigned long hpa, void __iomem *vaddr)
{
struct iosapic_info *isi = NULL;
struct irt_entry *irte = irt_cell;
......@@ -919,7 +919,7 @@ void *iosapic_register(unsigned long hpa)
return NULL;
}
isi->addr = ioremap(hpa, 4096);
isi->addr = vaddr;
isi->isi_hpa = hpa;
isi->isi_version = iosapic_rd_version(isi);
isi->isi_num_vectors = IOSAPIC_IRDT_MAX_ENTRY(isi->isi_version) + 1;
......
......@@ -17,6 +17,7 @@
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/types.h>
#include <linux/reboot.h>
#include <asm/io.h>
#include <asm/hardware.h>
......@@ -145,23 +146,19 @@ static void __init lasi_led_init(unsigned long lasi_hpa)
* 1 to PWR_ON_L in the Power Control Register
*
*/
static unsigned long lasi_power_off_hpa __read_mostly;
static void lasi_power_off(void)
static int lasi_power_off(struct sys_off_data *data)
{
unsigned long datareg;
struct gsc_asic *lasi = data->cb_data;
/* calculate addr of the Power Control Register */
datareg = lasi_power_off_hpa + 0x0000C000;
/* Power down the machine via Power Control Register */
gsc_writel(0x02, lasi->hpa + 0x0000C000);
/* Power down the machine */
gsc_writel(0x02, datareg);
/* might not be reached: */
return NOTIFY_DONE;
}
static int __init lasi_init_chip(struct parisc_device *dev)
{
extern void (*chassis_power_off)(void);
struct gsc_asic *lasi;
int ret;
......@@ -212,13 +209,10 @@ static int __init lasi_init_chip(struct parisc_device *dev)
gsc_fixup_irqs(dev, lasi, lasi_choose_irq);
/* initialize the power off function */
/* FIXME: Record the LASI HPA for the power off function. This should
* ensure that only the first LASI (the one controlling the power off)
* should set the HPA here */
lasi_power_off_hpa = lasi->hpa;
chassis_power_off = lasi_power_off;
/* register the LASI power off function */
register_sys_off_handler(SYS_OFF_MODE_POWER_OFF,
SYS_OFF_PRIO_DEFAULT, lasi_power_off, lasi);
return ret;
}
......@@ -226,9 +220,16 @@ static struct parisc_device_id lasi_tbl[] __initdata = {
{ HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00081 },
{ 0, }
};
MODULE_DEVICE_TABLE(parisc, lasi_tbl);
struct parisc_driver lasi_driver __refdata = {
static struct parisc_driver lasi_driver __refdata = {
.name = "lasi",
.id_table = lasi_tbl,
.probe = lasi_init_chip,
};
static int __init lasi_init(void)
{
return register_parisc_driver(&lasi_driver);
}
arch_initcall(lasi_init);
......@@ -1535,7 +1535,8 @@ lba_driver_probe(struct parisc_device *dev)
}
/* Tell I/O SAPIC driver we have a IRQ handler/region. */
tmp_obj = iosapic_register(dev->hpa.start + LBA_IOSAPIC_BASE);
tmp_obj = iosapic_register(dev->hpa.start + LBA_IOSAPIC_BASE,
addr + LBA_IOSAPIC_BASE);
/* NOTE: PCI devices (e.g. 103c:1005 graphics card) which don't
** have an IRT entry will get NULL back from iosapic code.
......@@ -1681,10 +1682,11 @@ static struct parisc_driver lba_driver __refdata = {
** One time initialization to let the world know the LBA was found.
** Must be called exactly once before pci_init().
*/
void __init lba_init(void)
static int __init lba_init(void)
{
register_parisc_driver(&lba_driver);
return register_parisc_driver(&lba_driver);
}
arch_initcall(lba_init);
/*
** Initialize the IBASE/IMASK registers for LBA (Elroy).
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Chassis LCD/LED driver for HP-PARISC workstations
* Chassis LCD/LED driver for HP-PARISC workstations
*
* (c) Copyright 2000 Red Hat Software
* (c) Copyright 2000 Helge Deller <hdeller@redhat.com>
* (c) Copyright 2001-2009 Helge Deller <deller@gmx.de>
* (c) Copyright 2001 Randolph Chung <tausq@debian.org>
* (c) Copyright 2000 Red Hat Software
* (c) Copyright 2000 Helge Deller <hdeller@redhat.com>
* (c) Copyright 2001 Randolph Chung <tausq@debian.org>
* (c) Copyright 2000-2023 Helge Deller <deller@gmx.de>
*
* TODO:
* - speed-up calculations with inlined assembler
* - interface to write to second row of LCD from /proc (if technically possible)
* The control of the LEDs and LCDs on PARISC machines has to be done
* completely in software.
*
* Changes:
* - Audit copy_from_user in led_proc_write.
* Daniele Bellucci <bellucda@tiscali.it>
* - Switch from using a tasklet to a work queue, so the led_LCD_driver
* can sleep.
* David Pye <dmp@davidmpye.dyndns.org>
* The LEDs can be configured at runtime in /sys/class/leds/
*/
#include <linux/module.h>
#include <linux/stddef.h> /* for offsetof() */
#include <linux/init.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <linux/utsname.h>
#include <linux/capability.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/in.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/reboot.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/ctype.h>
#include <linux/blkdev.h>
#include <linux/workqueue.h>
#include <linux/rcupdate.h>
#include <linux/uaccess.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/processor.h>
#include <asm/hardware.h>
#include <asm/param.h> /* HZ */
#include <asm/led.h>
#include <asm/pdc.h>
#include <linux/uaccess.h>
/* The control of the LEDs and LCDs on PARISC-machines have to be done
completely in software. The necessary calculations are done in a work queue
task which is scheduled regularly, and since the calculations may consume a
relatively large amount of CPU time, some of the calculations can be
turned off with the following variables (controlled via procfs) */
static int led_type __read_mostly = -1;
static unsigned char lastleds; /* LED state from most recent update */
static unsigned int led_heartbeat __read_mostly = 1;
static unsigned int led_diskio __read_mostly = 1;
static unsigned int led_lanrxtx __read_mostly = 1;
static char lcd_text[32] __read_mostly;
static char lcd_text_default[32] __read_mostly;
static int lcd_no_led_support __read_mostly = 0; /* KittyHawk doesn't support LED on its LCD */
#define LED_HAS_LCD 1
#define LED_HAS_LED 2
static struct workqueue_struct *led_wq;
static void led_work_func(struct work_struct *);
static DECLARE_DELAYED_WORK(led_task, led_work_func);
#if 0
#define DPRINTK(x) printk x
#else
#define DPRINTK(x)
#endif
static unsigned char led_type; /* bitmask of LED_HAS_XXX */
static unsigned char lastleds; /* LED state from most recent update */
static unsigned char lcd_new_text;
static unsigned char lcd_text[20];
static unsigned char lcd_text_default[20];
static unsigned char lcd_no_led_support; /* KittyHawk doesn't support LED on its LCD */
struct lcd_block {
unsigned char command; /* stores the command byte */
......@@ -80,7 +49,7 @@ struct lcd_block {
};
/* Structure returned by PDC_RETURN_CHASSIS_INFO */
/* NOTE: we use unsigned long:16 two times, since the following member
/* NOTE: we use unsigned long:16 two times, since the following member
lcd_cmd_reg_addr needs to be 64bit aligned on 64bit PA2.0-machines */
struct pdc_chassis_lcd_info_ret_block {
unsigned long model:16; /* DISPLAY_MODEL_XXXX */
......@@ -100,15 +69,15 @@ struct pdc_chassis_lcd_info_ret_block {
/* LCD_CMD and LCD_DATA for KittyHawk machines */
#define KITTYHAWK_LCD_CMD F_EXTEND(0xf0190000UL) /* 64bit-ready */
#define KITTYHAWK_LCD_DATA (KITTYHAWK_LCD_CMD+1)
#define KITTYHAWK_LCD_CMD F_EXTEND(0xf0190000UL)
#define KITTYHAWK_LCD_DATA (KITTYHAWK_LCD_CMD + 1)
/* lcd_info is pre-initialized to the values needed to program KittyHawk LCD's
/* lcd_info is pre-initialized to the values needed to program KittyHawk LCD's
* HP seems to have used Sharp/Hitachi HD44780 LCDs most of the time. */
static struct pdc_chassis_lcd_info_ret_block
lcd_info __attribute__((aligned(8))) __read_mostly =
lcd_info __attribute__((aligned(8))) =
{
.model = DISPLAY_MODEL_LCD,
.model = DISPLAY_MODEL_NONE,
.lcd_width = 16,
.lcd_cmd_reg_addr = KITTYHAWK_LCD_CMD,
.lcd_data_reg_addr = KITTYHAWK_LCD_DATA,
......@@ -117,165 +86,65 @@ lcd_info __attribute__((aligned(8))) __read_mostly =
.reset_cmd2 = 0xc0,
};
/* direct access to some of the lcd_info variables */
#define LCD_CMD_REG lcd_info.lcd_cmd_reg_addr
#define LCD_DATA_REG lcd_info.lcd_data_reg_addr
#define LCD_CMD_REG lcd_info.lcd_cmd_reg_addr
#define LCD_DATA_REG lcd_info.lcd_data_reg_addr
#define LED_DATA_REG lcd_info.lcd_cmd_reg_addr /* LASI & ASP only */
#define LED_HASLCD 1
#define LED_NOLCD 0
/* The workqueue must be created at init-time */
static int start_task(void)
{
/* Display the default text now */
if (led_type == LED_HASLCD) lcd_print( lcd_text_default );
/* KittyHawk has no LED support on its LCD */
if (lcd_no_led_support) return 0;
/* Create the work queue and queue the LED task */
led_wq = create_singlethread_workqueue("led_wq");
if (!led_wq)
return -ENOMEM;
queue_delayed_work(led_wq, &led_task, 0);
return 0;
}
device_initcall(start_task);
/* ptr to LCD/LED-specific function */
static void (*led_func_ptr) (unsigned char) __read_mostly;
#ifdef CONFIG_PROC_FS
static int led_proc_show(struct seq_file *m, void *v)
{
switch ((long)m->private)
{
case LED_NOLCD:
seq_printf(m, "Heartbeat: %d\n", led_heartbeat);
seq_printf(m, "Disk IO: %d\n", led_diskio);
seq_printf(m, "LAN Rx/Tx: %d\n", led_lanrxtx);
break;
case LED_HASLCD:
seq_printf(m, "%s\n", lcd_text);
break;
default:
return 0;
}
return 0;
}
static int led_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, led_proc_show, pde_data(inode));
}
static void (*led_func_ptr) (unsigned char);
static ssize_t led_proc_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
static void lcd_print_now(void)
{
void *data = pde_data(file_inode(file));
char *cur, lbuf[32];
int d;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (count >= sizeof(lbuf))
count = sizeof(lbuf)-1;
if (copy_from_user(lbuf, buf, count))
return -EFAULT;
lbuf[count] = 0;
cur = lbuf;
switch ((long)data)
{
case LED_NOLCD:
d = *cur++ - '0';
if (d != 0 && d != 1) goto parse_error;
led_heartbeat = d;
if (*cur++ != ' ') goto parse_error;
int i;
char *str = lcd_text;
d = *cur++ - '0';
if (d != 0 && d != 1) goto parse_error;
led_diskio = d;
if (lcd_info.model != DISPLAY_MODEL_LCD)
return;
if (*cur++ != ' ') goto parse_error;
if (!lcd_new_text)
return;
lcd_new_text = 0;
d = *cur++ - '0';
if (d != 0 && d != 1) goto parse_error;
led_lanrxtx = d;
/* Set LCD Cursor to 1st character */
gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG);
udelay(lcd_info.min_cmd_delay);
break;
case LED_HASLCD:
if (*cur && cur[strlen(cur)-1] == '\n')
cur[strlen(cur)-1] = 0;
if (*cur == 0)
cur = lcd_text_default;
lcd_print(cur);
break;
default:
return 0;
/* Print the string */
for (i = 0; i < lcd_info.lcd_width; i++) {
gsc_writeb(*str ? *str++ : ' ', LCD_DATA_REG);
udelay(lcd_info.min_cmd_delay);
}
return count;
parse_error:
if ((long)data == LED_NOLCD)
printk(KERN_CRIT "Parse error: expect \"n n n\" (n == 0 or 1) for heartbeat,\ndisk io and lan tx/rx indicators\n");
return -EINVAL;
}
static const struct proc_ops led_proc_ops = {
.proc_open = led_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
.proc_write = led_proc_write,
};
static int __init led_create_procfs(void)
/**
* lcd_print()
*
* @str: string to show on the LCD. If NULL, print current string again.
*
* Displays the given string on the LCD-Display of newer machines.
*/
void lcd_print(const char *str)
{
struct proc_dir_entry *proc_pdc_root = NULL;
struct proc_dir_entry *ent;
if (led_type == -1) return -1;
proc_pdc_root = proc_mkdir("pdc", NULL);
if (!proc_pdc_root) return -1;
if (!lcd_no_led_support)
{
ent = proc_create_data("led", 0644, proc_pdc_root,
&led_proc_ops, (void *)LED_NOLCD); /* LED */
if (!ent) return -1;
}
if (led_type == LED_HASLCD)
{
ent = proc_create_data("lcd", 0644, proc_pdc_root,
&led_proc_ops, (void *)LED_HASLCD); /* LCD */
if (!ent) return -1;
}
/* copy display string to buffer for procfs */
if (str)
strscpy(lcd_text, str, sizeof(lcd_text));
lcd_new_text = 1;
return 0;
/* print now if LCD without any LEDs */
if (led_type == LED_HAS_LCD)
lcd_print_now();
}
#endif
/*
**
** led_ASP_driver()
**
*/
#define LED_DATA 0x01 /* data to shift (0:on 1:off) */
#define LED_STROBE 0x02 /* strobe to clock data */
/**
* led_ASP_driver() - LED driver for the ASP controller chip
*
* @leds: bitmap representing the LED status
*/
static void led_ASP_driver(unsigned char leds)
{
int i;
......@@ -290,11 +159,10 @@ static void led_ASP_driver(unsigned char leds)
}
}
/*
**
** led_LASI_driver()
**
/**
* led_LASI_driver() - LED driver for the LASI controller chip
*
* @leds: bitmap representing the LED status
*/
static void led_LASI_driver(unsigned char leds)
{
......@@ -302,395 +170,298 @@ static void led_LASI_driver(unsigned char leds)
gsc_writeb( leds, LED_DATA_REG );
}
/*
**
** led_LCD_driver()
**
/**
* led_LCD_driver() - LED & LCD driver for LCD chips
*
* @leds: bitmap representing the LED status
*/
static void led_LCD_driver(unsigned char leds)
{
static int i;
static unsigned char mask[4] = { LED_HEARTBEAT, LED_DISK_IO,
static const unsigned char mask[4] = {
LED_HEARTBEAT, LED_DISK_IO,
LED_LAN_RCV, LED_LAN_TX };
static struct lcd_block * blockp[4] = {
static struct lcd_block * const blockp[4] = {
&lcd_info.heartbeat,
&lcd_info.disk_io,
&lcd_info.lan_rcv,
&lcd_info.lan_tx
};
static unsigned char latest_leds;
int i;
/* Convert min_cmd_delay to milliseconds */
unsigned int msec_cmd_delay = 1 + (lcd_info.min_cmd_delay / 1000);
for (i=0; i<4; ++i)
{
if ((leds & mask[i]) != (lastleds & mask[i]))
{
gsc_writeb( blockp[i]->command, LCD_CMD_REG );
msleep(msec_cmd_delay);
gsc_writeb( leds & mask[i] ? blockp[i]->on :
blockp[i]->off, LCD_DATA_REG );
msleep(msec_cmd_delay);
}
for (i = 0; i < 4; ++i) {
if ((leds & mask[i]) == (latest_leds & mask[i]))
continue;
gsc_writeb( blockp[i]->command, LCD_CMD_REG );
udelay(lcd_info.min_cmd_delay);
gsc_writeb( leds & mask[i] ? blockp[i]->on :
blockp[i]->off, LCD_DATA_REG );
udelay(lcd_info.min_cmd_delay);
}
latest_leds = leds;
lcd_print_now();
}
/*
**
** led_get_net_activity()
**
** calculate if there was TX- or RX-throughput on the network interfaces
** (analog to dev_get_info() from net/core/dev.c)
**
/**
* lcd_system_halt()
*
* @nb: pointer to the notifier_block structure
* @event: the event (SYS_RESTART, SYS_HALT or SYS_POWER_OFF)
* @buf: pointer to a buffer (not used)
*
* Called by the reboot notifier chain at shutdown. Stops all
* LED/LCD activities.
*/
static __inline__ int led_get_net_activity(void)
{
#ifndef CONFIG_NET
return 0;
#else
static u64 rx_total_last, tx_total_last;
u64 rx_total, tx_total;
struct net_device *dev;
int retval;
rx_total = tx_total = 0;
/* we are running as a workqueue task, so we can use an RCU lookup */
rcu_read_lock();
for_each_netdev_rcu(&init_net, dev) {
const struct rtnl_link_stats64 *stats;
struct rtnl_link_stats64 temp;
struct in_device *in_dev = __in_dev_get_rcu(dev);
if (!in_dev || !in_dev->ifa_list)
continue;
if (ipv4_is_loopback(in_dev->ifa_list->ifa_local))
continue;
stats = dev_get_stats(dev, &temp);
rx_total += stats->rx_packets;
tx_total += stats->tx_packets;
}
rcu_read_unlock();
retval = 0;
static int lcd_system_halt(struct notifier_block *nb, unsigned long event, void *buf)
{
const char *txt;
if (rx_total != rx_total_last) {
rx_total_last = rx_total;
retval |= LED_LAN_RCV;
switch (event) {
case SYS_RESTART: txt = "SYSTEM RESTART";
break;
case SYS_HALT: txt = "SYSTEM HALT";
break;
case SYS_POWER_OFF: txt = "SYSTEM POWER OFF";
break;
default: return NOTIFY_DONE;
}
if (tx_total != tx_total_last) {
tx_total_last = tx_total;
retval |= LED_LAN_TX;
}
lcd_print(txt);
return retval;
#endif
return NOTIFY_OK;
}
static struct notifier_block lcd_system_halt_notifier = {
.notifier_call = lcd_system_halt,
};
/*
**
** led_get_diskio_activity()
**
** calculate if there was disk-io in the system
**
*/
static __inline__ int led_get_diskio_activity(void)
{
static unsigned long last_pgpgin, last_pgpgout;
unsigned long events[NR_VM_EVENT_ITEMS];
int changed;
all_vm_events(events);
/* Just use a very simple calculation here. Do not care about overflow,
since we only want to know if there was activity or not. */
changed = (events[PGPGIN] != last_pgpgin) ||
(events[PGPGOUT] != last_pgpgout);
last_pgpgin = events[PGPGIN];
last_pgpgout = events[PGPGOUT];
return (changed ? LED_DISK_IO : 0);
}
static void set_led(struct led_classdev *led_cdev, enum led_brightness brightness);
struct hppa_led {
struct led_classdev led_cdev;
unsigned char led_bit;
};
#define to_hppa_led(d) container_of(d, struct hppa_led, led_cdev)
/*
** led_work_func()
**
** manages when and which chassis LCD/LED gets updated
typedef void (*set_handler)(struct led_classdev *, enum led_brightness);
struct led_type {
const char *name;
set_handler handler;
const char *default_trigger;
};
TODO:
- display load average (older machines like 715/64 have 4 "free" LED's for that)
- optimizations
*/
#define NUM_LEDS_PER_BOARD 8
struct hppa_drvdata {
struct hppa_led leds[NUM_LEDS_PER_BOARD];
};
#define HEARTBEAT_LEN (HZ*10/100)
#define HEARTBEAT_2ND_RANGE_START (HZ*28/100)
#define HEARTBEAT_2ND_RANGE_END (HEARTBEAT_2ND_RANGE_START + HEARTBEAT_LEN)
static void set_led(struct led_classdev *led_cdev, enum led_brightness brightness)
{
struct hppa_led *p = to_hppa_led(led_cdev);
unsigned char led_bit = p->led_bit;
#define LED_UPDATE_INTERVAL (1 + (HZ*19/1000))
if (brightness == LED_OFF)
lastleds &= ~led_bit;
else
lastleds |= led_bit;
static void led_work_func (struct work_struct *unused)
{
static unsigned long last_jiffies;
static unsigned long count_HZ; /* counter in range 0..HZ */
unsigned char currentleds = 0; /* stores current value of the LEDs */
if (led_func_ptr)
led_func_ptr(lastleds);
}
/* exit if not initialized */
if (!led_func_ptr)
return;
/* increment the heartbeat timekeeper */
count_HZ += jiffies - last_jiffies;
last_jiffies = jiffies;
if (count_HZ >= HZ)
count_HZ = 0;
static int hppa_led_generic_probe(struct platform_device *pdev,
struct led_type *types)
{
struct hppa_drvdata *p;
int i, err;
if (likely(led_heartbeat))
{
/* flash heartbeat-LED like a real heart
* (2 x short then a long delay)
*/
if (count_HZ < HEARTBEAT_LEN ||
(count_HZ >= HEARTBEAT_2ND_RANGE_START &&
count_HZ < HEARTBEAT_2ND_RANGE_END))
currentleds |= LED_HEARTBEAT;
}
p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
if (likely(led_lanrxtx)) currentleds |= led_get_net_activity();
if (likely(led_diskio)) currentleds |= led_get_diskio_activity();
/* blink LEDs if we got an Oops (HPMC) */
if (unlikely(oops_in_progress)) {
if (boot_cpu_data.cpu_type >= pcxl2) {
/* newer machines don't have loadavg. LEDs, so we
* let all LEDs blink twice per second instead */
currentleds = (count_HZ <= (HZ/2)) ? 0 : 0xff;
} else {
/* old machines: blink loadavg. LEDs twice per second */
if (count_HZ <= (HZ/2))
currentleds &= ~(LED4|LED5|LED6|LED7);
else
currentleds |= (LED4|LED5|LED6|LED7);
for (i = 0; i < NUM_LEDS_PER_BOARD; i++) {
struct led_classdev *lp = &p->leds[i].led_cdev;
p->leds[i].led_bit = BIT(i);
lp->name = types[i].name;
lp->brightness = LED_FULL;
lp->brightness_set = types[i].handler;
lp->default_trigger = types[i].default_trigger;
err = led_classdev_register(&pdev->dev, lp);
if (err) {
dev_err(&pdev->dev, "Could not register %s LED\n",
lp->name);
for (i--; i >= 0; i--)
led_classdev_unregister(&p->leds[i].led_cdev);
return err;
}
}
if (currentleds != lastleds)
{
led_func_ptr(currentleds); /* Update the LCD/LEDs */
lastleds = currentleds;
}
platform_set_drvdata(pdev, p);
queue_delayed_work(led_wq, &led_task, LED_UPDATE_INTERVAL);
return 0;
}
/*
** led_halt()
**
** called by the reboot notifier chain at shutdown and stops all
** LED/LCD activities.
**
*/
static int platform_led_remove(struct platform_device *pdev)
{
struct hppa_drvdata *p = platform_get_drvdata(pdev);
int i;
static int led_halt(struct notifier_block *, unsigned long, void *);
for (i = 0; i < NUM_LEDS_PER_BOARD; i++)
led_classdev_unregister(&p->leds[i].led_cdev);
static struct notifier_block led_notifier = {
.notifier_call = led_halt,
return 0;
}
static struct led_type mainboard_led_types[NUM_LEDS_PER_BOARD] = {
{
.name = "platform-lan-tx",
.handler = set_led,
.default_trigger = "tx",
},
{
.name = "platform-lan-rx",
.handler = set_led,
.default_trigger = "rx",
},
{
.name = "platform-disk",
.handler = set_led,
.default_trigger = "disk-activity",
},
{
.name = "platform-heartbeat",
.handler = set_led,
.default_trigger = "heartbeat",
},
{
.name = "platform-LED4",
.handler = set_led,
.default_trigger = "panic",
},
{
.name = "platform-LED5",
.handler = set_led,
.default_trigger = "panic",
},
{
.name = "platform-LED6",
.handler = set_led,
.default_trigger = "panic",
},
{
.name = "platform-LED7",
.handler = set_led,
.default_trigger = "panic",
},
};
static int notifier_disabled = 0;
static int led_halt(struct notifier_block *nb, unsigned long event, void *buf)
static int platform_led_probe(struct platform_device *pdev)
{
char *txt;
return hppa_led_generic_probe(pdev, mainboard_led_types);
}
if (notifier_disabled)
return NOTIFY_OK;
MODULE_ALIAS("platform:platform-leds");
notifier_disabled = 1;
switch (event) {
case SYS_RESTART: txt = "SYSTEM RESTART";
break;
case SYS_HALT: txt = "SYSTEM HALT";
break;
case SYS_POWER_OFF: txt = "SYSTEM POWER OFF";
break;
default: return NOTIFY_DONE;
}
/* Cancel the work item and delete the queue */
if (led_wq) {
cancel_delayed_work_sync(&led_task);
destroy_workqueue(led_wq);
led_wq = NULL;
}
if (lcd_info.model == DISPLAY_MODEL_LCD)
lcd_print(txt);
else
if (led_func_ptr)
led_func_ptr(0xff); /* turn all LEDs ON */
return NOTIFY_OK;
}
static struct platform_driver hppa_mainboard_led_driver = {
.probe = platform_led_probe,
.remove = platform_led_remove,
.driver = {
.name = "platform-leds",
},
};
/*
** register_led_driver()
**
** registers an external LED or LCD for usage by this driver.
** currently only LCD-, LASI- and ASP-style LCD/LED's are supported.
**
*/
static struct platform_driver * const drivers[] = {
&hppa_mainboard_led_driver,
};
static struct platform_device platform_leds = {
.name = "platform-leds",
};
/**
* register_led_driver()
*
* @model: model type, one of the DISPLAY_MODEL_XXXX values
* @cmd_reg: physical address of cmd register for the LED/LCD
* @data_reg: physical address of data register for the LED/LCD
*
* Registers a chassis LED or LCD which should be driven by this driver.
* Only PDC-based, LASI- or ASP-style LEDs and LCDs are supported.
*/
int __init register_led_driver(int model, unsigned long cmd_reg, unsigned long data_reg)
{
static int initialized;
if (initialized || !data_reg)
if (led_func_ptr || !data_reg)
return 1;
/* No LEDs when running in QEMU */
if (running_on_qemu)
return 1;
lcd_info.model = model; /* store the values */
LCD_CMD_REG = (cmd_reg == LED_CMD_REG_NONE) ? 0 : cmd_reg;
switch (lcd_info.model) {
case DISPLAY_MODEL_LCD:
LCD_DATA_REG = data_reg;
printk(KERN_INFO "LCD display at %lx,%lx registered\n",
pr_info("led: LCD display at %#lx and %#lx\n",
LCD_CMD_REG , LCD_DATA_REG);
led_func_ptr = led_LCD_driver;
led_type = LED_HASLCD;
if (lcd_no_led_support)
led_type = LED_HAS_LCD;
else
led_type = LED_HAS_LCD | LED_HAS_LED;
break;
case DISPLAY_MODEL_LASI:
/* Skip to register LED in QEMU */
if (running_on_qemu)
return 1;
LED_DATA_REG = data_reg;
led_func_ptr = led_LASI_driver;
printk(KERN_INFO "LED display at %lx registered\n", LED_DATA_REG);
led_type = LED_NOLCD;
pr_info("led: LED display at %#lx\n", LED_DATA_REG);
led_type = LED_HAS_LED;
break;
case DISPLAY_MODEL_OLD_ASP:
LED_DATA_REG = data_reg;
led_func_ptr = led_ASP_driver;
printk(KERN_INFO "LED (ASP-style) display at %lx registered\n",
pr_info("led: LED (ASP-style) display at %#lx\n",
LED_DATA_REG);
led_type = LED_NOLCD;
led_type = LED_HAS_LED;
break;
default:
printk(KERN_ERR "%s: Wrong LCD/LED model %d !\n",
__func__, lcd_info.model);
pr_err("led: Unknown LCD/LED model type %d\n", lcd_info.model);
return 1;
}
/* mark the LCD/LED driver now as initialized and
* register to the reboot notifier chain */
initialized++;
register_reboot_notifier(&led_notifier);
/* Ensure the work is queued */
if (led_wq) {
queue_delayed_work(led_wq, &led_task, 0);
}
return 0;
}
/*
** register_led_regions()
**
** register_led_regions() registers the LCD/LED regions for /procfs.
** At bootup - where the initialisation of the LCD/LED normally happens -
** not all internal structures of request_region() are properly set up,
** so that we delay the led-registration until after busdevices_init()
** has been executed.
**
*/
platform_register_drivers(drivers, ARRAY_SIZE(drivers));
void __init register_led_regions(void)
{
switch (lcd_info.model) {
case DISPLAY_MODEL_LCD:
request_mem_region((unsigned long)LCD_CMD_REG, 1, "lcd_cmd");
request_mem_region((unsigned long)LCD_DATA_REG, 1, "lcd_data");
break;
case DISPLAY_MODEL_LASI:
case DISPLAY_MODEL_OLD_ASP:
request_mem_region((unsigned long)LED_DATA_REG, 1, "led_data");
break;
}
}
/*
**
** lcd_print()
**
** Displays the given string on the LCD-Display of newer machines.
** lcd_print() disables/enables the timer-based led work queue to
** avoid a race condition while writing the CMD/DATA register pair.
**
*/
int lcd_print( const char *str )
{
int i;
if (!led_func_ptr || lcd_info.model != DISPLAY_MODEL_LCD)
return 0;
/* temporarily disable the led work task */
if (led_wq)
cancel_delayed_work_sync(&led_task);
/* copy display string to buffer for procfs */
strscpy(lcd_text, str, sizeof(lcd_text));
/* Set LCD Cursor to 1st character */
gsc_writeb(lcd_info.reset_cmd1, LCD_CMD_REG);
udelay(lcd_info.min_cmd_delay);
/* Print the string */
for (i=0; i < lcd_info.lcd_width; i++) {
if (str && *str)
gsc_writeb(*str++, LCD_DATA_REG);
else
gsc_writeb(' ', LCD_DATA_REG);
udelay(lcd_info.min_cmd_delay);
}
/* re-queue the work */
if (led_wq) {
queue_delayed_work(led_wq, &led_task, 0);
}
return lcd_info.lcd_width;
return register_reboot_notifier(&lcd_system_halt_notifier);
}
/*
** led_init()
**
** led_init() is called very early in the bootup-process from setup.c
** and asks the PDC for an usable chassis LCD or LED.
** If the PDC doesn't return any info, then the LED
** is detected by lasi.c or asp.c and registered with the
** above functions lasi_led_init() or asp_led_init().
** KittyHawk machines have often a buggy PDC, so that
** we explicitly check for those machines here.
/**
* early_led_init()
*
* early_led_init() is called early in the bootup-process and asks the
* PDC for an usable chassis LCD or LED. If the PDC doesn't return any
* info, then a LED might be detected by the LASI or ASP drivers later.
* KittyHawk machines have often a buggy PDC, so that we explicitly check
* for those machines here.
*/
int __init led_init(void)
static int __init early_led_init(void)
{
struct pdc_chassis_info chassis_info;
int ret;
snprintf(lcd_text_default, sizeof(lcd_text_default),
"Linux %s", init_utsname()->release);
strcpy(lcd_text, lcd_text_default);
lcd_new_text = 1;
/* Work around the buggy PDC of KittyHawk-machines */
switch (CPU_HVERSION) {
......@@ -699,82 +470,86 @@ int __init led_init(void)
case 0x582: /* KittyHawk DC3 100 (K400) */
case 0x583: /* KittyHawk DC3 120 (K410) */
case 0x58B: /* KittyHawk DC2 100 (K200) */
printk(KERN_INFO "%s: KittyHawk-Machine (hversion 0x%x) found, "
"LED detection skipped.\n", __FILE__, CPU_HVERSION);
pr_info("LCD on KittyHawk-Machine found.\n");
lcd_info.model = DISPLAY_MODEL_LCD;
/* KittyHawk has no LED support on its LCD, so skip LED detection */
lcd_no_led_support = 1;
goto found; /* use the preinitialized values of lcd_info */
}
/* initialize the struct, so that we can check for valid return values */
lcd_info.model = DISPLAY_MODEL_NONE;
chassis_info.actcnt = chassis_info.maxcnt = 0;
ret = pdc_chassis_info(&chassis_info, &lcd_info, sizeof(lcd_info));
if (ret == PDC_OK) {
DPRINTK((KERN_INFO "%s: chassis info: model=%d (%s), "
"lcd_width=%d, cmd_delay=%u,\n"
"%s: sizecnt=%d, actcnt=%ld, maxcnt=%ld\n",
__FILE__, lcd_info.model,
(lcd_info.model==DISPLAY_MODEL_LCD) ? "LCD" :
(lcd_info.model==DISPLAY_MODEL_LASI) ? "LED" : "unknown",
lcd_info.lcd_width, lcd_info.min_cmd_delay,
__FILE__, sizeof(lcd_info),
chassis_info.actcnt, chassis_info.maxcnt));
DPRINTK((KERN_INFO "%s: cmd=%p, data=%p, reset1=%x, reset2=%x, act_enable=%d\n",
__FILE__, lcd_info.lcd_cmd_reg_addr,
lcd_info.lcd_data_reg_addr, lcd_info.reset_cmd1,
lcd_info.reset_cmd2, lcd_info.act_enable ));
/* check the results. Some machines have a buggy PDC */
if (chassis_info.actcnt <= 0 || chassis_info.actcnt != chassis_info.maxcnt)
goto not_found;
if (ret != PDC_OK) {
not_found:
lcd_info.model = DISPLAY_MODEL_NONE;
return 1;
}
switch (lcd_info.model) {
case DISPLAY_MODEL_LCD: /* LCD display */
if (chassis_info.actcnt <
offsetof(struct pdc_chassis_lcd_info_ret_block, _pad)-1)
goto not_found;
if (!lcd_info.act_enable) {
DPRINTK((KERN_INFO "PDC prohibited usage of the LCD.\n"));
goto not_found;
}
break;
case DISPLAY_MODEL_NONE: /* no LED or LCD available */
printk(KERN_INFO "PDC reported no LCD or LED.\n");
/* check the results. Some machines have a buggy PDC */
if (chassis_info.actcnt <= 0 || chassis_info.actcnt != chassis_info.maxcnt)
goto not_found;
switch (lcd_info.model) {
case DISPLAY_MODEL_LCD: /* LCD display */
if (chassis_info.actcnt <
offsetof(struct pdc_chassis_lcd_info_ret_block, _pad)-1)
goto not_found;
if (!lcd_info.act_enable) {
/* PDC tells LCD should not be used. */
goto not_found;
}
break;
case DISPLAY_MODEL_LASI: /* Lasi style 8 bit LED display */
if (chassis_info.actcnt != 8 && chassis_info.actcnt != 32)
goto not_found;
break;
case DISPLAY_MODEL_NONE: /* no LED or LCD available */
goto not_found;
default:
printk(KERN_WARNING "PDC reported unknown LCD/LED model %d\n",
lcd_info.model);
case DISPLAY_MODEL_LASI: /* Lasi style 8 bit LED display */
if (chassis_info.actcnt != 8 && chassis_info.actcnt != 32)
goto not_found;
} /* switch() */
found:
/* register the LCD/LED driver */
register_led_driver(lcd_info.model, LCD_CMD_REG, LCD_DATA_REG);
return 0;
break;
} else { /* if() */
DPRINTK((KERN_INFO "pdc_chassis_info call failed with retval = %d\n", ret));
default:
pr_warn("PDC reported unknown LCD/LED model %d\n",
lcd_info.model);
goto not_found;
}
not_found:
lcd_info.model = DISPLAY_MODEL_NONE;
return 1;
found:
/* register the LCD/LED driver */
return register_led_driver(lcd_info.model, LCD_CMD_REG, LCD_DATA_REG);
}
arch_initcall(early_led_init);
static void __exit led_exit(void)
/**
* register_led_regions()
*
* Register_led_regions() registers the LCD/LED regions for /procfs.
* At bootup - where the initialisation of the LCD/LED often happens
* not all internal structures of request_region() are properly set up,
* so that we delay the led-registration until after busdevices_init()
* has been executed.
*/
static void __init register_led_regions(void)
{
unregister_reboot_notifier(&led_notifier);
return;
switch (lcd_info.model) {
case DISPLAY_MODEL_LCD:
request_mem_region((unsigned long)LCD_CMD_REG, 1, "lcd_cmd");
request_mem_region((unsigned long)LCD_DATA_REG, 1, "lcd_data");
break;
case DISPLAY_MODEL_LASI:
case DISPLAY_MODEL_OLD_ASP:
request_mem_region((unsigned long)LED_DATA_REG, 1, "led_data");
break;
}
}
#ifdef CONFIG_PROC_FS
module_init(led_create_procfs)
#endif
static int __init startup_leds(void)
{
if (platform_device_register(&platform_leds))
printk(KERN_INFO "LED: failed to register LEDs\n");
register_led_regions();
return 0;
}
device_initcall(startup_leds);
......@@ -121,7 +121,7 @@ module_param(sba_reserve_agpgart, int, 0444);
MODULE_PARM_DESC(sba_reserve_agpgart, "Reserve half of IO pdir as AGPGART");
#endif
struct proc_dir_entry *proc_runway_root __ro_after_init;
static struct proc_dir_entry *proc_runway_root __ro_after_init;
struct proc_dir_entry *proc_mckinley_root __ro_after_init;
/************************************
......@@ -1994,10 +1994,11 @@ static int __init sba_driver_callback(struct parisc_device *dev)
** This is the only routine which is NOT static.
** Must be called exactly once before pci_init().
*/
void __init sba_init(void)
static int __init sba_init(void)
{
register_parisc_driver(&sba_driver);
return register_parisc_driver(&sba_driver);
}
arch_initcall(sba_init);
/**
......
......@@ -4,7 +4,7 @@
*
* (c) Copyright 2000 The Puffin Group Inc.
*
* by Helge Deller <deller@gmx.de>
* (c) 2000-2023 by Helge Deller <deller@gmx.de>
*/
#include <linux/errno.h>
......@@ -121,14 +121,20 @@ static int __init wax_init_chip(struct parisc_device *dev)
}
static const struct parisc_device_id wax_tbl[] __initconst = {
{ HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0008e },
{ HPHW_BA, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0008e },
{ 0, }
};
MODULE_DEVICE_TABLE(parisc, wax_tbl);
struct parisc_driver wax_driver __refdata = {
static struct parisc_driver wax_driver __refdata = {
.name = "wax",
.id_table = wax_tbl,
.probe = wax_init_chip,
};
static int __init wax_init(void)
{
return register_parisc_driver(&wax_driver);
}
arch_initcall(wax_init);
......@@ -396,7 +396,10 @@ static int mmap_is_legacy(struct rlimit *rlim_stack)
if (current->personality & ADDR_COMPAT_LAYOUT)
return 1;
if (rlim_stack->rlim_cur == RLIM_INFINITY)
/* On parisc the stack always grows up - so a unlimited stack should
* not be an indicator to use the legacy memory layout. */
if (rlim_stack->rlim_cur == RLIM_INFINITY &&
!IS_ENABLED(CONFIG_STACK_GROWSUP))
return 1;
return sysctl_legacy_va_layout;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment