diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /arch/xtensa/platforms/iss | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/xtensa/platforms/iss')
-rw-r--r-- | arch/xtensa/platforms/iss/Makefile | 11 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/console.c | 199 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/include/platform/serial.h | 15 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/include/platform/simcall-gdbio.h | 34 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/include/platform/simcall-iss.h | 73 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/include/platform/simcall.h | 110 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/network.c | 641 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/setup.c | 91 | ||||
-rw-r--r-- | arch/xtensa/platforms/iss/simdisk.c | 366 |
9 files changed, 1540 insertions, 0 deletions
diff --git a/arch/xtensa/platforms/iss/Makefile b/arch/xtensa/platforms/iss/Makefile new file mode 100644 index 0000000000..f3dd5e72a3 --- /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 0000000000..7d1f8b398a --- /dev/null +++ b/arch/xtensa/platforms/iss/console.c @@ -0,0 +1,199 @@ +/* + * 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/major.h> +#include <linux/param.h> +#include <linux/seq_file.h> +#include <linux/serial.h> + +#include <linux/uaccess.h> +#include <asm/irq.h> + +#include <platform/simcall.h> + +#include <linux/tty.h> +#include <linux/tty_flip.h> + +#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) + del_timer_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; + unsigned char 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) +{ + if (s && *s != 0) { + int len = strlen(s); + simc_write(1, s, count < len ? count : len); + } +} + +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 0000000000..16aec542d4 --- /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 0000000000..e642860e25 --- /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 0000000000..5a1e7a1f18 --- /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 0000000000..e1ec50ce39 --- /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 <linux/bug.h> + +#ifdef CONFIG_XTENSA_SIMCALL_ISS +#include <platform/simcall-iss.h> +#endif +#ifdef CONFIG_XTENSA_SIMCALL_GDBIO +#include <platform/simcall-gdbio.h> +#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 0000000000..e89f27f2bb --- /dev/null +++ b/arch/xtensa/platforms/iss/network.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * arch/xtensa/platforms/iss/network.c + * + * Platform specific initialization. + * + * Authors: Chris Zankel <chris@zankel.net> + * Based on work form the UML team. + * + * Copyright 2005 Tensilica Inc. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/list.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/if_ether.h> +#include <linux/inetdevice.h> +#include <linux/init.h> +#include <linux/if_tun.h> +#include <linux/etherdevice.h> +#include <linux/interrupt.h> +#include <linux/ioctl.h> +#include <linux/memblock.h> +#include <linux/ethtool.h> +#include <linux/rtnetlink.h> +#include <linux/platform_device.h> + +#include <platform/simcall.h> + +#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 = from_timer(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); + del_timer_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 0000000000..0f1fe13269 --- /dev/null +++ b/arch/xtensa/platforms/iss/setup.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * arch/xtensa/platform-iss/setup.c + * + * Platform specific initialization. + * + * Authors: Chris Zankel <chris@zankel.net> + * Joe Taylor <joe@tensilica.com> + * + * Copyright 2001 - 2005 Tensilica Inc. + * Copyright 2017 Cadence Design Systems Inc. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/notifier.h> +#include <linux/panic_notifier.h> +#include <linux/printk.h> +#include <linux/reboot.h> +#include <linux/string.h> + +#include <asm/platform.h> +#include <asm/setup.h> + +#include <platform/simcall.h> + + +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 notifier_block *this, + unsigned long event, void *ptr) +{ + /* Flush and reset the mmu, simulate a processor reset, and + * jump to the reset vector. */ + cpu_reset(); + + return NOTIFY_DONE; +} + +static struct notifier_block iss_restart_block = { + .notifier_call = iss_restart, +}; + +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_restart_handler(&iss_restart_block); + 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 0000000000..178cf96ca1 --- /dev/null +++ b/arch/xtensa/platforms/iss/simdisk.c @@ -0,0 +1,366 @@ +/* + * 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 <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/blkdev.h> +#include <linux/bio.h> +#include <linux/proc_fs.h> +#include <linux/uaccess.h> +#include <platform/simcall.h> + +#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 const char *filename[MAX_SIMDISK_COUNT] = { +#ifdef CONFIG_SIMDISK0_FILENAME + CONFIG_SIMDISK0_FILENAME, +#ifdef CONFIG_SIMDISK1_FILENAME + CONFIG_SIMDISK1_FILENAME, +#endif +#endif +}; + +static int simdisk_param_set_filename(const char *val, + const struct kernel_param *kp) +{ + if (n_files < ARRAY_SIZE(filename)) + filename[n_files++] = val; + else + return -EINVAL; + return 0; +} + +static const struct kernel_param_ops simdisk_param_ops_filename = { + .set = simdisk_param_set_filename, +}; +module_param_cb(filename, &simdisk_param_ops_filename, &n_files, 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", + write ? "write" : "read", 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 = memdup_user_nul(buf, count); + struct simdisk *dev = pde_data(file_inode(file)); + int err; + + 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) +{ + char tmp[2] = { '0' + which, 0 }; + int err = -ENOMEM; + + dev->fd = -1; + dev->filename = NULL; + spin_lock_init(&dev->lock); + dev->users = 0; + + dev->gd = blk_alloc_disk(NUMA_NO_NODE); + if (!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_array(simdisk_count, sizeof(*sddev), GFP_KERNEL); + 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"); |