From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- arch/xtensa/platforms/Makefile | 4 + arch/xtensa/platforms/iss/Makefile | 11 + arch/xtensa/platforms/iss/console.c | 198 +++++++ .../xtensa/platforms/iss/include/platform/serial.h | 15 + .../platforms/iss/include/platform/simcall-gdbio.h | 34 ++ .../platforms/iss/include/platform/simcall-iss.h | 73 +++ .../platforms/iss/include/platform/simcall.h | 110 ++++ arch/xtensa/platforms/iss/network.c | 642 +++++++++++++++++++++ arch/xtensa/platforms/iss/setup.c | 88 +++ arch/xtensa/platforms/iss/simdisk.c | 404 +++++++++++++ arch/xtensa/platforms/xt2000/Makefile | 6 + .../platforms/xt2000/include/platform/hardware.h | 43 ++ .../platforms/xt2000/include/platform/serial.h | 28 + arch/xtensa/platforms/xt2000/setup.c | 147 +++++ arch/xtensa/platforms/xtfpga/Makefile | 11 + .../platforms/xtfpga/include/platform/hardware.h | 60 ++ .../xtensa/platforms/xtfpga/include/platform/lcd.h | 35 ++ .../platforms/xtfpga/include/platform/serial.h | 18 + arch/xtensa/platforms/xtfpga/lcd.c | 89 +++ arch/xtensa/platforms/xtfpga/setup.c | 301 ++++++++++ 20 files changed, 2317 insertions(+) create mode 100644 arch/xtensa/platforms/Makefile create mode 100644 arch/xtensa/platforms/iss/Makefile create mode 100644 arch/xtensa/platforms/iss/console.c create mode 100644 arch/xtensa/platforms/iss/include/platform/serial.h create mode 100644 arch/xtensa/platforms/iss/include/platform/simcall-gdbio.h create mode 100644 arch/xtensa/platforms/iss/include/platform/simcall-iss.h create mode 100644 arch/xtensa/platforms/iss/include/platform/simcall.h create mode 100644 arch/xtensa/platforms/iss/network.c create mode 100644 arch/xtensa/platforms/iss/setup.c create mode 100644 arch/xtensa/platforms/iss/simdisk.c create mode 100644 arch/xtensa/platforms/xt2000/Makefile create mode 100644 arch/xtensa/platforms/xt2000/include/platform/hardware.h create mode 100644 arch/xtensa/platforms/xt2000/include/platform/serial.h create mode 100644 arch/xtensa/platforms/xt2000/setup.c create mode 100644 arch/xtensa/platforms/xtfpga/Makefile create mode 100644 arch/xtensa/platforms/xtfpga/include/platform/hardware.h create mode 100644 arch/xtensa/platforms/xtfpga/include/platform/lcd.h create mode 100644 arch/xtensa/platforms/xtfpga/include/platform/serial.h create mode 100644 arch/xtensa/platforms/xtfpga/lcd.c create mode 100644 arch/xtensa/platforms/xtfpga/setup.c (limited to 'arch/xtensa/platforms') diff --git a/arch/xtensa/platforms/Makefile b/arch/xtensa/platforms/Makefile new file mode 100644 index 000000000..e2e7e0726 --- /dev/null +++ b/arch/xtensa/platforms/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_XTENSA_PLATFORM_XT2000) += xt2000/ +obj-$(CONFIG_XTENSA_PLATFORM_ISS) += iss/ +obj-$(CONFIG_XTENSA_PLATFORM_XTFPGA) += xtfpga/ diff --git a/arch/xtensa/platforms/iss/Makefile b/arch/xtensa/platforms/iss/Makefile new file mode 100644 index 000000000..f3dd5e72a --- /dev/null +++ b/arch/xtensa/platforms/iss/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# $Id: Makefile,v 1.1.1.1 2002/08/28 16:10:14 aroll Exp $ +# +# Makefile for the Xtensa Instruction Set Simulator (ISS) +# "prom monitor" library routines under Linux. +# + +obj-y = setup.o +obj-$(CONFIG_TTY) += console.o +obj-$(CONFIG_NET) += network.o +obj-$(CONFIG_BLK_DEV_SIMDISK) += simdisk.o diff --git a/arch/xtensa/platforms/iss/console.c b/arch/xtensa/platforms/iss/console.c new file mode 100644 index 000000000..8e54625cb --- /dev/null +++ b/arch/xtensa/platforms/iss/console.c @@ -0,0 +1,198 @@ +/* + * arch/xtensa/platforms/iss/console.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001-2005 Tensilica Inc. + * Authors Christian Zankel, Joe Taylor + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#define SERIAL_MAX_NUM_LINES 1 +#define SERIAL_TIMER_VALUE (HZ / 10) + +static void rs_poll(struct timer_list *); + +static struct tty_driver *serial_driver; +static struct tty_port serial_port; +static DEFINE_TIMER(serial_timer, rs_poll); + +static int rs_open(struct tty_struct *tty, struct file * filp) +{ + if (tty->count == 1) + mod_timer(&serial_timer, jiffies + SERIAL_TIMER_VALUE); + + return 0; +} + +static void rs_close(struct tty_struct *tty, struct file * filp) +{ + if (tty->count == 1) + timer_delete_sync(&serial_timer); +} + + +static ssize_t rs_write(struct tty_struct * tty, const u8 *buf, size_t count) +{ + /* see drivers/char/serialX.c to reference original version */ + + simc_write(1, buf, count); + return count; +} + +static void rs_poll(struct timer_list *unused) +{ + struct tty_port *port = &serial_port; + int i = 0; + int rd = 1; + u8 c; + + while (simc_poll(0)) { + rd = simc_read(0, &c, 1); + if (rd <= 0) + break; + tty_insert_flip_char(port, c, TTY_NORMAL); + i++; + } + + if (i) + tty_flip_buffer_push(port); + if (rd) + mod_timer(&serial_timer, jiffies + SERIAL_TIMER_VALUE); +} + +static unsigned int rs_write_room(struct tty_struct *tty) +{ + /* Let's say iss can always accept 2K characters.. */ + return 2 * 1024; +} + +static int rs_proc_show(struct seq_file *m, void *v) +{ + seq_printf(m, "serinfo:1.0 driver:0.1\n"); + return 0; +} + +static const struct tty_operations serial_ops = { + .open = rs_open, + .close = rs_close, + .write = rs_write, + .write_room = rs_write_room, + .proc_show = rs_proc_show, +}; + +static int __init rs_init(void) +{ + struct tty_driver *driver; + int ret; + + driver = tty_alloc_driver(SERIAL_MAX_NUM_LINES, TTY_DRIVER_REAL_RAW); + if (IS_ERR(driver)) + return PTR_ERR(driver); + + tty_port_init(&serial_port); + + /* Initialize the tty_driver structure */ + + driver->driver_name = "iss_serial"; + driver->name = "ttyS"; + driver->major = TTY_MAJOR; + driver->minor_start = 64; + driver->type = TTY_DRIVER_TYPE_SERIAL; + driver->subtype = SERIAL_TYPE_NORMAL; + driver->init_termios = tty_std_termios; + driver->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + + tty_set_operations(driver, &serial_ops); + tty_port_link_device(&serial_port, driver, 0); + + ret = tty_register_driver(driver); + if (ret) { + pr_err("Couldn't register serial driver\n"); + tty_driver_kref_put(driver); + tty_port_destroy(&serial_port); + + return ret; + } + + serial_driver = driver; + + return 0; +} + + +static __exit void rs_exit(void) +{ + tty_unregister_driver(serial_driver); + tty_driver_kref_put(serial_driver); + tty_port_destroy(&serial_port); +} + + +/* We use `late_initcall' instead of just `__initcall' as a workaround for + * the fact that (1) simcons_tty_init can't be called before tty_init, + * (2) tty_init is called via `module_init', (3) if statically linked, + * module_init == device_init, and (4) there's no ordering of init lists. + * We can do this easily because simcons is always statically linked, but + * other tty drivers that depend on tty_init and which must use + * `module_init' to declare their init routines are likely to be broken. + */ + +late_initcall(rs_init); + + +#ifdef CONFIG_SERIAL_CONSOLE + +static void iss_console_write(struct console *co, const char *s, unsigned count) +{ + count = s ? strnlen(s, count) : 0; + if (count) + simc_write(1, s, count); +} + +static struct tty_driver* iss_console_device(struct console *c, int *index) +{ + *index = c->index; + return serial_driver; +} + + +static struct console sercons = { + .name = "ttyS", + .write = iss_console_write, + .device = iss_console_device, + .flags = CON_PRINTBUFFER, + .index = -1 +}; + +static int __init iss_console_init(void) +{ + register_console(&sercons); + return 0; +} + +console_initcall(iss_console_init); + +#endif /* CONFIG_SERIAL_CONSOLE */ + diff --git a/arch/xtensa/platforms/iss/include/platform/serial.h b/arch/xtensa/platforms/iss/include/platform/serial.h new file mode 100644 index 000000000..16aec542d --- /dev/null +++ b/arch/xtensa/platforms/iss/include/platform/serial.h @@ -0,0 +1,15 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2012 Tensilica Inc. + */ + +#ifndef __ASM_XTENSA_ISS_SERIAL_H +#define __ASM_XTENSA_ISS_SERIAL_H + +/* Have no meaning on ISS, but needed for 8250_early.c */ +#define BASE_BAUD 0 + +#endif /* __ASM_XTENSA_ISS_SERIAL_H */ diff --git a/arch/xtensa/platforms/iss/include/platform/simcall-gdbio.h b/arch/xtensa/platforms/iss/include/platform/simcall-gdbio.h new file mode 100644 index 000000000..e642860e2 --- /dev/null +++ b/arch/xtensa/platforms/iss/include/platform/simcall-gdbio.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2021 Cadence Design Systems Inc. */ + +#ifndef _XTENSA_PLATFORM_ISS_SIMCALL_GDBIO_H +#define _XTENSA_PLATFORM_ISS_SIMCALL_GDBIO_H + +/* + * System call like services offered by the GDBIO host. + */ + +#define SYS_open -2 +#define SYS_close -3 +#define SYS_read -4 +#define SYS_write -5 +#define SYS_lseek -6 + +static int errno; + +static inline int __simc(int a, int b, int c, int d) +{ + register int a1 asm("a2") = a; + register int b1 asm("a6") = b; + register int c1 asm("a3") = c; + register int d1 asm("a4") = d; + __asm__ __volatile__ ( + "break 1, 14\n" + : "+r"(a1), "+r"(c1) + : "r"(b1), "r"(d1) + : "memory"); + errno = c1; + return a1; +} + +#endif /* _XTENSA_PLATFORM_ISS_SIMCALL_GDBIO_H */ diff --git a/arch/xtensa/platforms/iss/include/platform/simcall-iss.h b/arch/xtensa/platforms/iss/include/platform/simcall-iss.h new file mode 100644 index 000000000..5a1e7a1f1 --- /dev/null +++ b/arch/xtensa/platforms/iss/include/platform/simcall-iss.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2021 Cadence Design Systems Inc. */ + +#ifndef _XTENSA_PLATFORM_ISS_SIMCALL_ISS_H +#define _XTENSA_PLATFORM_ISS_SIMCALL_ISS_H + +/* + * System call like services offered by the simulator host. + */ + +#define SYS_nop 0 /* unused */ +#define SYS_exit 1 /*x*/ +#define SYS_fork 2 +#define SYS_read 3 /*x*/ +#define SYS_write 4 /*x*/ +#define SYS_open 5 /*x*/ +#define SYS_close 6 /*x*/ +#define SYS_rename 7 /*x 38 - waitpid */ +#define SYS_creat 8 /*x*/ +#define SYS_link 9 /*x (not implemented on WIN32) */ +#define SYS_unlink 10 /*x*/ +#define SYS_execv 11 /* n/a - execve */ +#define SYS_execve 12 /* 11 - chdir */ +#define SYS_pipe 13 /* 42 - time */ +#define SYS_stat 14 /* 106 - mknod */ +#define SYS_chmod 15 +#define SYS_chown 16 /* 202 - lchown */ +#define SYS_utime 17 /* 30 - break */ +#define SYS_wait 18 /* n/a - oldstat */ +#define SYS_lseek 19 /*x*/ +#define SYS_getpid 20 +#define SYS_isatty 21 /* n/a - mount */ +#define SYS_fstat 22 /* 108 - oldumount */ +#define SYS_time 23 /* 13 - setuid */ +#define SYS_gettimeofday 24 /*x 78 - getuid (not implemented on WIN32) */ +#define SYS_times 25 /*X 43 - stime (Xtensa-specific implementation) */ +#define SYS_socket 26 +#define SYS_sendto 27 +#define SYS_recvfrom 28 +#define SYS_select_one 29 /* not compatible select, one file descriptor at the time */ +#define SYS_bind 30 +#define SYS_ioctl 31 + +#define SYS_iss_argc 1000 /* returns value of argc */ +#define SYS_iss_argv_size 1001 /* bytes needed for argv & arg strings */ +#define SYS_iss_set_argv 1002 /* saves argv & arg strings at given addr */ + +/* + * SYS_select_one specifiers + */ + +#define XTISS_SELECT_ONE_READ 1 +#define XTISS_SELECT_ONE_WRITE 2 +#define XTISS_SELECT_ONE_EXCEPT 3 + +static int errno; + +static inline int __simc(int a, int b, int c, int d) +{ + register int a1 asm("a2") = a; + register int b1 asm("a3") = b; + register int c1 asm("a4") = c; + register int d1 asm("a5") = d; + __asm__ __volatile__ ( + "simcall\n" + : "+r"(a1), "+r"(b1) + : "r"(c1), "r"(d1) + : "memory"); + errno = b1; + return a1; +} + +#endif /* _XTENSA_PLATFORM_ISS_SIMCALL_ISS_H */ diff --git a/arch/xtensa/platforms/iss/include/platform/simcall.h b/arch/xtensa/platforms/iss/include/platform/simcall.h new file mode 100644 index 000000000..e1ec50ce3 --- /dev/null +++ b/arch/xtensa/platforms/iss/include/platform/simcall.h @@ -0,0 +1,110 @@ +/* + * include/asm-xtensa/platform-iss/simcall.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001 Tensilica Inc. + * Copyright (C) 2017 - 2021 Cadence Design Systems Inc. + */ + +#ifndef _XTENSA_PLATFORM_ISS_SIMCALL_H +#define _XTENSA_PLATFORM_ISS_SIMCALL_H + +#include + +#ifdef CONFIG_XTENSA_SIMCALL_ISS +#include +#endif +#ifdef CONFIG_XTENSA_SIMCALL_GDBIO +#include +#endif + +static inline int simc_exit(int exit_code) +{ +#ifdef SYS_exit + return __simc(SYS_exit, exit_code, 0, 0); +#else + WARN_ONCE(1, "%s: not implemented\n", __func__); + return -1; +#endif +} + +static inline int simc_open(const char *file, int flags, int mode) +{ + return __simc(SYS_open, (int) file, flags, mode); +} + +static inline int simc_close(int fd) +{ + return __simc(SYS_close, fd, 0, 0); +} + +static inline int simc_ioctl(int fd, int request, void *arg) +{ +#ifdef SYS_ioctl + return __simc(SYS_ioctl, fd, request, (int) arg); +#else + WARN_ONCE(1, "%s: not implemented\n", __func__); + return -1; +#endif +} + +static inline int simc_read(int fd, void *buf, size_t count) +{ + return __simc(SYS_read, fd, (int) buf, count); +} + +static inline int simc_write(int fd, const void *buf, size_t count) +{ + return __simc(SYS_write, fd, (int) buf, count); +} + +static inline int simc_poll(int fd) +{ +#ifdef SYS_select_one + long timeval[2] = { 0, 0 }; + + return __simc(SYS_select_one, fd, XTISS_SELECT_ONE_READ, (int)&timeval); +#else + WARN_ONCE(1, "%s: not implemented\n", __func__); + return -1; +#endif +} + +static inline int simc_lseek(int fd, uint32_t off, int whence) +{ + return __simc(SYS_lseek, fd, off, whence); +} + +static inline int simc_argc(void) +{ +#ifdef SYS_iss_argc + return __simc(SYS_iss_argc, 0, 0, 0); +#else + WARN_ONCE(1, "%s: not implemented\n", __func__); + return 0; +#endif +} + +static inline int simc_argv_size(void) +{ +#ifdef SYS_iss_argv_size + return __simc(SYS_iss_argv_size, 0, 0, 0); +#else + WARN_ONCE(1, "%s: not implemented\n", __func__); + return 0; +#endif +} + +static inline void simc_argv(void *buf) +{ +#ifdef SYS_iss_set_argv + __simc(SYS_iss_set_argv, (int)buf, 0, 0); +#else + WARN_ONCE(1, "%s: not implemented\n", __func__); +#endif +} + +#endif /* _XTENSA_PLATFORM_ISS_SIMCALL_H */ diff --git a/arch/xtensa/platforms/iss/network.c b/arch/xtensa/platforms/iss/network.c new file mode 100644 index 000000000..832579143 --- /dev/null +++ b/arch/xtensa/platforms/iss/network.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * arch/xtensa/platforms/iss/network.c + * + * Platform specific initialization. + * + * Authors: Chris Zankel + * Based on work form the UML team. + * + * Copyright 2005 Tensilica Inc. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "iss-netdev" +#define ETH_MAX_PACKET 1500 +#define ETH_HEADER_OTHER 14 +#define ISS_NET_TIMER_VALUE (HZ / 10) + +/* ------------------------------------------------------------------------- */ + +/* We currently only support the TUNTAP transport protocol. */ + +#define TRANSPORT_TUNTAP_NAME "tuntap" +#define TRANSPORT_TUNTAP_MTU ETH_MAX_PACKET + +struct tuntap_info { + char dev_name[IFNAMSIZ]; + int fd; +}; + +/* ------------------------------------------------------------------------- */ + + +struct iss_net_private; + +struct iss_net_ops { + int (*open)(struct iss_net_private *lp); + void (*close)(struct iss_net_private *lp); + int (*read)(struct iss_net_private *lp, struct sk_buff **skb); + int (*write)(struct iss_net_private *lp, struct sk_buff **skb); + unsigned short (*protocol)(struct sk_buff *skb); + int (*poll)(struct iss_net_private *lp); +}; + +/* This structure contains out private information for the driver. */ + +struct iss_net_private { + spinlock_t lock; + struct net_device *dev; + struct platform_device pdev; + struct timer_list tl; + struct rtnl_link_stats64 stats; + + struct timer_list timer; + unsigned int timer_val; + + int index; + int mtu; + + struct { + union { + struct tuntap_info tuntap; + } info; + + const struct iss_net_ops *net_ops; + } tp; + +}; + +/* ================================ HELPERS ================================ */ + + +static char *split_if_spec(char *str, ...) +{ + char **arg, *end; + va_list ap; + + va_start(ap, str); + while ((arg = va_arg(ap, char**)) != NULL) { + if (*str == '\0') { + va_end(ap); + return NULL; + } + end = strchr(str, ','); + if (end != str) + *arg = str; + if (end == NULL) { + va_end(ap); + return NULL; + } + *end++ = '\0'; + str = end; + } + va_end(ap); + return str; +} + +/* Set Ethernet address of the specified device. */ + +static void setup_etheraddr(struct net_device *dev, char *str) +{ + u8 addr[ETH_ALEN]; + + if (str == NULL) + goto random; + + if (!mac_pton(str, addr)) { + pr_err("%s: failed to parse '%s' as an ethernet address\n", + dev->name, str); + goto random; + } + if (is_multicast_ether_addr(addr)) { + pr_err("%s: attempt to assign a multicast ethernet address\n", + dev->name); + goto random; + } + if (!is_valid_ether_addr(addr)) { + pr_err("%s: attempt to assign an invalid ethernet address\n", + dev->name); + goto random; + } + if (!is_local_ether_addr(addr)) + pr_warn("%s: assigning a globally valid ethernet address\n", + dev->name); + eth_hw_addr_set(dev, addr); + return; + +random: + pr_info("%s: choosing a random ethernet address\n", + dev->name); + eth_hw_addr_random(dev); +} + +/* ======================= TUNTAP TRANSPORT INTERFACE ====================== */ + +static int tuntap_open(struct iss_net_private *lp) +{ + struct ifreq ifr; + char *dev_name = lp->tp.info.tuntap.dev_name; + int err = -EINVAL; + int fd; + + fd = simc_open("/dev/net/tun", 02, 0); /* O_RDWR */ + if (fd < 0) { + pr_err("%s: failed to open /dev/net/tun, returned %d (errno = %d)\n", + lp->dev->name, fd, errno); + return fd; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strscpy(ifr.ifr_name, dev_name, sizeof(ifr.ifr_name)); + + err = simc_ioctl(fd, TUNSETIFF, &ifr); + if (err < 0) { + pr_err("%s: failed to set interface %s, returned %d (errno = %d)\n", + lp->dev->name, dev_name, err, errno); + simc_close(fd); + return err; + } + + lp->tp.info.tuntap.fd = fd; + return err; +} + +static void tuntap_close(struct iss_net_private *lp) +{ + simc_close(lp->tp.info.tuntap.fd); + lp->tp.info.tuntap.fd = -1; +} + +static int tuntap_read(struct iss_net_private *lp, struct sk_buff **skb) +{ + return simc_read(lp->tp.info.tuntap.fd, + (*skb)->data, (*skb)->dev->mtu + ETH_HEADER_OTHER); +} + +static int tuntap_write(struct iss_net_private *lp, struct sk_buff **skb) +{ + return simc_write(lp->tp.info.tuntap.fd, (*skb)->data, (*skb)->len); +} + +static unsigned short tuntap_protocol(struct sk_buff *skb) +{ + return eth_type_trans(skb, skb->dev); +} + +static int tuntap_poll(struct iss_net_private *lp) +{ + return simc_poll(lp->tp.info.tuntap.fd); +} + +static const struct iss_net_ops tuntap_ops = { + .open = tuntap_open, + .close = tuntap_close, + .read = tuntap_read, + .write = tuntap_write, + .protocol = tuntap_protocol, + .poll = tuntap_poll, +}; + +/* + * ethX=tuntap,[mac address],device name + */ + +static int tuntap_probe(struct iss_net_private *lp, int index, char *init) +{ + struct net_device *dev = lp->dev; + char *dev_name = NULL, *mac_str = NULL, *rem = NULL; + + /* Transport should be 'tuntap': ethX=tuntap,mac,dev_name */ + + if (strncmp(init, TRANSPORT_TUNTAP_NAME, + sizeof(TRANSPORT_TUNTAP_NAME) - 1)) + return 0; + + init += sizeof(TRANSPORT_TUNTAP_NAME) - 1; + if (*init == ',') { + rem = split_if_spec(init + 1, &mac_str, &dev_name, NULL); + if (rem != NULL) { + pr_err("%s: extra garbage on specification : '%s'\n", + dev->name, rem); + return 0; + } + } else if (*init != '\0') { + pr_err("%s: invalid argument: %s. Skipping device!\n", + dev->name, init); + return 0; + } + + if (!dev_name) { + pr_err("%s: missing tuntap device name\n", dev->name); + return 0; + } + + strscpy(lp->tp.info.tuntap.dev_name, dev_name, + sizeof(lp->tp.info.tuntap.dev_name)); + + setup_etheraddr(dev, mac_str); + + lp->mtu = TRANSPORT_TUNTAP_MTU; + + lp->tp.info.tuntap.fd = -1; + lp->tp.net_ops = &tuntap_ops; + + return 1; +} + +/* ================================ ISS NET ================================ */ + +static int iss_net_rx(struct net_device *dev) +{ + struct iss_net_private *lp = netdev_priv(dev); + int pkt_len; + struct sk_buff *skb; + + /* Check if there is any new data. */ + + if (lp->tp.net_ops->poll(lp) == 0) + return 0; + + /* Try to allocate memory, if it fails, try again next round. */ + + skb = dev_alloc_skb(dev->mtu + 2 + ETH_HEADER_OTHER); + if (skb == NULL) { + spin_lock_bh(&lp->lock); + lp->stats.rx_dropped++; + spin_unlock_bh(&lp->lock); + return 0; + } + + skb_reserve(skb, 2); + + /* Setup skb */ + + skb->dev = dev; + skb_reset_mac_header(skb); + pkt_len = lp->tp.net_ops->read(lp, &skb); + skb_put(skb, pkt_len); + + if (pkt_len > 0) { + skb_trim(skb, pkt_len); + skb->protocol = lp->tp.net_ops->protocol(skb); + + spin_lock_bh(&lp->lock); + lp->stats.rx_bytes += skb->len; + lp->stats.rx_packets++; + spin_unlock_bh(&lp->lock); + netif_rx(skb); + return pkt_len; + } + kfree_skb(skb); + return pkt_len; +} + +static int iss_net_poll(struct iss_net_private *lp) +{ + int err, ret = 0; + + if (!netif_running(lp->dev)) + return 0; + + while ((err = iss_net_rx(lp->dev)) > 0) + ret++; + + if (err < 0) { + pr_err("Device '%s' read returned %d, shutting it down\n", + lp->dev->name, err); + dev_close(lp->dev); + } else { + /* FIXME reactivate_fd(lp->fd, ISS_ETH_IRQ); */ + } + + return ret; +} + + +static void iss_net_timer(struct timer_list *t) +{ + struct iss_net_private *lp = timer_container_of(lp, t, timer); + + iss_net_poll(lp); + mod_timer(&lp->timer, jiffies + lp->timer_val); +} + + +static int iss_net_open(struct net_device *dev) +{ + struct iss_net_private *lp = netdev_priv(dev); + int err; + + err = lp->tp.net_ops->open(lp); + if (err < 0) + return err; + + netif_start_queue(dev); + + /* clear buffer - it can happen that the host side of the interface + * is full when we get here. In this case, new data is never queued, + * SIGIOs never arrive, and the net never works. + */ + while ((err = iss_net_rx(dev)) > 0) + ; + + timer_setup(&lp->timer, iss_net_timer, 0); + lp->timer_val = ISS_NET_TIMER_VALUE; + mod_timer(&lp->timer, jiffies + lp->timer_val); + + return err; +} + +static int iss_net_close(struct net_device *dev) +{ + struct iss_net_private *lp = netdev_priv(dev); + + netif_stop_queue(dev); + timer_delete_sync(&lp->timer); + lp->tp.net_ops->close(lp); + + return 0; +} + +static int iss_net_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct iss_net_private *lp = netdev_priv(dev); + int len; + + netif_stop_queue(dev); + + len = lp->tp.net_ops->write(lp, &skb); + + if (len == skb->len) { + spin_lock_bh(&lp->lock); + lp->stats.tx_packets++; + lp->stats.tx_bytes += skb->len; + spin_unlock_bh(&lp->lock); + netif_trans_update(dev); + netif_start_queue(dev); + + /* this is normally done in the interrupt when tx finishes */ + netif_wake_queue(dev); + + } else if (len == 0) { + netif_start_queue(dev); + spin_lock_bh(&lp->lock); + lp->stats.tx_dropped++; + spin_unlock_bh(&lp->lock); + + } else { + netif_start_queue(dev); + pr_err("%s: %s failed(%d)\n", dev->name, __func__, len); + } + + + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + + +static void iss_net_get_stats64(struct net_device *dev, + struct rtnl_link_stats64 *stats) +{ + struct iss_net_private *lp = netdev_priv(dev); + + spin_lock_bh(&lp->lock); + *stats = lp->stats; + spin_unlock_bh(&lp->lock); +} + +static void iss_net_set_multicast_list(struct net_device *dev) +{ +} + +static void iss_net_tx_timeout(struct net_device *dev, unsigned int txqueue) +{ +} + +static int iss_net_change_mtu(struct net_device *dev, int new_mtu) +{ + return -EINVAL; +} + +static void iss_net_user_timer_expire(struct timer_list *unused) +{ +} + + +static struct platform_driver iss_net_driver = { + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int driver_registered; + +static const struct net_device_ops iss_netdev_ops = { + .ndo_open = iss_net_open, + .ndo_stop = iss_net_close, + .ndo_get_stats64 = iss_net_get_stats64, + .ndo_start_xmit = iss_net_start_xmit, + .ndo_validate_addr = eth_validate_addr, + .ndo_change_mtu = iss_net_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_tx_timeout = iss_net_tx_timeout, + .ndo_set_rx_mode = iss_net_set_multicast_list, +}; + +static void iss_net_pdev_release(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iss_net_private *lp = + container_of(pdev, struct iss_net_private, pdev); + + free_netdev(lp->dev); +} + +static void iss_net_configure(int index, char *init) +{ + struct net_device *dev; + struct iss_net_private *lp; + + dev = alloc_etherdev(sizeof(*lp)); + if (dev == NULL) { + pr_err("eth_configure: failed to allocate device\n"); + return; + } + + /* Initialize private element. */ + + lp = netdev_priv(dev); + *lp = (struct iss_net_private) { + .dev = dev, + .index = index, + }; + + spin_lock_init(&lp->lock); + /* + * If this name ends up conflicting with an existing registered + * netdevice, that is OK, register_netdev{,ice}() will notice this + * and fail. + */ + snprintf(dev->name, sizeof(dev->name), "eth%d", index); + + /* + * Try all transport protocols. + * Note: more protocols can be added by adding '&& !X_init(lp, eth)'. + */ + + if (!tuntap_probe(lp, index, init)) { + pr_err("%s: invalid arguments. Skipping device!\n", + dev->name); + goto err_free_netdev; + } + + pr_info("Netdevice %d (%pM)\n", index, dev->dev_addr); + + /* sysfs register */ + + if (!driver_registered) { + if (platform_driver_register(&iss_net_driver)) + goto err_free_netdev; + driver_registered = 1; + } + + lp->pdev.id = index; + lp->pdev.name = DRIVER_NAME; + lp->pdev.dev.release = iss_net_pdev_release; + if (platform_device_register(&lp->pdev)) + goto err_free_netdev; + SET_NETDEV_DEV(dev, &lp->pdev.dev); + + dev->netdev_ops = &iss_netdev_ops; + dev->mtu = lp->mtu; + dev->watchdog_timeo = (HZ >> 1); + dev->irq = -1; + + rtnl_lock(); + if (register_netdevice(dev)) { + rtnl_unlock(); + pr_err("%s: error registering net device!\n", dev->name); + platform_device_unregister(&lp->pdev); + /* dev is freed by the iss_net_pdev_release callback */ + return; + } + rtnl_unlock(); + + timer_setup(&lp->tl, iss_net_user_timer_expire, 0); + + return; + +err_free_netdev: + free_netdev(dev); +} + +/* ------------------------------------------------------------------------- */ + +/* Filled in during early boot */ + +struct list_head eth_cmd_line = LIST_HEAD_INIT(eth_cmd_line); + +struct iss_net_init { + struct list_head list; + char *init; /* init string */ + int index; +}; + +/* + * Parse the command line and look for 'ethX=...' fields, and register all + * those fields. They will be later initialized in iss_net_init. + */ + +static int __init iss_net_setup(char *str) +{ + struct iss_net_init *device = NULL; + struct iss_net_init *new; + struct list_head *ele; + char *end; + int rc; + unsigned n; + + end = strchr(str, '='); + if (!end) { + pr_err("Expected '=' after device number\n"); + return 1; + } + *end = 0; + rc = kstrtouint(str, 0, &n); + *end = '='; + if (rc < 0) { + pr_err("Failed to parse '%s'\n", str); + return 1; + } + str = end; + + list_for_each(ele, ð_cmd_line) { + device = list_entry(ele, struct iss_net_init, list); + if (device->index == n) + break; + } + + if (device && device->index == n) { + pr_err("Device %u already configured\n", n); + return 1; + } + + new = memblock_alloc(sizeof(*new), SMP_CACHE_BYTES); + if (new == NULL) { + pr_err("Alloc_bootmem failed\n"); + return 1; + } + + INIT_LIST_HEAD(&new->list); + new->index = n; + new->init = str + 1; + + list_add_tail(&new->list, ð_cmd_line); + return 1; +} + +__setup("eth", iss_net_setup); + +/* + * Initialize all ISS Ethernet devices previously registered in iss_net_setup. + */ + +static int iss_net_init(void) +{ + struct list_head *ele, *next; + + /* Walk through all Ethernet devices specified in the command line. */ + + list_for_each_safe(ele, next, ð_cmd_line) { + struct iss_net_init *eth; + eth = list_entry(ele, struct iss_net_init, list); + iss_net_configure(eth->index, eth->init); + } + + return 1; +} +device_initcall(iss_net_init); diff --git a/arch/xtensa/platforms/iss/setup.c b/arch/xtensa/platforms/iss/setup.c new file mode 100644 index 000000000..21283acab --- /dev/null +++ b/arch/xtensa/platforms/iss/setup.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * arch/xtensa/platform-iss/setup.c + * + * Platform specific initialization. + * + * Authors: Chris Zankel + * Joe Taylor + * + * Copyright 2001 - 2005 Tensilica Inc. + * Copyright 2017 Cadence Design Systems Inc. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +static int iss_power_off(struct sys_off_data *unused) +{ + pr_info(" ** Called platform_power_off() **\n"); + simc_exit(0); + return NOTIFY_DONE; +} + +static int iss_restart(struct sys_off_data *unused) +{ + /* Flush and reset the mmu, simulate a processor reset, and + * jump to the reset vector. */ + cpu_reset(); + + return NOTIFY_DONE; +} + +static int +iss_panic_event(struct notifier_block *this, unsigned long event, void *ptr) +{ + simc_exit(1); + return NOTIFY_DONE; +} + +static struct notifier_block iss_panic_block = { + .notifier_call = iss_panic_event, +}; + +void __init platform_setup(char **p_cmdline) +{ + static void *argv[COMMAND_LINE_SIZE / sizeof(void *)] __initdata; + static char cmdline[COMMAND_LINE_SIZE] __initdata; + int argc = simc_argc(); + int argv_size = simc_argv_size(); + + if (argc > 1) { + if (argv_size > sizeof(argv)) { + pr_err("%s: command line too long: argv_size = %d\n", + __func__, argv_size); + } else { + int i; + + cmdline[0] = 0; + simc_argv((void *)argv); + + for (i = 1; i < argc; ++i) { + if (i > 1) + strcat(cmdline, " "); + strcat(cmdline, argv[i]); + } + *p_cmdline = cmdline; + } + } + + atomic_notifier_chain_register(&panic_notifier_list, &iss_panic_block); + register_sys_off_handler(SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_PLATFORM, + iss_restart, NULL); + register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_PLATFORM, + iss_power_off, NULL); +} diff --git a/arch/xtensa/platforms/iss/simdisk.c b/arch/xtensa/platforms/iss/simdisk.c new file mode 100644 index 000000000..a2ddb49c2 --- /dev/null +++ b/arch/xtensa/platforms/iss/simdisk.c @@ -0,0 +1,404 @@ +/* + * arch/xtensa/platforms/iss/simdisk.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001-2013 Tensilica Inc. + * Authors Victor Prupis + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIMDISK_MAJOR 240 +#define SIMDISK_MINORS 1 +#define MAX_SIMDISK_COUNT 10 + +struct simdisk { + const char *filename; + spinlock_t lock; + struct gendisk *gd; + struct proc_dir_entry *procfile; + int users; + unsigned long size; + int fd; +}; + + +static int simdisk_count = CONFIG_BLK_DEV_SIMDISK_COUNT; +module_param(simdisk_count, int, S_IRUGO); +MODULE_PARM_DESC(simdisk_count, "Number of simdisk units."); + +static int n_files; +static char *filename[MAX_SIMDISK_COUNT] = { +#ifdef CONFIG_SIMDISK0_FILENAME + CONFIG_SIMDISK0_FILENAME, +#ifdef CONFIG_SIMDISK1_FILENAME + CONFIG_SIMDISK1_FILENAME, +#endif +#endif +}; + +/* + * The simdisk code can be built either into the kernel or as a loadable module. + * When built-in, CONFIG_SIMDISK{0,1}_FILENAME can be used to specify the + * initial simdisk filenames and additional filenames can be provided on the + * kernel command line. These arguments are parsed during early boot when slab + * is not yet available, but the command line itself is preserved for the + * lifetime of the kernel, so the incoming pointer is stored directly. + * When built as a loadable module, each value is copied with kstrdup() and all + * allocated memory is freed in simdisk_param_free_filename() when the module is + * unloaded. + */ +static int simdisk_param_set_filename(const char *val, + const struct kernel_param *kp) +{ + char *str; + + if (n_files >= ARRAY_SIZE(filename)) + return -EINVAL; + +#ifdef MODULE + str = kstrdup(val, GFP_KERNEL); + if (!str) + return -ENOMEM; +#else + str = (char *)val; +#endif + + filename[n_files++] = str; + return 0; +} + +static void simdisk_param_free_filename(void *arg) +{ + for (int i = 0; i < n_files; i++) + kfree(filename[i]); +} + +static const struct kernel_param_ops simdisk_param_ops_filename = { + .set = simdisk_param_set_filename, + .free = simdisk_param_free_filename, +}; +module_param_cb(filename, &simdisk_param_ops_filename, NULL, 0); +MODULE_PARM_DESC(filename, "Backing storage filename."); + +static int simdisk_major = SIMDISK_MAJOR; + +static void simdisk_transfer(struct simdisk *dev, unsigned long sector, + unsigned long nsect, char *buffer, int write) +{ + unsigned long offset = sector << SECTOR_SHIFT; + unsigned long nbytes = nsect << SECTOR_SHIFT; + + if (offset > dev->size || dev->size - offset < nbytes) { + pr_notice("Beyond-end %s (%ld %ld)\n", + str_write_read(write), offset, nbytes); + return; + } + + spin_lock(&dev->lock); + while (nbytes > 0) { + unsigned long io; + + simc_lseek(dev->fd, offset, SEEK_SET); + READ_ONCE(*buffer); + if (write) + io = simc_write(dev->fd, buffer, nbytes); + else + io = simc_read(dev->fd, buffer, nbytes); + if (io == -1) { + pr_err("SIMDISK: IO error %d\n", errno); + break; + } + buffer += io; + offset += io; + nbytes -= io; + } + spin_unlock(&dev->lock); +} + +static void simdisk_submit_bio(struct bio *bio) +{ + struct simdisk *dev = bio->bi_bdev->bd_disk->private_data; + struct bio_vec bvec; + struct bvec_iter iter; + sector_t sector = bio->bi_iter.bi_sector; + + bio_for_each_segment(bvec, bio, iter) { + char *buffer = bvec_kmap_local(&bvec); + unsigned len = bvec.bv_len >> SECTOR_SHIFT; + + simdisk_transfer(dev, sector, len, buffer, + bio_data_dir(bio) == WRITE); + sector += len; + kunmap_local(buffer); + } + + bio_endio(bio); +} + +static int simdisk_open(struct gendisk *disk, blk_mode_t mode) +{ + struct simdisk *dev = disk->private_data; + + spin_lock(&dev->lock); + ++dev->users; + spin_unlock(&dev->lock); + return 0; +} + +static void simdisk_release(struct gendisk *disk) +{ + struct simdisk *dev = disk->private_data; + spin_lock(&dev->lock); + --dev->users; + spin_unlock(&dev->lock); +} + +static const struct block_device_operations simdisk_ops = { + .owner = THIS_MODULE, + .submit_bio = simdisk_submit_bio, + .open = simdisk_open, + .release = simdisk_release, +}; + +static struct simdisk *sddev; +static struct proc_dir_entry *simdisk_procdir; + +static int simdisk_attach(struct simdisk *dev, const char *filename) +{ + int err = 0; + + filename = kstrdup(filename, GFP_KERNEL); + if (filename == NULL) + return -ENOMEM; + + spin_lock(&dev->lock); + + if (dev->fd != -1) { + err = -EBUSY; + goto out; + } + dev->fd = simc_open(filename, O_RDWR, 0); + if (dev->fd == -1) { + pr_err("SIMDISK: Can't open %s: %d\n", filename, errno); + err = -ENODEV; + goto out; + } + dev->size = simc_lseek(dev->fd, 0, SEEK_END); + set_capacity(dev->gd, dev->size >> SECTOR_SHIFT); + dev->filename = filename; + pr_info("SIMDISK: %s=%s\n", dev->gd->disk_name, dev->filename); +out: + if (err) + kfree(filename); + spin_unlock(&dev->lock); + + return err; +} + +static int simdisk_detach(struct simdisk *dev) +{ + int err = 0; + + spin_lock(&dev->lock); + + if (dev->users != 0) { + err = -EBUSY; + } else if (dev->fd != -1) { + if (simc_close(dev->fd)) { + pr_err("SIMDISK: error closing %s: %d\n", + dev->filename, errno); + err = -EIO; + } else { + pr_info("SIMDISK: %s detached from %s\n", + dev->gd->disk_name, dev->filename); + dev->fd = -1; + kfree(dev->filename); + dev->filename = NULL; + } + } + spin_unlock(&dev->lock); + return err; +} + +static ssize_t proc_read_simdisk(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + struct simdisk *dev = pde_data(file_inode(file)); + const char *s = dev->filename; + if (s) { + ssize_t len = strlen(s); + char *temp = kmalloc(len + 2, GFP_KERNEL); + + if (!temp) + return -ENOMEM; + + len = scnprintf(temp, len + 2, "%s\n", s); + len = simple_read_from_buffer(buf, size, ppos, + temp, len); + + kfree(temp); + return len; + } + return simple_read_from_buffer(buf, size, ppos, "\n", 1); +} + +static ssize_t proc_write_simdisk(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *tmp; + struct simdisk *dev = pde_data(file_inode(file)); + int err; + + if (count == 0 || count > PAGE_SIZE) + return -EINVAL; + + tmp = memdup_user_nul(buf, count); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + + err = simdisk_detach(dev); + if (err != 0) + goto out_free; + + if (count > 0 && tmp[count - 1] == '\n') + tmp[count - 1] = 0; + + if (tmp[0]) + err = simdisk_attach(dev, tmp); + + if (err == 0) + err = count; +out_free: + kfree(tmp); + return err; +} + +static const struct proc_ops simdisk_proc_ops = { + .proc_read = proc_read_simdisk, + .proc_write = proc_write_simdisk, + .proc_lseek = default_llseek, +}; + +static int __init simdisk_setup(struct simdisk *dev, int which, + struct proc_dir_entry *procdir) +{ + struct queue_limits lim = { + .features = BLK_FEAT_ROTATIONAL, + }; + char tmp[2] = { '0' + which, 0 }; + int err; + + dev->fd = -1; + dev->filename = NULL; + spin_lock_init(&dev->lock); + dev->users = 0; + + dev->gd = blk_alloc_disk(&lim, NUMA_NO_NODE); + if (IS_ERR(dev->gd)) { + err = PTR_ERR(dev->gd); + goto out; + } + dev->gd->major = simdisk_major; + dev->gd->first_minor = which; + dev->gd->minors = SIMDISK_MINORS; + dev->gd->fops = &simdisk_ops; + dev->gd->private_data = dev; + snprintf(dev->gd->disk_name, 32, "simdisk%d", which); + set_capacity(dev->gd, 0); + err = add_disk(dev->gd); + if (err) + goto out_cleanup_disk; + + dev->procfile = proc_create_data(tmp, 0644, procdir, &simdisk_proc_ops, dev); + + return 0; + +out_cleanup_disk: + put_disk(dev->gd); +out: + return err; +} + +static int __init simdisk_init(void) +{ + int i; + + if (register_blkdev(simdisk_major, "simdisk") < 0) { + pr_err("SIMDISK: register_blkdev: %d\n", simdisk_major); + return -EIO; + } + pr_info("SIMDISK: major: %d\n", simdisk_major); + + if (n_files > simdisk_count) + simdisk_count = n_files; + if (simdisk_count > MAX_SIMDISK_COUNT) + simdisk_count = MAX_SIMDISK_COUNT; + + sddev = kmalloc_objs(*sddev, simdisk_count); + if (sddev == NULL) + goto out_unregister; + + simdisk_procdir = proc_mkdir("simdisk", 0); + if (simdisk_procdir == NULL) + goto out_free_unregister; + + for (i = 0; i < simdisk_count; ++i) { + if (simdisk_setup(sddev + i, i, simdisk_procdir) == 0) { + if (filename[i] != NULL && filename[i][0] != 0 && + (n_files == 0 || i < n_files)) + simdisk_attach(sddev + i, filename[i]); + } + } + + return 0; + +out_free_unregister: + kfree(sddev); +out_unregister: + unregister_blkdev(simdisk_major, "simdisk"); + return -ENOMEM; +} +module_init(simdisk_init); + +static void simdisk_teardown(struct simdisk *dev, int which, + struct proc_dir_entry *procdir) +{ + char tmp[2] = { '0' + which, 0 }; + + simdisk_detach(dev); + if (dev->gd) { + del_gendisk(dev->gd); + put_disk(dev->gd); + } + remove_proc_entry(tmp, procdir); +} + +static void __exit simdisk_exit(void) +{ + int i; + + for (i = 0; i < simdisk_count; ++i) + simdisk_teardown(sddev + i, i, simdisk_procdir); + remove_proc_entry("simdisk", 0); + kfree(sddev); + unregister_blkdev(simdisk_major, "simdisk"); +} +module_exit(simdisk_exit); + +MODULE_ALIAS_BLOCKDEV_MAJOR(SIMDISK_MAJOR); + +MODULE_LICENSE("GPL"); diff --git a/arch/xtensa/platforms/xt2000/Makefile b/arch/xtensa/platforms/xt2000/Makefile new file mode 100644 index 000000000..53eaeba5e --- /dev/null +++ b/arch/xtensa/platforms/xt2000/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Tensilica XT2000 Emulation Board +# + +obj-y = setup.o diff --git a/arch/xtensa/platforms/xt2000/include/platform/hardware.h b/arch/xtensa/platforms/xt2000/include/platform/hardware.h new file mode 100644 index 000000000..9f213f573 --- /dev/null +++ b/arch/xtensa/platforms/xt2000/include/platform/hardware.h @@ -0,0 +1,43 @@ +/* + * platform/hardware.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001 Tensilica Inc. + */ + +/* + * This file contains the hardware configuration of the XT2000 board. + */ + +#ifndef _XTENSA_XT2000_HARDWARE_H +#define _XTENSA_XT2000_HARDWARE_H + +#include + +/* + * On-board components. + */ + +#define SONIC83934_INTNUM XCHAL_EXTINT3_NUM +#define SONIC83934_ADDR IOADDR(0x0d030000) + +/* + * V3-PCI + */ + +/* The XT2000 uses the V3 as a cascaded interrupt controller for the PCI bus */ + +#define IRQ_PCI_A (XCHAL_NUM_INTERRUPTS + 0) +#define IRQ_PCI_B (XCHAL_NUM_INTERRUPTS + 1) +#define IRQ_PCI_C (XCHAL_NUM_INTERRUPTS + 2) + +/* + * Various other components. + */ + +#define XT2000_LED_ADDR IOADDR(0x0d040000) + +#endif /* _XTENSA_XT2000_HARDWARE_H */ diff --git a/arch/xtensa/platforms/xt2000/include/platform/serial.h b/arch/xtensa/platforms/xt2000/include/platform/serial.h new file mode 100644 index 000000000..cde804827 --- /dev/null +++ b/arch/xtensa/platforms/xt2000/include/platform/serial.h @@ -0,0 +1,28 @@ +/* + * platform/serial.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001 Tensilica Inc. + */ + +#ifndef _XTENSA_XT2000_SERIAL_H +#define _XTENSA_XT2000_SERIAL_H + +#include +#include + +/* National-Semi PC16552D DUART: */ + +#define DUART16552_1_INTNUM XCHAL_EXTINT4_NUM +#define DUART16552_2_INTNUM XCHAL_EXTINT5_NUM + +#define DUART16552_1_ADDR IOADDR(0x0d050020) /* channel 1 */ +#define DUART16552_2_ADDR IOADDR(0x0d050000) /* channel 2 */ + +#define DUART16552_XTAL_FREQ 18432000 /* crystal frequency in Hz */ +#define BASE_BAUD ( DUART16552_XTAL_FREQ / 16 ) + +#endif /* _XTENSA_XT2000_SERIAL_H */ diff --git a/arch/xtensa/platforms/xt2000/setup.c b/arch/xtensa/platforms/xt2000/setup.c new file mode 100644 index 000000000..eda4c15c6 --- /dev/null +++ b/arch/xtensa/platforms/xt2000/setup.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * arch/xtensa/platforms/xt2000/setup.c + * + * Platform specific functions for the XT2000 board. + * + * Authors: Chris Zankel + * Joe Taylor + * + * Copyright 2001 - 2004 Tensilica Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* Assumes s points to an 8-chr string. No checking for NULL. */ + +static void led_print (int f, char *s) +{ + unsigned long* led_addr = (unsigned long*) (XT2000_LED_ADDR + 0xE0) + f; + int i; + for (i = f; i < 8; i++) + if ((*led_addr++ = *s++) == 0) + break; +} + +static int xt2000_power_off(struct sys_off_data *unused) +{ + led_print (0, "POWEROFF"); + local_irq_disable(); + while (1); + return NOTIFY_DONE; +} + +static int xt2000_restart(struct sys_off_data *unused) +{ + /* Flush and reset the mmu, simulate a processor reset, and + * jump to the reset vector. */ + cpu_reset(); + + return NOTIFY_DONE; +} + +void __init platform_setup(char** cmdline) +{ + led_print (0, "LINUX "); +} + +/* Heartbeat. Let the LED blink. */ + +static void xt2000_heartbeat(struct timer_list *unused); + +static DEFINE_TIMER(heartbeat_timer, xt2000_heartbeat); + +static void xt2000_heartbeat(struct timer_list *unused) +{ + static int i; + + led_print(7, i ? "." : " "); + i ^= 1; + mod_timer(&heartbeat_timer, jiffies + HZ / 2); +} + +//#define RS_TABLE_SIZE 2 + +#define _SERIAL_PORT(_base,_irq) \ +{ \ + .mapbase = (_base), \ + .membase = (void*)(_base), \ + .irq = (_irq), \ + .uartclk = DUART16552_XTAL_FREQ, \ + .iotype = UPIO_MEM, \ + .flags = UPF_BOOT_AUTOCONF, \ + .regshift = 2, \ +} + +static struct plat_serial8250_port xt2000_serial_data[] = { +#if XCHAL_HAVE_BE + _SERIAL_PORT(DUART16552_1_ADDR + 3, DUART16552_1_INTNUM), + _SERIAL_PORT(DUART16552_2_ADDR + 3, DUART16552_2_INTNUM), +#else + _SERIAL_PORT(DUART16552_1_ADDR, DUART16552_1_INTNUM), + _SERIAL_PORT(DUART16552_2_ADDR, DUART16552_2_INTNUM), +#endif + { } +}; + +static struct platform_device xt2000_serial8250_device = { + .name = "serial8250", + .id = PLAT8250_DEV_PLATFORM, + .dev = { + .platform_data = xt2000_serial_data, + }, +}; + +static struct resource xt2000_sonic_res[] = { + { + .start = SONIC83934_ADDR, + .end = SONIC83934_ADDR + 0xff, + .flags = IORESOURCE_MEM, + }, + { + .start = SONIC83934_INTNUM, + .end = SONIC83934_INTNUM, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device xt2000_sonic_device = { + .name = "xtsonic", + .num_resources = ARRAY_SIZE(xt2000_sonic_res), + .resource = xt2000_sonic_res, +}; + +static int __init xt2000_setup_devinit(void) +{ + platform_device_register(&xt2000_serial8250_device); + platform_device_register(&xt2000_sonic_device); + mod_timer(&heartbeat_timer, jiffies + HZ / 2); + register_sys_off_handler(SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_PLATFORM, + xt2000_restart, NULL); + register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + xt2000_power_off, NULL); + return 0; +} + +device_initcall(xt2000_setup_devinit); diff --git a/arch/xtensa/platforms/xtfpga/Makefile b/arch/xtensa/platforms/xtfpga/Makefile new file mode 100644 index 000000000..0600371b2 --- /dev/null +++ b/arch/xtensa/platforms/xtfpga/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for the Tensilica xtavnet Emulation Board +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are in the main makefile... + +obj-y += setup.o +obj-$(CONFIG_XTFPGA_LCD) += lcd.o diff --git a/arch/xtensa/platforms/xtfpga/include/platform/hardware.h b/arch/xtensa/platforms/xtfpga/include/platform/hardware.h new file mode 100644 index 000000000..30d9cb6cf --- /dev/null +++ b/arch/xtensa/platforms/xtfpga/include/platform/hardware.h @@ -0,0 +1,60 @@ +/* + * arch/xtensa/platform/xtavnet/include/platform/hardware.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2006 Tensilica Inc. + */ + +/* + * This file contains the hardware configuration of the XTAVNET boards. + */ + +#include + +#ifndef __XTENSA_XTAVNET_HARDWARE_H +#define __XTENSA_XTAVNET_HARDWARE_H + +/* Default assignment of LX60 devices to external interrupts. */ + +#ifdef CONFIG_XTENSA_MX +#define DUART16552_INTNUM XCHAL_EXTINT3_NUM +#define OETH_IRQ XCHAL_EXTINT4_NUM +#define C67X00_IRQ XCHAL_EXTINT8_NUM +#else +#define DUART16552_INTNUM XCHAL_EXTINT0_NUM +#define OETH_IRQ XCHAL_EXTINT1_NUM +#define C67X00_IRQ XCHAL_EXTINT5_NUM +#endif + +/* + * Device addresses and parameters. + */ + +/* UART */ +#define DUART16552_PADDR (XCHAL_KIO_PADDR + 0x0D050020) + +/* Misc. */ +#define XTFPGA_FPGAREGS_VADDR IOADDR(0x0D020000) +/* Clock frequency in Hz (read-only): */ +#define XTFPGA_CLKFRQ_VADDR (XTFPGA_FPGAREGS_VADDR + 0x04) +/* Setting of 8 DIP switches: */ +#define DIP_SWITCHES_VADDR (XTFPGA_FPGAREGS_VADDR + 0x0C) +/* Software reset (write 0xdead): */ +#define XTFPGA_SWRST_VADDR (XTFPGA_FPGAREGS_VADDR + 0x10) + +/* OpenCores Ethernet controller: */ + /* regs + RX/TX descriptors */ +#define OETH_REGS_PADDR (XCHAL_KIO_PADDR + 0x0D030000) +#define OETH_REGS_SIZE 0x1000 +#define OETH_SRAMBUFF_PADDR (XCHAL_KIO_PADDR + 0x0D800000) + + /* 5*rx buffs + 5*tx buffs */ +#define OETH_SRAMBUFF_SIZE (5 * 0x600 + 5 * 0x600) + +#define C67X00_PADDR (XCHAL_KIO_PADDR + 0x0D0D0000) +#define C67X00_SIZE 0x10 + +#endif /* __XTENSA_XTAVNET_HARDWARE_H */ diff --git a/arch/xtensa/platforms/xtfpga/include/platform/lcd.h b/arch/xtensa/platforms/xtfpga/include/platform/lcd.h new file mode 100644 index 000000000..4c8541ed1 --- /dev/null +++ b/arch/xtensa/platforms/xtfpga/include/platform/lcd.h @@ -0,0 +1,35 @@ +/* + * arch/xtensa/platform/xtavnet/include/platform/lcd.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001, 2006 Tensilica Inc. + */ + +#ifndef __XTENSA_XTAVNET_LCD_H +#define __XTENSA_XTAVNET_LCD_H + +#ifdef CONFIG_XTFPGA_LCD +/* Display string STR at position POS on the LCD. */ +void lcd_disp_at_pos(char *str, unsigned char pos); + +/* Shift the contents of the LCD display left or right. */ +void lcd_shiftleft(void); +void lcd_shiftright(void); +#else +static inline void lcd_disp_at_pos(char *str, unsigned char pos) +{ +} + +static inline void lcd_shiftleft(void) +{ +} + +static inline void lcd_shiftright(void) +{ +} +#endif + +#endif diff --git a/arch/xtensa/platforms/xtfpga/include/platform/serial.h b/arch/xtensa/platforms/xtfpga/include/platform/serial.h new file mode 100644 index 000000000..14d8f7bee --- /dev/null +++ b/arch/xtensa/platforms/xtfpga/include/platform/serial.h @@ -0,0 +1,18 @@ +/* + * arch/xtensa/platform/xtavnet/include/platform/serial.h + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001, 2006 Tensilica Inc. + */ + +#ifndef __ASM_XTENSA_XTAVNET_SERIAL_H +#define __ASM_XTENSA_XTAVNET_SERIAL_H + +#include + +#define BASE_BAUD (*(long *)XTFPGA_CLKFRQ_VADDR / 16) + +#endif /* __ASM_XTENSA_XTAVNET_SERIAL_H */ diff --git a/arch/xtensa/platforms/xtfpga/lcd.c b/arch/xtensa/platforms/xtfpga/lcd.c new file mode 100644 index 000000000..2f7eb66c2 --- /dev/null +++ b/arch/xtensa/platforms/xtfpga/lcd.c @@ -0,0 +1,89 @@ +/* + * Driver for the LCD display on the Tensilica XTFPGA board family. + * http://www.mytechcorp.com/cfdata/productFile/File1/MOC-16216B-B-A0A04.pdf + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2001, 2006 Tensilica Inc. + * Copyright (C) 2015 Cadence Design Systems Inc. + */ + +#include +#include +#include + +#include +#include + +/* LCD instruction and data addresses. */ +#define LCD_INSTR_ADDR ((char *)IOADDR(CONFIG_XTFPGA_LCD_BASE_ADDR)) +#define LCD_DATA_ADDR (LCD_INSTR_ADDR + 4) + +#define LCD_CLEAR 0x1 +#define LCD_DISPLAY_ON 0xc + +/* 8bit and 2 lines display */ +#define LCD_DISPLAY_MODE8BIT 0x38 +#define LCD_DISPLAY_MODE4BIT 0x28 +#define LCD_DISPLAY_POS 0x80 +#define LCD_SHIFT_LEFT 0x18 +#define LCD_SHIFT_RIGHT 0x1c + +static void lcd_put_byte(u8 *addr, u8 data) +{ +#ifdef CONFIG_XTFPGA_LCD_8BIT_ACCESS + WRITE_ONCE(*addr, data); +#else + WRITE_ONCE(*addr, data & 0xf0); + WRITE_ONCE(*addr, (data << 4) & 0xf0); +#endif +} + +static int __init lcd_init(void) +{ + WRITE_ONCE(*LCD_INSTR_ADDR, LCD_DISPLAY_MODE8BIT); + mdelay(5); + WRITE_ONCE(*LCD_INSTR_ADDR, LCD_DISPLAY_MODE8BIT); + udelay(200); + WRITE_ONCE(*LCD_INSTR_ADDR, LCD_DISPLAY_MODE8BIT); + udelay(50); +#ifndef CONFIG_XTFPGA_LCD_8BIT_ACCESS + WRITE_ONCE(*LCD_INSTR_ADDR, LCD_DISPLAY_MODE4BIT); + udelay(50); + lcd_put_byte(LCD_INSTR_ADDR, LCD_DISPLAY_MODE4BIT); + udelay(50); +#endif + lcd_put_byte(LCD_INSTR_ADDR, LCD_DISPLAY_ON); + udelay(50); + lcd_put_byte(LCD_INSTR_ADDR, LCD_CLEAR); + mdelay(10); + lcd_disp_at_pos("XTENSA LINUX", 0); + return 0; +} + +void lcd_disp_at_pos(char *str, unsigned char pos) +{ + lcd_put_byte(LCD_INSTR_ADDR, LCD_DISPLAY_POS | pos); + udelay(100); + while (*str != 0) { + lcd_put_byte(LCD_DATA_ADDR, *str); + udelay(200); + str++; + } +} + +void lcd_shiftleft(void) +{ + lcd_put_byte(LCD_INSTR_ADDR, LCD_SHIFT_LEFT); + udelay(50); +} + +void lcd_shiftright(void) +{ + lcd_put_byte(LCD_INSTR_ADDR, LCD_SHIFT_RIGHT); + udelay(50); +} + +arch_initcall(lcd_init); diff --git a/arch/xtensa/platforms/xtfpga/setup.c b/arch/xtensa/platforms/xtfpga/setup.c new file mode 100644 index 000000000..67d7f37f1 --- /dev/null +++ b/arch/xtensa/platforms/xtfpga/setup.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * arch/xtensa/platform/xtavnet/setup.c + * + * ... + * + * Authors: Chris Zankel + * Joe Taylor + * + * Copyright 2001 - 2006 Tensilica Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int xtfpga_power_off(struct sys_off_data *unused) +{ + lcd_disp_at_pos("POWEROFF", 0); + local_irq_disable(); + while (1) + cpu_relax(); + return NOTIFY_DONE; +} + +static int xtfpga_restart(struct sys_off_data *unused) +{ + /* Try software reset first. */ + WRITE_ONCE(*(u32 *)XTFPGA_SWRST_VADDR, 0xdead); + + /* If software reset did not work, flush and reset the mmu, + * simulate a processor reset, and jump to the reset vector. + */ + cpu_reset(); + + return NOTIFY_DONE; +} + +#ifdef CONFIG_XTENSA_CALIBRATE_CCOUNT + +void __init platform_calibrate_ccount(void) +{ + ccount_freq = *(long *)XTFPGA_CLKFRQ_VADDR; +} + +#endif + +static void __init xtfpga_register_handlers(void) +{ + register_sys_off_handler(SYS_OFF_MODE_RESTART, + SYS_OFF_PRIO_PLATFORM, + xtfpga_restart, NULL); + register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + xtfpga_power_off, NULL); +} + +#ifdef CONFIG_USE_OF + +static void __init xtfpga_clk_setup(struct device_node *np) +{ + void __iomem *base = of_iomap(np, 0); + struct clk *clk; + u32 freq; + + if (!base) { + pr_err("%pOFn: invalid address\n", np); + return; + } + + freq = __raw_readl(base); + iounmap(base); + clk = clk_register_fixed_rate(NULL, np->name, NULL, 0, freq); + + if (IS_ERR(clk)) { + pr_err("%pOFn: clk registration failed\n", np); + return; + } + + if (of_clk_add_provider(np, of_clk_src_simple_get, clk)) { + pr_err("%pOFn: clk provider registration failed\n", np); + return; + } +} +CLK_OF_DECLARE(xtfpga_clk, "cdns,xtfpga-clock", xtfpga_clk_setup); + +#define MAC_LEN 6 +static void __init update_local_mac(struct device_node *node) +{ + struct property *newmac; + const u8* macaddr; + int prop_len; + + macaddr = of_get_property(node, "local-mac-address", &prop_len); + if (macaddr == NULL || prop_len != MAC_LEN) + return; + + newmac = kzalloc(sizeof(*newmac) + MAC_LEN, GFP_KERNEL); + if (newmac == NULL) + return; + + newmac->value = newmac + 1; + newmac->length = MAC_LEN; + newmac->name = kstrdup("local-mac-address", GFP_KERNEL); + if (newmac->name == NULL) { + kfree(newmac); + return; + } + + memcpy(newmac->value, macaddr, MAC_LEN); + ((u8*)newmac->value)[5] = (*(u32*)DIP_SWITCHES_VADDR) & 0x3f; + of_update_property(node, newmac); +} + +static int __init machine_setup(void) +{ + struct device_node *eth = NULL; + + if ((eth = of_find_compatible_node(eth, NULL, "opencores,ethoc"))) + update_local_mac(eth); + of_node_put(eth); + + xtfpga_register_handlers(); + + return 0; +} +arch_initcall(machine_setup); + +#else + +#include +#include +#include +#include + +/*---------------------------------------------------------------------------- + * Ethernet -- OpenCores Ethernet MAC (ethoc driver) + */ + +static struct resource ethoc_res[] = { + [0] = { /* register space */ + .start = OETH_REGS_PADDR, + .end = OETH_REGS_PADDR + OETH_REGS_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { /* buffer space */ + .start = OETH_SRAMBUFF_PADDR, + .end = OETH_SRAMBUFF_PADDR + OETH_SRAMBUFF_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [2] = { /* IRQ number */ + .start = XTENSA_PIC_LINUX_IRQ(OETH_IRQ), + .end = XTENSA_PIC_LINUX_IRQ(OETH_IRQ), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct ethoc_platform_data ethoc_pdata = { + /* + * The MAC address for these boards is 00:50:c2:13:6f:xx. + * The last byte (here as zero) is read from the DIP switches on the + * board. + */ + .hwaddr = { 0x00, 0x50, 0xc2, 0x13, 0x6f, 0 }, + .phy_id = -1, + .big_endian = XCHAL_HAVE_BE, +}; + +static struct platform_device ethoc_device = { + .name = "ethoc", + .id = -1, + .num_resources = ARRAY_SIZE(ethoc_res), + .resource = ethoc_res, + .dev = { + .platform_data = ðoc_pdata, + }, +}; + +/*---------------------------------------------------------------------------- + * USB Host/Device -- Cypress CY7C67300 + */ + +static struct resource c67x00_res[] = { + [0] = { /* register space */ + .start = C67X00_PADDR, + .end = C67X00_PADDR + C67X00_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { /* IRQ number */ + .start = XTENSA_PIC_LINUX_IRQ(C67X00_IRQ), + .end = XTENSA_PIC_LINUX_IRQ(C67X00_IRQ), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct c67x00_platform_data c67x00_pdata = { + .sie_config = C67X00_SIE1_HOST | C67X00_SIE2_UNUSED, + .hpi_regstep = 4, +}; + +static struct platform_device c67x00_device = { + .name = "c67x00", + .id = -1, + .num_resources = ARRAY_SIZE(c67x00_res), + .resource = c67x00_res, + .dev = { + .platform_data = &c67x00_pdata, + }, +}; + +/*---------------------------------------------------------------------------- + * UART + */ + +static struct resource serial_resource = { + .start = DUART16552_PADDR, + .end = DUART16552_PADDR + 0x1f, + .flags = IORESOURCE_MEM, +}; + +static struct plat_serial8250_port serial_platform_data[] = { + [0] = { + .mapbase = DUART16552_PADDR, + .irq = XTENSA_PIC_LINUX_IRQ(DUART16552_INTNUM), + .flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | + UPF_IOREMAP, + .iotype = XCHAL_HAVE_BE ? UPIO_MEM32BE : UPIO_MEM32, + .regshift = 2, + .uartclk = 0, /* set in xtavnet_init() */ + }, + { }, +}; + +static struct platform_device xtavnet_uart = { + .name = "serial8250", + .id = PLAT8250_DEV_PLATFORM, + .dev = { + .platform_data = serial_platform_data, + }, + .num_resources = 1, + .resource = &serial_resource, +}; + +/* platform devices */ +static struct platform_device *platform_devices[] __initdata = { + ðoc_device, + &c67x00_device, + &xtavnet_uart, +}; + + +static int __init xtavnet_init(void) +{ + /* Ethernet MAC address. */ + ethoc_pdata.hwaddr[5] = *(u32 *)DIP_SWITCHES_VADDR; + + /* Clock rate varies among FPGA bitstreams; board specific FPGA register + * reports the actual clock rate. + */ + serial_platform_data[0].uartclk = *(long *)XTFPGA_CLKFRQ_VADDR; + + + /* register platform devices */ + platform_add_devices(platform_devices, ARRAY_SIZE(platform_devices)); + + /* ETHOC driver is a bit quiet; at least display Ethernet MAC, so user + * knows whether they set it correctly on the DIP switches. + */ + pr_info("XTFPGA: Ethernet MAC %pM\n", ethoc_pdata.hwaddr); + ethoc_pdata.eth_clkfreq = *(long *)XTFPGA_CLKFRQ_VADDR; + + xtfpga_register_handlers(); + + return 0; +} + +/* + * Register to be done during do_initcalls(). + */ +arch_initcall(xtavnet_init); + +#endif /* CONFIG_USE_OF */ -- cgit v1.3.1