diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2008-01-25 16:40:02 (GMT) |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-01-25 16:40:02 (GMT) |
commit | 7556afa0e0e436cad4f560ee83e5fbd5dac9359a (patch) | |
tree | c1500918b4b7c8b760feab1c8eeb8a815d2135ca /arch/avr32/kernel | |
parent | e07dd2ad305f6b29b47d713600aa8b722ef2a9f7 (diff) | |
parent | d6c49a7a78fc841418bbd58bda504076f80ec51d (diff) | |
download | linux-7556afa0e0e436cad4f560ee83e5fbd5dac9359a.tar.xz |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hskinnemoen/avr32-2.6
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/hskinnemoen/avr32-2.6:
[AVR32] extint: Set initial irq type to low level
[AVR32] extint: change set_irq_type() handling
[AVR32] NMI debugging
[AVR32] constify function pointer tables
[AVR32] ATNGW100: Update defconfig
[AVR32] ATSTK1002: Update defconfig
[AVR32] Kconfig: Choose daughterboard instead of CPU
[AVR32] Add support for ATSTK1003 and ATSTK1004
[AVR32] Clean up external DAC setup code
[AVR32] ATSTK1000: Move gpio-leds setup to setup.c
[AVR32] Add support for AT32AP7001 and AT32AP7002
[AVR32] Provide more CPU information in /proc/cpuinfo and dmesg
[AVR32] Oprofile support
[AVR32] Include instrumentation menu
Disable VGA text console for AVR32 architecture
[AVR32] Enable debugging only when needed
ptrace: Call arch_ptrace_attach() when request=PTRACE_TRACEME
[AVR32] Remove redundant try_to_freeze() call from do_signal()
[AVR32] Drop GFP_COMP for DMA memory allocations
Diffstat (limited to 'arch/avr32/kernel')
-rw-r--r-- | arch/avr32/kernel/Makefile | 3 | ||||
-rw-r--r-- | arch/avr32/kernel/cpu.c | 96 | ||||
-rw-r--r-- | arch/avr32/kernel/irq.c | 11 | ||||
-rw-r--r-- | arch/avr32/kernel/kprobes.c | 5 | ||||
-rw-r--r-- | arch/avr32/kernel/nmi_debug.c | 82 | ||||
-rw-r--r-- | arch/avr32/kernel/ocd.c | 163 | ||||
-rw-r--r-- | arch/avr32/kernel/process.c | 5 | ||||
-rw-r--r-- | arch/avr32/kernel/ptrace.c | 5 | ||||
-rw-r--r-- | arch/avr32/kernel/signal.c | 7 | ||||
-rw-r--r-- | arch/avr32/kernel/traps.c | 21 |
10 files changed, 352 insertions, 46 deletions
diff --git a/arch/avr32/kernel/Makefile b/arch/avr32/kernel/Makefile index 2d6d48f..e4b6d12 100644 --- a/arch/avr32/kernel/Makefile +++ b/arch/avr32/kernel/Makefile @@ -6,9 +6,10 @@ extra-y := head.o vmlinux.lds obj-$(CONFIG_SUBARCH_AVR32B) += entry-avr32b.o obj-y += syscall_table.o syscall-stubs.o irq.o -obj-y += setup.o traps.o semaphore.o ptrace.o +obj-y += setup.o traps.o semaphore.o ocd.o ptrace.o obj-y += signal.o sys_avr32.o process.o time.o obj-y += init_task.o switch_to.o cpu.o obj-$(CONFIG_MODULES) += module.o avr32_ksyms.o obj-$(CONFIG_KPROBES) += kprobes.o obj-$(CONFIG_STACKTRACE) += stacktrace.o +obj-$(CONFIG_NMI_DEBUGGING) += nmi_debug.o diff --git a/arch/avr32/kernel/cpu.c b/arch/avr32/kernel/cpu.c index 2714cf6..b8409ca 100644 --- a/arch/avr32/kernel/cpu.c +++ b/arch/avr32/kernel/cpu.c @@ -13,6 +13,7 @@ #include <linux/percpu.h> #include <linux/param.h> #include <linux/errno.h> +#include <linux/clk.h> #include <asm/setup.h> #include <asm/sysreg.h> @@ -187,9 +188,20 @@ static int __init topology_init(void) subsys_initcall(topology_init); +struct chip_id_map { + u16 mid; + u16 pn; + const char *name; +}; + +static const struct chip_id_map chip_names[] = { + { .mid = 0x1f, .pn = 0x1e82, .name = "AT32AP700x" }, +}; +#define NR_CHIP_NAMES ARRAY_SIZE(chip_names) + static const char *cpu_names[] = { "Morgan", - "AP7000", + "AP7", }; #define NR_CPU_NAMES ARRAY_SIZE(cpu_names) @@ -206,12 +218,32 @@ static const char *mmu_types[] = { "MPU" }; +static const char *cpu_feature_flags[] = { + "rmw", "dsp", "simd", "ocd", "perfctr", "java", "fpu", +}; + +static const char *get_chip_name(struct avr32_cpuinfo *cpu) +{ + unsigned int i; + unsigned int mid = avr32_get_manufacturer_id(cpu); + unsigned int pn = avr32_get_product_number(cpu); + + for (i = 0; i < NR_CHIP_NAMES; i++) { + if (chip_names[i].mid == mid && chip_names[i].pn == pn) + return chip_names[i].name; + } + + return "(unknown)"; +} + void __init setup_processor(void) { unsigned long config0, config1; unsigned long features; unsigned cpu_id, cpu_rev, arch_id, arch_rev, mmu_type; + unsigned device_id; unsigned tmp; + unsigned i; config0 = sysreg_read(CONFIG0); config1 = sysreg_read(CONFIG1); @@ -221,11 +253,14 @@ void __init setup_processor(void) arch_rev = SYSREG_BFEXT(AR, config0); mmu_type = SYSREG_BFEXT(MMUT, config0); + device_id = ocd_read(DID); + boot_cpu_data.arch_type = arch_id; boot_cpu_data.cpu_type = cpu_id; boot_cpu_data.arch_revision = arch_rev; boot_cpu_data.cpu_revision = cpu_rev; boot_cpu_data.tlb_config = mmu_type; + boot_cpu_data.device_id = device_id; tmp = SYSREG_BFEXT(ILSZ, config1); if (tmp) { @@ -247,41 +282,34 @@ void __init setup_processor(void) return; } - printk ("CPU: %s [%02x] revision %d (%s revision %d)\n", + printk ("CPU: %s chip revision %c\n", get_chip_name(&boot_cpu_data), + avr32_get_chip_revision(&boot_cpu_data) + 'A'); + printk ("CPU: %s [%02x] core revision %d (%s arch revision %d)\n", cpu_names[cpu_id], cpu_id, cpu_rev, arch_names[arch_id], arch_rev); printk ("CPU: MMU configuration: %s\n", mmu_types[mmu_type]); printk ("CPU: features:"); features = 0; - if (config0 & SYSREG_BIT(CONFIG0_R)) { + if (config0 & SYSREG_BIT(CONFIG0_R)) features |= AVR32_FEATURE_RMW; - printk(" rmw"); - } - if (config0 & SYSREG_BIT(CONFIG0_D)) { + if (config0 & SYSREG_BIT(CONFIG0_D)) features |= AVR32_FEATURE_DSP; - printk(" dsp"); - } - if (config0 & SYSREG_BIT(CONFIG0_S)) { + if (config0 & SYSREG_BIT(CONFIG0_S)) features |= AVR32_FEATURE_SIMD; - printk(" simd"); - } - if (config0 & SYSREG_BIT(CONFIG0_O)) { + if (config0 & SYSREG_BIT(CONFIG0_O)) features |= AVR32_FEATURE_OCD; - printk(" ocd"); - } - if (config0 & SYSREG_BIT(CONFIG0_P)) { + if (config0 & SYSREG_BIT(CONFIG0_P)) features |= AVR32_FEATURE_PCTR; - printk(" perfctr"); - } - if (config0 & SYSREG_BIT(CONFIG0_J)) { + if (config0 & SYSREG_BIT(CONFIG0_J)) features |= AVR32_FEATURE_JAVA; - printk(" java"); - } - if (config0 & SYSREG_BIT(CONFIG0_F)) { + if (config0 & SYSREG_BIT(CONFIG0_F)) features |= AVR32_FEATURE_FPU; - printk(" fpu"); - } + + for (i = 0; i < ARRAY_SIZE(cpu_feature_flags); i++) + if (features & (1 << i)) + printk(" %s", cpu_feature_flags[i]); + printk("\n"); boot_cpu_data.features = features; } @@ -291,6 +319,8 @@ static int c_show(struct seq_file *m, void *v) { unsigned int icache_size, dcache_size; unsigned int cpu = smp_processor_id(); + unsigned int freq; + unsigned int i; icache_size = boot_cpu_data.icache.ways * boot_cpu_data.icache.sets * @@ -301,15 +331,21 @@ static int c_show(struct seq_file *m, void *v) seq_printf(m, "processor\t: %d\n", cpu); + seq_printf(m, "chip type\t: %s revision %c\n", + get_chip_name(&boot_cpu_data), + avr32_get_chip_revision(&boot_cpu_data) + 'A'); if (boot_cpu_data.arch_type < NR_ARCH_NAMES) - seq_printf(m, "cpu family\t: %s revision %d\n", + seq_printf(m, "cpu arch\t: %s revision %d\n", arch_names[boot_cpu_data.arch_type], boot_cpu_data.arch_revision); if (boot_cpu_data.cpu_type < NR_CPU_NAMES) - seq_printf(m, "cpu type\t: %s revision %d\n", + seq_printf(m, "cpu core\t: %s revision %d\n", cpu_names[boot_cpu_data.cpu_type], boot_cpu_data.cpu_revision); + freq = (clk_get_rate(boot_cpu_data.clk) + 500) / 1000; + seq_printf(m, "cpu MHz\t\t: %u.%03u\n", freq / 1000, freq % 1000); + seq_printf(m, "i-cache\t\t: %dK (%u ways x %u sets x %u)\n", icache_size >> 10, boot_cpu_data.icache.ways, @@ -320,7 +356,13 @@ static int c_show(struct seq_file *m, void *v) boot_cpu_data.dcache.ways, boot_cpu_data.dcache.sets, boot_cpu_data.dcache.linesz); - seq_printf(m, "bogomips\t: %lu.%02lu\n", + + seq_printf(m, "features\t:"); + for (i = 0; i < ARRAY_SIZE(cpu_feature_flags); i++) + if (boot_cpu_data.features & (1 << i)) + seq_printf(m, " %s", cpu_feature_flags[i]); + + seq_printf(m, "\nbogomips\t: %lu.%02lu\n", boot_cpu_data.loops_per_jiffy / (500000/HZ), (boot_cpu_data.loops_per_jiffy / (5000/HZ)) % 100); @@ -343,7 +385,7 @@ static void c_stop(struct seq_file *m, void *v) } -struct seq_operations cpuinfo_op = { +const struct seq_operations cpuinfo_op = { .start = c_start, .next = c_next, .stop = c_stop, diff --git a/arch/avr32/kernel/irq.c b/arch/avr32/kernel/irq.c index 61f2de2..a8e767d 100644 --- a/arch/avr32/kernel/irq.c +++ b/arch/avr32/kernel/irq.c @@ -25,6 +25,17 @@ void ack_bad_irq(unsigned int irq) printk("unexpected IRQ %u\n", irq); } +/* May be overridden by platform code */ +int __weak nmi_enable(void) +{ + return -ENOSYS; +} + +void __weak nmi_disable(void) +{ + +} + #ifdef CONFIG_PROC_FS int show_interrupts(struct seq_file *p, void *v) { diff --git a/arch/avr32/kernel/kprobes.c b/arch/avr32/kernel/kprobes.c index 799ba89..f820e9f 100644 --- a/arch/avr32/kernel/kprobes.c +++ b/arch/avr32/kernel/kprobes.c @@ -48,6 +48,7 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p) void __kprobes arch_arm_kprobe(struct kprobe *p) { pr_debug("arming kprobe at %p\n", p->addr); + ocd_enable(NULL); *p->addr = BREAKPOINT_INSTRUCTION; flush_icache_range((unsigned long)p->addr, (unsigned long)p->addr + sizeof(kprobe_opcode_t)); @@ -56,6 +57,7 @@ void __kprobes arch_arm_kprobe(struct kprobe *p) void __kprobes arch_disarm_kprobe(struct kprobe *p) { pr_debug("disarming kprobe at %p\n", p->addr); + ocd_disable(NULL); *p->addr = p->opcode; flush_icache_range((unsigned long)p->addr, (unsigned long)p->addr + sizeof(kprobe_opcode_t)); @@ -260,9 +262,6 @@ int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs) int __init arch_init_kprobes(void) { - printk("KPROBES: Enabling monitor mode (MM|DBE)...\n"); - ocd_write(DC, (1 << OCD_DC_MM_BIT) | (1 << OCD_DC_DBE_BIT)); - /* TODO: Register kretprobe trampoline */ return 0; } diff --git a/arch/avr32/kernel/nmi_debug.c b/arch/avr32/kernel/nmi_debug.c new file mode 100644 index 0000000..3414b85 --- /dev/null +++ b/arch/avr32/kernel/nmi_debug.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/delay.h> +#include <linux/kdebug.h> +#include <linux/notifier.h> +#include <linux/sched.h> + +#include <asm/irq.h> + +enum nmi_action { + NMI_SHOW_STATE = 1 << 0, + NMI_SHOW_REGS = 1 << 1, + NMI_DIE = 1 << 2, + NMI_DEBOUNCE = 1 << 3, +}; + +static unsigned long nmi_actions; + +static int nmi_debug_notify(struct notifier_block *self, + unsigned long val, void *data) +{ + struct die_args *args = data; + + if (likely(val != DIE_NMI)) + return NOTIFY_DONE; + + if (nmi_actions & NMI_SHOW_STATE) + show_state(); + if (nmi_actions & NMI_SHOW_REGS) + show_regs(args->regs); + if (nmi_actions & NMI_DEBOUNCE) + mdelay(10); + if (nmi_actions & NMI_DIE) + return NOTIFY_BAD; + + return NOTIFY_OK; +} + +static struct notifier_block nmi_debug_nb = { + .notifier_call = nmi_debug_notify, +}; + +static int __init nmi_debug_setup(char *str) +{ + char *p, *sep; + + register_die_notifier(&nmi_debug_nb); + if (nmi_enable()) { + printk(KERN_WARNING "Unable to enable NMI.\n"); + return 0; + } + + if (*str != '=') + return 0; + + for (p = str + 1; *p; p = sep + 1) { + sep = strchr(p, ','); + if (sep) + *sep = 0; + if (strcmp(p, "state") == 0) + nmi_actions |= NMI_SHOW_STATE; + else if (strcmp(p, "regs") == 0) + nmi_actions |= NMI_SHOW_REGS; + else if (strcmp(p, "debounce") == 0) + nmi_actions |= NMI_DEBOUNCE; + else if (strcmp(p, "die") == 0) + nmi_actions |= NMI_DIE; + else + printk(KERN_WARNING "NMI: Unrecognized action `%s'\n", + p); + if (!sep) + break; + } + + return 0; +} +__setup("nmi_debug", nmi_debug_setup); diff --git a/arch/avr32/kernel/ocd.c b/arch/avr32/kernel/ocd.c new file mode 100644 index 0000000..c4f0232 --- /dev/null +++ b/arch/avr32/kernel/ocd.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2007 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/spinlock.h> + +#include <asm/ocd.h> + +static long ocd_count; +static spinlock_t ocd_lock; + +/** + * ocd_enable - enable on-chip debugging + * @child: task to be debugged + * + * If @child is non-NULL, ocd_enable() first checks if debugging has + * already been enabled for @child, and if it has, does nothing. + * + * If @child is NULL (e.g. when debugging the kernel), or debugging + * has not already been enabled for it, ocd_enable() increments the + * reference count and enables the debugging hardware. + */ +void ocd_enable(struct task_struct *child) +{ + u32 dc; + + if (child) + pr_debug("ocd_enable: child=%s [%u]\n", + child->comm, child->pid); + else + pr_debug("ocd_enable (no child)\n"); + + if (!child || !test_and_set_tsk_thread_flag(child, TIF_DEBUG)) { + spin_lock(&ocd_lock); + ocd_count++; + dc = ocd_read(DC); + dc |= (1 << OCD_DC_MM_BIT) | (1 << OCD_DC_DBE_BIT); + ocd_write(DC, dc); + spin_unlock(&ocd_lock); + } +} + +/** + * ocd_disable - disable on-chip debugging + * @child: task that was being debugged, but isn't anymore + * + * If @child is non-NULL, ocd_disable() checks if debugging is enabled + * for @child, and if it isn't, does nothing. + * + * If @child is NULL (e.g. when debugging the kernel), or debugging is + * enabled, ocd_disable() decrements the reference count, and if it + * reaches zero, disables the debugging hardware. + */ +void ocd_disable(struct task_struct *child) +{ + u32 dc; + + if (!child) + pr_debug("ocd_disable (no child)\n"); + else if (test_tsk_thread_flag(child, TIF_DEBUG)) + pr_debug("ocd_disable: child=%s [%u]\n", + child->comm, child->pid); + + if (!child || test_and_clear_tsk_thread_flag(child, TIF_DEBUG)) { + spin_lock(&ocd_lock); + ocd_count--; + + WARN_ON(ocd_count < 0); + + if (ocd_count <= 0) { + dc = ocd_read(DC); + dc &= ~((1 << OCD_DC_MM_BIT) | (1 << OCD_DC_DBE_BIT)); + ocd_write(DC, dc); + } + spin_unlock(&ocd_lock); + } +} + +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#include <linux/module.h> + +static struct dentry *ocd_debugfs_root; +static struct dentry *ocd_debugfs_DC; +static struct dentry *ocd_debugfs_DS; +static struct dentry *ocd_debugfs_count; + +static u64 ocd_DC_get(void *data) +{ + return ocd_read(DC); +} +static void ocd_DC_set(void *data, u64 val) +{ + ocd_write(DC, val); +} +DEFINE_SIMPLE_ATTRIBUTE(fops_DC, ocd_DC_get, ocd_DC_set, "0x%08llx\n"); + +static u64 ocd_DS_get(void *data) +{ + return ocd_read(DS); +} +DEFINE_SIMPLE_ATTRIBUTE(fops_DS, ocd_DS_get, NULL, "0x%08llx\n"); + +static u64 ocd_count_get(void *data) +{ + return ocd_count; +} +DEFINE_SIMPLE_ATTRIBUTE(fops_count, ocd_count_get, NULL, "%lld\n"); + +static void ocd_debugfs_init(void) +{ + struct dentry *root; + + root = debugfs_create_dir("ocd", NULL); + if (IS_ERR(root) || !root) + goto err_root; + ocd_debugfs_root = root; + + ocd_debugfs_DC = debugfs_create_file("DC", S_IRUSR | S_IWUSR, + root, NULL, &fops_DC); + if (!ocd_debugfs_DC) + goto err_DC; + + ocd_debugfs_DS = debugfs_create_file("DS", S_IRUSR, root, + NULL, &fops_DS); + if (!ocd_debugfs_DS) + goto err_DS; + + ocd_debugfs_count = debugfs_create_file("count", S_IRUSR, root, + NULL, &fops_count); + if (!ocd_debugfs_count) + goto err_count; + + return; + +err_count: + debugfs_remove(ocd_debugfs_DS); +err_DS: + debugfs_remove(ocd_debugfs_DC); +err_DC: + debugfs_remove(ocd_debugfs_root); +err_root: + printk(KERN_WARNING "OCD: Failed to create debugfs entries\n"); +} +#else +static inline void ocd_debugfs_init(void) +{ + +} +#endif + +static int __init ocd_init(void) +{ + spin_lock_init(&ocd_lock); + ocd_debugfs_init(); + return 0; +} +arch_initcall(ocd_init); diff --git a/arch/avr32/kernel/process.c b/arch/avr32/kernel/process.c index 9d6dac8..eaaa69b 100644 --- a/arch/avr32/kernel/process.c +++ b/arch/avr32/kernel/process.c @@ -103,7 +103,7 @@ EXPORT_SYMBOL(kernel_thread); */ void exit_thread(void) { - /* nothing to do */ + ocd_disable(current); } void flush_thread(void) @@ -345,6 +345,9 @@ int copy_thread(int nr, unsigned long clone_flags, unsigned long usp, p->thread.cpu_context.ksp = (unsigned long)childregs; p->thread.cpu_context.pc = (unsigned long)ret_from_fork; + if ((clone_flags & CLONE_PTRACE) && test_thread_flag(TIF_DEBUG)) + ocd_enable(p); + return 0; } diff --git a/arch/avr32/kernel/ptrace.c b/arch/avr32/kernel/ptrace.c index 002369e..1fed38f 100644 --- a/arch/avr32/kernel/ptrace.c +++ b/arch/avr32/kernel/ptrace.c @@ -58,6 +58,7 @@ void ptrace_disable(struct task_struct *child) { clear_tsk_thread_flag(child, TIF_SINGLE_STEP); clear_tsk_thread_flag(child, TIF_BREAKPOINT); + ocd_disable(child); } /* @@ -144,10 +145,6 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) { int ret; - pr_debug("ptrace: Enabling monitor mode...\n"); - ocd_write(DC, ocd_read(DC) | (1 << OCD_DC_MM_BIT) - | (1 << OCD_DC_DBE_BIT)); - switch (request) { /* Read the word at location addr in the child process */ case PTRACE_PEEKTEXT: diff --git a/arch/avr32/kernel/signal.c b/arch/avr32/kernel/signal.c index 0ec14854..5616a00 100644 --- a/arch/avr32/kernel/signal.c +++ b/arch/avr32/kernel/signal.c @@ -270,19 +270,12 @@ int do_signal(struct pt_regs *regs, sigset_t *oldset, int syscall) if (!user_mode(regs)) return 0; - if (try_to_freeze()) { - signr = 0; - if (!signal_pending(current)) - goto no_signal; - } - if (test_thread_flag(TIF_RESTORE_SIGMASK)) oldset = ¤t->saved_sigmask; else if (!oldset) oldset = ¤t->blocked; signr = get_signal_to_deliver(&info, &ka, regs, NULL); -no_signal: if (syscall) { switch (regs->r12) { case -ERESTART_RESTARTBLOCK: diff --git a/arch/avr32/kernel/traps.c b/arch/avr32/kernel/traps.c index 870c075..cf6f686 100644 --- a/arch/avr32/kernel/traps.c +++ b/arch/avr32/kernel/traps.c @@ -9,6 +9,7 @@ #include <linux/bug.h> #include <linux/init.h> #include <linux/kallsyms.h> +#include <linux/kdebug.h> #include <linux/module.h> #include <linux/notifier.h> #include <linux/sched.h> @@ -107,9 +108,23 @@ void _exception(long signr, struct pt_regs *regs, int code, asmlinkage void do_nmi(unsigned long ecr, struct pt_regs *regs) { - printk(KERN_ALERT "Got Non-Maskable Interrupt, dumping regs\n"); - show_regs_log_lvl(regs, KERN_ALERT); - show_stack_log_lvl(current, regs->sp, regs, KERN_ALERT); + int ret; + + nmi_enter(); + + ret = notify_die(DIE_NMI, "NMI", regs, 0, ecr, SIGINT); + switch (ret) { + case NOTIFY_OK: + case NOTIFY_STOP: + return; + case NOTIFY_BAD: + die("Fatal Non-Maskable Interrupt", regs, SIGINT); + default: + break; + } + + printk(KERN_ALERT "Got NMI, but nobody cared. Disabling...\n"); + nmi_disable(); } asmlinkage void do_critical_exception(unsigned long ecr, struct pt_regs *regs) |