aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/soc/marvell/octeontx2-pcicons/otx2-pci-console.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/marvell/octeontx2-pcicons/otx2-pci-console.c')
-rw-r--r--drivers/soc/marvell/octeontx2-pcicons/otx2-pci-console.c1350
1 files changed, 1350 insertions, 0 deletions
diff --git a/drivers/soc/marvell/octeontx2-pcicons/otx2-pci-console.c b/drivers/soc/marvell/octeontx2-pcicons/otx2-pci-console.c
new file mode 100644
index 000000000000..a13ffd32c73a
--- /dev/null
+++ b/drivers/soc/marvell/octeontx2-pcicons/otx2-pci-console.c
@@ -0,0 +1,1350 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Supports the PCI Console when OcteonTX2 is running as an Endpoint.
+ *
+ */
+
+/* Implementation notes:
+ *
+ * There are four types of devices for which a driver is provided by this
+ * module:
+ *
+ * - PCI console nexus device
+ * - PCI console device
+ * - Linux console
+ * - Linux TTY
+ *
+ * The primary device to which the driver initially attaches is the
+ * 'PCI console nexus', represented in the Device Tree as
+ * 'pci-console-nexus@0x7f000000'.
+ *
+ * The driver entry points are declared in 'pci_console_nexus_driver'.
+ *
+ * During its initialization, the pci_console_nexus_driver locates its device
+ * memory and verifies that it has been configured appropriately
+ * (i.e. by U-Boot).
+ *
+ * Next, it registers a platform driver for the actual console devices;
+ * this driver's entry points are declared in 'pci_console_driver'.
+ *
+ * Finally, it populates the Device Tree with the console devices, which are
+ * represented in the Device Tree as 'pci-console@{0-7}' – these are children
+ * of the 'PCI console nexus' device.
+ *
+ * At this point, Linux will probe each new console device, which in turn will
+ * register a Linux console. The entry points for the Linux console are
+ * declared as part of the private state structure of the Linux console device
+ * (see invocation of 'register_console()' in the function 'pci_console_init()'.
+ *
+ * The Linux console device will, in turn, register a Linux TTY device. These
+ * device entry points are declared in 'pci_console_dev_tty_ops'.
+ *
+ * It is the Linux console & TTY devices which actually transfer data between
+ * Linux and the OcteonTX device memory; the OcteonTX device memory is accessed
+ * by the host remote console application. This data transfer uses low-level
+ * OcteonTX functions.
+ *
+ * Naming conventions:
+ *
+ * PCI console nexus device functions are named 'pci_console_nexus_xxx'.
+ * PCI console device functions are named 'pci_console_xxx'.
+ * Linux console device functions are named 'pci_console_dev_xxx'.
+ * Linux TTY device functions are named 'pci_console_dev_tty_xxx'.
+ * Low-level OcteonTX routines are named 'octeontx_console_xxx'.
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/uio_driver.h>
+#include <linux/irqchip/arm-gic-v3.h>
+#include <linux/dma-mapping.h>
+#include <linux/device.h>
+#include <linux/iommu.h>
+#include <linux/of_address.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/tty_driver.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include "otx2-pci-console.h"
+
+#define DRV_NAME "pci-console"
+#define NEXUS_DRV_NAME DRV_NAME "-nexus"
+
+/* copied from Octeon pci console driver */
+#define TTY_DRV_MAJOR_VER 4
+#define TTY_DRV_MINOR_VER_START 96
+
+#ifdef CONFIG_OCTEONTX2_PCI_CONSOLE_DEBUG
+# define dbgmsg(dev, ...) dev_info((dev), __VA_ARGS__)
+#else
+# define dbgmsg(dev, ...) (void)(dev)
+#endif // CONFIG_OCTEONTX2_PCI_CONSOLE_DEBUG
+
+static u32 max_consoles = 1;
+module_param(max_consoles, uint, 0644);
+MODULE_PARM_DESC(max_consoles, "Maximum console count to support");
+
+/* pci console driver prototypes */
+static void pci_console_dev_write(struct console *cons, const char *buf,
+ unsigned int len);
+static struct tty_driver *pci_console_dev_device(struct console *cons,
+ int *index);
+static int pci_console_dev_setup(struct console *cons, char *arg);
+static struct platform_driver pci_console_driver;
+
+/* pci console TTY driver prototypes */
+static int pci_console_dev_tty_open(struct tty_struct *tty, struct file *filp);
+static void pci_console_dev_tty_close(struct tty_struct *tty,
+ struct file *filp);
+static int pci_console_dev_tty_write(struct tty_struct *tty,
+ const unsigned char *buf, int count);
+static int pci_console_dev_tty_write_room(struct tty_struct *tty);
+static int pci_console_dev_tty_chars_in_buffer(struct tty_struct *tty);
+static void pci_console_dev_tty_send_xchar(struct tty_struct *tty, char ch);
+
+/* TTY driver operations table */
+static const struct tty_operations pci_console_dev_tty_ops = {
+ .open = pci_console_dev_tty_open,
+ .close = pci_console_dev_tty_close,
+ .write = pci_console_dev_tty_write,
+ .write_room = pci_console_dev_tty_write_room,
+ .chars_in_buffer = pci_console_dev_tty_chars_in_buffer,
+ .send_xchar = pci_console_dev_tty_send_xchar,
+};
+
+static u32 max_cons_mask;
+
+/*
+ * Utility function; returns the number of free bytes in the buffer.
+ *
+ * @param buffer_size size of buffer
+ * @param wr_idx write index
+ * @param rd_idx read index
+ *
+ * @return number of bytes free
+ */
+static int buffer_free_bytes(size_t buffer_size, u32 wr_idx, u32 rd_idx)
+{
+ if (rd_idx >= buffer_size || wr_idx >= buffer_size)
+ return -1;
+ return ((buffer_size - 1) - (wr_idx - rd_idx)) % buffer_size;
+}
+
+/*
+ * Utility function; returns the number of pending bytes (i.e. data) in the
+ * buffer.
+ *
+ * @param buffer_size size of buffer
+ * @param wr_idx write index
+ * @param rd_idx read index
+ *
+ * @return number of pending data bytes
+ */
+static int buffer_pending_bytes(size_t buffer_size, u32 wr_idx, u32 rd_idx)
+{
+ if (rd_idx >= buffer_size || wr_idx >= buffer_size)
+ return -1;
+ return buffer_size - 1 -
+ buffer_free_bytes(buffer_size, wr_idx, rd_idx);
+}
+
+/* ======================== pci console nexus driver ======================== */
+
+/*
+ * Check that the console version is acceptable.
+ */
+static bool pci_console_nexus_check_ver(u8 major, u8 minor)
+{
+ if (major > OCTEONTX_PCIE_CONSOLE_MAJOR)
+ return true;
+ if (major == OCTEONTX_PCIE_CONSOLE_MAJOR &&
+ minor >= OCTEONTX_PCIE_CONSOLE_MINOR)
+ return true;
+ return false;
+}
+
+/*
+ * Used to initialize access to nexus memory.
+ */
+static int pci_console_nexus_init_resources(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console_nexus *pci_cons_nexus = platform_get_drvdata(pdev);
+ struct device_node *of_node;
+ const __be32 *of_base;
+ u64 of_xbase, of_size;
+ int ret;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ WARN_ON(!pci_cons_nexus);
+
+ ret = -ENODEV;
+
+ pci_cons_nexus->of_node = of_node = pdev->dev.of_node;
+ if (!of_node) {
+ dev_err(dev, "Missing devicetree configuration\n");
+ goto exit;
+ }
+
+ of_base = of_get_address(of_node, 0, &of_size, 0);
+ if (!of_base) {
+ dev_err(dev, "Missing configuration base address\n");
+ goto exit;
+ }
+
+ of_xbase = of_translate_address(of_node, of_base);
+ /* TODO: verify we can use WC */
+ if (of_xbase != OF_BAD_ADDR)
+ pci_cons_nexus->desc =
+ ioremap_wc(of_xbase, of_size);
+
+ if (!pci_cons_nexus->desc) {
+ dev_err(dev, "Invalid configuration base address\n");
+ goto exit;
+ }
+
+ dbgmsg(dev, "of_base: %p (%llx), of_size: %llx, nexus:%p\n",
+ of_base, of_xbase, of_size, pci_cons_nexus->desc);
+
+ ret = 0;
+
+exit:
+ return ret ? -ENODEV : 0;
+}
+
+static int pci_console_nexus_de_init_resources(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console_nexus *pci_cons_nexus = platform_get_drvdata(pdev);
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ if (pci_cons_nexus && pci_cons_nexus->desc) {
+ iounmap(pci_cons_nexus->desc);
+ pci_cons_nexus->desc = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * This is used to initialize the console nexus state.
+ */
+static int pci_console_nexus_init(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console_nexus *pci_cons_nexus = platform_get_drvdata(pdev);
+ struct octeontx_pcie_console_nexus __iomem *nexus;
+ struct device_node *child_node;
+ uint num_consoles;
+ int ret;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ WARN_ON(!pci_cons_nexus);
+
+ ret = -ENODEV;
+
+ nexus = pci_cons_nexus->desc;
+ if (!nexus)
+ goto exit;
+
+ /* Verify/use existing configuration (i.e. from U-Boot) */
+ if (readq(&nexus->magic) !=
+ cpu_to_le64(OCTEONTX_PCIE_CONSOLE_NEXUS_MAGIC)) {
+ dev_err(dev, "Invalid nexus signature (0x%llx).\n",
+ (long long)readq(&nexus->magic));
+ goto exit;
+ }
+
+ if (!pci_console_nexus_check_ver(readb(&nexus->major_version),
+ readb(&nexus->minor_version))) {
+ dev_err(dev,
+ "Unsupported nexus version %u.%u (%u.%u)\n)",
+ readb(&nexus->major_version),
+ readb(&nexus->minor_version),
+ OCTEONTX_PCIE_CONSOLE_MAJOR,
+ OCTEONTX_PCIE_CONSOLE_MINOR);
+ goto exit;
+ }
+
+ if (!readb(&nexus->num_consoles)) {
+ dev_err(dev, "No consoles present");
+ goto exit;
+ }
+
+ /* enumerate 'available' consoles present in device tree */
+ num_consoles = 0;
+ for_each_available_child_of_node(pci_cons_nexus->of_node,
+ child_node)
+ if (of_device_is_compatible(child_node,
+ "marvell,pci-console"))
+ num_consoles++;
+
+ if (num_consoles < readb(&nexus->num_consoles)) {
+ dev_err(dev,
+ "Console count mismatch: DT %d, nexus: %d\n",
+ num_consoles, readb(&nexus->num_consoles));
+ goto exit;
+ }
+
+ dbgmsg(dev,
+ "Console nexus initialized: ver %u.%u, %u consoles available\n",
+ readb(&nexus->major_version), readb(&nexus->minor_version),
+ readb(&nexus->num_consoles));
+
+ ret = 0;
+
+exit:
+ return ret ? -ENODEV : 0;
+}
+
+/*
+ * This is the main probe routine for the console nexus driver.
+ */
+static int pci_console_nexus_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console_nexus *pci_cons_nexus;
+ bool registered;
+ int ret;
+
+ BUILD_BUG_ON(offsetof(struct octeontx_pcie_console_nexus, console_addr)
+ != 128);
+
+ dbgmsg(dev, "%s: entry, max_consoles %d\n", __func__, max_consoles);
+
+ max_cons_mask = BIT(max_consoles) - 1;
+
+ pci_cons_nexus = NULL;
+ registered = false;
+
+ ret = -ENODEV;
+
+ /* allocate device structure */
+ pci_cons_nexus = devm_kzalloc(dev, sizeof(*pci_cons_nexus),
+ GFP_KERNEL);
+
+ if (pci_cons_nexus == NULL) {
+ ret = -ENOMEM;
+ dev_err(dev, "Unable to allocate drv context.\n");
+ goto exit;
+ }
+
+ platform_set_drvdata(pdev, pci_cons_nexus);
+
+ ret = pci_console_nexus_init_resources(pdev);
+ if (ret)
+ goto exit;
+
+ ret = pci_console_nexus_init(pdev);
+ if (ret)
+ goto exit;
+
+ dev_info(dev, "Registering child console driver...\n");
+
+ ret = platform_driver_register(&pci_console_driver);
+
+ if (ret) {
+ dev_err(dev,
+ "Error %d registering child console driver\n",
+ ret);
+ goto exit;
+ } else
+ registered = true;
+
+ ret = of_platform_populate(pci_cons_nexus->of_node, NULL, NULL,
+ dev);
+
+ if (ret) {
+ dev_err(dev, "Error %d populating children of %s\n",
+ ret,
+ of_node_full_name(pci_cons_nexus->of_node));
+ goto exit;
+ }
+
+ ret = 0;
+
+exit:
+ if (ret) {
+ if (registered)
+ platform_driver_unregister(&pci_console_driver);
+
+ pci_console_nexus_de_init_resources(pdev);
+
+ if (pci_cons_nexus != NULL)
+ devm_kfree(dev, pci_cons_nexus);
+ }
+
+ return ret;
+}
+
+static void pci_console_nexus_shutdown(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+}
+
+/*
+ * Linux driver callback.
+ */
+static int pci_console_nexus_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console_nexus *pci_cons_nexus = platform_get_drvdata(pdev);
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ WARN_ON(!pci_cons_nexus);
+
+ of_platform_depopulate(dev);
+
+ platform_driver_unregister(&pci_console_driver);
+
+ pci_console_nexus_de_init_resources(pdev);
+
+ devm_kfree(dev, pci_cons_nexus);
+
+ return 0;
+}
+
+static const struct of_device_id pci_console_nexus_of_match[] = {
+ { .compatible = "marvell,pci-console-nexus", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pci_console_nexus_of_match);
+
+static const struct platform_device_id pci_console_nexus_pdev_match[] = {
+ { .name = NEXUS_DRV_NAME, },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, pci_console_nexus_pdev_match);
+
+static struct platform_driver pci_console_nexus_driver = {
+ .driver = {
+ .name = NEXUS_DRV_NAME,
+ .of_match_table = pci_console_nexus_of_match,
+ },
+ .probe = pci_console_nexus_probe,
+ .remove = pci_console_nexus_remove,
+ .shutdown = pci_console_nexus_shutdown,
+ .id_table = pci_console_nexus_pdev_match,
+};
+
+module_platform_driver(pci_console_nexus_driver);
+
+MODULE_DESCRIPTION("OcteonTX PCI Console Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" NEXUS_DRV_NAME);
+
+/* =========================== pci console driver =========================== */
+
+/*
+ * Low-level initialization function for octeontx console state.
+ */
+static int octeontx_console_init(struct device *dev,
+ struct pci_console *pci_cons, int index,
+ u64 cons_addr, u64 cons_size)
+{
+ int ret;
+ u32 cons_num;
+ struct octeontx_pcie_console __iomem *ring_descr;
+
+ /* see notes in structure declaration regarding these elements */
+ BUILD_BUG_ON(offsetof(struct octeontx_pcie_console,
+ host_console_connected) & 0x7);
+ BUILD_BUG_ON((offsetof(struct octeontx_pcie_console,
+ host_console_connected) + sizeof(u32)) !=
+ offsetof(struct octeontx_pcie_console, output_read_index));
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ ret = -ENODEV;
+ cons_num = index;
+
+ if (!cons_addr) {
+ dev_err(dev, "Missing console base address\n");
+ goto exit;
+ }
+
+ /* map the ring descriptor from the nexus */
+ /* TODO: verify we can use WC */
+ pci_cons->ring_descr = ioremap_wc(cons_addr, cons_size);
+ if (!pci_cons->ring_descr) {
+ dev_err(dev,
+ "Unable to remap console %d base address\n",
+ cons_num);
+ goto exit;
+ }
+
+ ring_descr = pci_cons->ring_descr;
+
+ /* Here, we verify/use existing configuration
+ * (i.e. from U-Boot).
+ *
+ * If this changes and the console is initialized here,
+ * then the pcie_lock must be taken/released around
+ * the init code.
+ */
+
+ if (readq(&ring_descr->magic) !=
+ cpu_to_le64(OCTEONTX_PCIE_CONSOLE_MAGIC)) {
+ dev_err(dev, "Invalid console %d signature\n",
+ cons_num);
+ goto exit;
+ }
+
+ /* Implementation note: using 'u32' will catch negative vals */
+ if (((u32)le32_to_cpu(readl(&ring_descr->input_read_index)) >=
+ le32_to_cpu(readl(&ring_descr->input_buf_size))) ||
+ ((u32)le32_to_cpu(readl(&ring_descr->input_write_index)) >=
+ le32_to_cpu(readl(&ring_descr->input_buf_size))) ||
+ ((u32)le32_to_cpu(readl(&ring_descr->output_read_index)) >=
+ le32_to_cpu(readl(&ring_descr->output_buf_size))) ||
+ ((u32)le32_to_cpu(readl(&ring_descr->output_write_index)) >=
+ le32_to_cpu(readl(&ring_descr->output_buf_size))) ||
+ !readl(&ring_descr->input_buf_size) ||
+ !readl(&ring_descr->output_buf_size) ||
+ !readq(&ring_descr->input_base_addr) ||
+ !readq(&ring_descr->output_base_addr)) {
+ dev_err(dev, "Invalid console %d ring configuration\n",
+ cons_num);
+ goto exit;
+ }
+
+ /* map the input buffer */
+ pci_cons->input_ring = ioremap_wc(readq(&ring_descr->input_base_addr),
+ readl(&ring_descr->input_buf_size));
+
+ /* map the output buffer */
+ pci_cons->output_ring = ioremap_wc(readq(&ring_descr->output_base_addr),
+ readl(&ring_descr->output_buf_size));
+
+ if (!pci_cons->input_ring || !pci_cons->output_ring) {
+ dev_err(dev,
+ "Unable to remap console %d memory ring[s]\n",
+ cons_num);
+ goto exit;
+ }
+
+ writel(cpu_to_le32(cons_num), &ring_descr->host.cons_idx);
+ spin_lock_init(&pci_cons->excl_lock[cons_num]);
+
+ ret = 0;
+
+exit:
+ return ret ? -ENODEV : 0;
+}
+
+/*
+ * Low-level de-initialization function for octeontx console state.
+ */
+static int octeontx_console_de_init(struct device *dev,
+ struct pci_console *pci_cons, int index,
+ u64 cons_addr, u64 cons_size)
+{
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ if (pci_cons->input_ring) {
+ iounmap(pci_cons->input_ring);
+ pci_cons->input_ring = NULL;
+ }
+
+ if (pci_cons->output_ring) {
+ iounmap(pci_cons->output_ring);
+ pci_cons->output_ring = NULL;
+ }
+
+ if (pci_cons->ring_descr) {
+ iounmap(pci_cons->ring_descr);
+ pci_cons->ring_descr = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Used to acquire or release a low-level octeontx console.
+ */
+static bool
+octeontx_console_acquire(struct octeontx_pcie_console_nexus __iomem *nexus_desc,
+ int index, bool acquire, u64 *old, u64 *new)
+{
+ bool b_ok;
+ u32 wait_usecs;
+ u64 old_use_mask, new_use_mask;
+ int cons_num;
+
+ b_ok = false;
+
+ if (!nexus_desc)
+ return b_ok;
+
+ cons_num = index;
+ wait_usecs = 0;
+
+#define CONSOLE_NEXUS_IN_USE_WAIT_USECS 1
+#define CONSOLE_NEXUS_IN_USE_TIMEOUT_USECS 100
+
+ do {
+ old_use_mask = le32_to_cpu(readl(&nexus_desc->in_use)) |
+ (u64)le32_to_cpu(readl(
+ &nexus_desc->exclusive)) << 32;
+
+ /* set (or clear) both 'in-use' and 'exclusive' bits */
+ new_use_mask = ((1ULL << cons_num) |
+ ((1ULL << cons_num) << 32));
+
+ if (acquire) {
+ /* Check if console has already been acquired */
+ if (old_use_mask & (1ULL << cons_num))
+ break;
+ new_use_mask = old_use_mask | new_use_mask;
+ } else {
+ new_use_mask = old_use_mask & ~new_use_mask;
+ }
+
+ b_ok = (__atomic_compare_exchange_n((u64 *)&nexus_desc->in_use,
+ &old_use_mask, new_use_mask,
+ false, __ATOMIC_SEQ_CST,
+ __ATOMIC_SEQ_CST));
+ if (b_ok)
+ break;
+
+ udelay(CONSOLE_NEXUS_IN_USE_WAIT_USECS);
+ wait_usecs += CONSOLE_NEXUS_IN_USE_WAIT_USECS;
+
+ } while (wait_usecs < CONSOLE_NEXUS_IN_USE_TIMEOUT_USECS);
+
+ if (old)
+ *old = old_use_mask;
+
+ if (new)
+ *new = new_use_mask;
+
+ return b_ok;
+}
+
+/*
+ * Clears pending data [bytes] from the low-level octeontx console output
+ * buffer if the host console is not connected.
+ * If the host console IS connected, an error is returned.
+ *
+ * @param console console to clear output from
+ * @param bytes_to_clear Number of bytes to free up
+ *
+ * @return 0 for success, -1 on error, or a positive value
+ * If the size of pending data is less than 'bytes_to_clear',
+ * the return value equals the count of pending data.
+ */
+int octeontx_console_output_truncate(struct octeontx_pcie_console *console,
+ size_t bytes_to_clear)
+{
+ u64 old_val;
+ u64 new_val;
+ size_t bytes_avail;
+ const u32 out_buf_size = le32_to_cpu(readl(&console->output_buf_size));
+ u32 out_wr_idx, out_rd_idx;
+ int ret;
+
+ if (le32_to_cpu(readl(&console->host_console_connected)))
+ return -1;
+
+ out_wr_idx = le32_to_cpu(readl(&console->output_write_index));
+ out_rd_idx = le32_to_cpu(readl(&console->output_read_index));
+
+ old_val = cpu_to_le64((u64)out_rd_idx << 32);
+ bytes_avail = buffer_pending_bytes(out_buf_size, out_wr_idx,
+ out_rd_idx);
+ if (bytes_avail < 0)
+ return bytes_avail;
+ /* Not enough space */
+ if (bytes_to_clear > bytes_avail)
+ return bytes_avail;
+
+ out_rd_idx = (out_rd_idx + bytes_to_clear) % out_buf_size;
+ new_val = cpu_to_le64((u64)out_rd_idx << 32);
+
+ /*
+ * We need to use an atomic operation here in case the host
+ * console should connect. This guarantees that if the host
+ * connects that it will always see a consistent state. Normally
+ * only the host can modify the read pointer. This assures us
+ * that the read pointer will only be modified if the host
+ * is disconnected.
+ */
+ ret = __atomic_compare_exchange_n
+ ((u64 *)(&console->host_console_connected),
+ &old_val, new_val, 0,
+ __ATOMIC_RELAXED, __ATOMIC_RELAXED);
+
+ return ret ? 0 : -1;
+}
+
+/*
+ * Low-level octeontx console write function.
+ *
+ * NOTE: this may NOT sleep, as it is called by the TTY 'write()' API.
+ *
+ */
+static unsigned
+octeontx_console_write(struct device *dev, const char *buf, unsigned int len,
+ struct octeontx_pcie_console __iomem *ring_descr,
+ u8 __iomem *output_ring, spinlock_t *excl_lock)
+{
+ const u8 *src;
+ int srclen, avail, wr_len, written;
+ unsigned int wait_usecs;
+ u32 sz, rd_idx, wr_idx;
+
+ spin_lock(excl_lock);
+
+ sz = le32_to_cpu(readl(&ring_descr->output_buf_size));
+ src = buf;
+ srclen = len;
+ written = 0;
+ wait_usecs = 0;
+
+ wr_idx = le32_to_cpu(readl(&ring_descr->output_write_index));
+
+ while (srclen > 0) {
+ rd_idx = le32_to_cpu(readl(&ring_descr->output_read_index));
+ avail = buffer_free_bytes(sz, wr_idx, rd_idx);
+
+ if (avail > 0) {
+ /* reset host wait time */
+ wait_usecs = 0;
+
+ wr_len = min(avail, srclen);
+ srclen -= wr_len;
+ if (wr_idx + wr_len > sz) {
+ memcpy_toio(output_ring + wr_idx, src,
+ (sz - wr_idx));
+ wr_len -= (sz - wr_idx);
+ src += (sz - wr_idx);
+ wr_idx = 0;
+ }
+ if (wr_len)
+ memcpy_toio(output_ring + wr_idx, src, wr_len);
+ src += wr_len;
+ written += wr_len;
+ wr_idx = (wr_idx + wr_len) % sz;
+
+ /* The write index is used by another process
+ * (remote PCI) to indicate the presence of [new] data
+ * in the ring buffer.
+ * Use a barrier here to ensure that all such data
+ * has been committed to memory prior to updating
+ * the write index in the descriptor.
+ */
+ wmb();
+ writel(cpu_to_le32(wr_idx),
+ &ring_descr->output_write_index);
+ } else if (!avail) {
+ /* Try to free space in output buffer (i.e. truncate) */
+ wr_len = octeontx_console_output_truncate(ring_descr,
+ srclen);
+
+ if (wr_len < 0) {
+ if (wait_usecs >=
+ PCI_CONS_HOST_WAIT_TIMEOUT_USECS) {
+ dev_err_once(dev,
+ "Timeout awaiting host\n");
+ break;
+ }
+ /* We cannot sleep, we have acquired the lock */
+ udelay(PCI_CONS_HOST_WAIT_LOOP_USECS);
+ wait_usecs += PCI_CONS_HOST_WAIT_LOOP_USECS;
+ } else if (wr_len > 0) {
+ /* Truncate what we can */
+ wr_len = octeontx_console_output_truncate(
+ ring_descr, wr_len);
+ if (wr_len != 0) {
+ dev_err(dev,
+ "output buffer truncate error\n");
+ break;
+ }
+ }
+ } else {
+ dev_err_once(dev, "output buffer error\n");
+ break;
+ }
+ }
+
+ spin_unlock(excl_lock);
+
+ return written;
+}
+
+/*
+ * Linux console callback.
+ */
+static void pci_console_dev_write(struct console *cons, const char *buf,
+ unsigned int len)
+{
+ struct pci_console *pci_cons = cons->data;
+ struct device *dev = pci_cons->device;
+ struct octeontx_pcie_console __iomem *ring_descr;
+ u8 __iomem *output_ring;
+ u32 cons_idx;
+
+ ring_descr = pci_cons->ring_descr;
+ output_ring = pci_cons->output_ring;
+
+ cons_idx = le32_to_cpu(readl(&ring_descr->host.cons_idx));
+
+ octeontx_console_write(dev, buf, len, ring_descr, output_ring,
+ &pci_cons->excl_lock[cons_idx]);
+}
+
+/*
+ * Linux console callback.
+ */
+static struct tty_driver *pci_console_dev_device(struct console *cons,
+ int *index)
+{
+ struct pci_console *pci_cons = cons->data;
+ struct device *dev = pci_cons->device;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ *index = pci_cons->cons.index;
+
+ dbgmsg(dev, "return index: %d, tty driver: %p\n", *index,
+ pci_cons->tty.drv);
+
+ return pci_cons->tty.drv;
+}
+
+/*
+ * Linux console initialization callback.
+ *
+ * Create and register a TTY driver to be used with this console.
+ *
+ */
+int pci_console_dev_setup(struct console *cons, char *arg)
+{
+ struct pci_console *pci_cons = cons->data;
+ struct device *dev = pci_cons->device;
+ struct tty_driver *ttydrv;
+ int ret;
+
+ dbgmsg(dev, "%s: entry, args '%s'\n", __func__, arg);
+
+ ret = 0;
+ ttydrv = NULL;
+
+ /* Create/register our TTY driver */
+ if (!pci_cons->tty.drv) {
+ ret = -ENODEV;
+
+ ttydrv = tty_alloc_driver(1 /*i.e. a single line */,
+ TTY_DRIVER_REAL_RAW);
+ if (!ttydrv) {
+ dev_err(dev, "Cannot allocate tty driver\n");
+ goto exit;
+ }
+
+ ttydrv->driver_name = DRV_NAME;
+ ttydrv->name = "ttyPCI";
+ ttydrv->type = TTY_DRIVER_TYPE_SERIAL;
+ ttydrv->subtype = SERIAL_TYPE_NORMAL;
+ ttydrv->major = TTY_DRV_MAJOR_VER;
+ ttydrv->minor_start = TTY_DRV_MINOR_VER_START;
+ ttydrv->init_termios = tty_std_termios;
+ ttydrv->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ ttydrv->driver_state = pci_cons;
+ tty_set_operations(ttydrv, &pci_console_dev_tty_ops);
+ tty_port_init(&pci_cons->tty.port);
+ tty_port_link_device(&pci_cons->tty.port, ttydrv,
+ 0 /* i.e. the first, and only, port */);
+ ret = tty_register_driver(ttydrv);
+ if (ret) {
+ dev_err(dev, "Error registering TTY %s\n",
+ ttydrv->name);
+ goto exit;
+ }
+
+ pci_cons->tty.drv = ttydrv;
+
+ ret = 0;
+ }
+
+exit:
+ /* If error initializing tty driver, release it */
+ if (ret && ttydrv)
+ put_tty_driver(ttydrv);
+
+ return ret ? -ENODEV : 0;
+}
+
+/*
+ * Main initialization function for pci_console device instance.
+ *
+ * returns:
+ * 0 if no error
+ * -ENODEV if error occurred initializing device
+ * ENODEV if device should not be used (not an error per se)
+ */
+static int pci_console_init(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console *pci_cons = platform_get_drvdata(pdev);
+ struct device_node *of_node, *of_parent;
+ int ret, cons_num, len;
+ u64 cons_addr, cons_size, new_use_mask, old_use_mask;
+ u64 of_parent_sz, of_parent_xbase;
+ u32 cons_index;
+ const __be32 *of_base, *of_parent_base;
+ struct octeontx_pcie_console_nexus __iomem *nexus_desc;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ ret = -ENODEV;
+
+ nexus_desc = NULL;
+
+ of_node = pdev->dev.of_node;
+ if (!of_node) {
+ dev_err(dev, "Missing devicetree configuration\n");
+ goto exit;
+ }
+
+ /* retrieve our console index */
+ cons_num = -1;
+ if (!of_property_read_u32(of_node, "reg", &cons_index))
+ cons_num = cons_index;
+ if ((cons_num < 0) ||
+ (cons_num >= OCTEONTX_PCIE_MAX_CONSOLES)) {
+ dev_err(dev, "Invalid configuration console index\n");
+ goto exit;
+ }
+
+ if (!(max_cons_mask & BIT(cons_num))) {
+ dev_info(dev, "Ignoring excluded console %d\n",
+ cons_num);
+ ret = ENODEV;
+ goto exit;
+ }
+
+ /* Retrieve console base address and length from device tree */
+ cons_addr = OF_BAD_ADDR;
+ of_base = of_get_address(of_node, 0, &cons_size, 0);
+ if (of_base)
+ cons_addr = of_translate_address(of_node, of_base);
+ if (cons_addr == OF_BAD_ADDR) {
+ dev_err(dev, "Invalid configuration base address\n");
+ goto exit;
+ }
+
+ dbgmsg(dev, "Located console %d, address %#llx, size: %#llx\n",
+ cons_num, cons_addr, cons_size);
+
+ /* ======================================================= */
+ /* Note: we must [eventually] call 'of_node_put' on parent */
+ of_parent = of_get_parent(of_node);
+ if (!of_parent) {
+ dev_err(dev,
+ "Missing devicetree parent configuration\n");
+ goto exit;
+ }
+
+ /* retrieve (and map) nexus pointer from parent node */
+ of_parent_base = of_get_address(of_parent, 0, &of_parent_sz, 0);
+ if (of_parent_base) {
+ of_parent_xbase = of_translate_address(of_parent,
+ of_parent_base);
+ /* TODO: verify we can use WC */
+ if (of_parent_xbase != OF_BAD_ADDR) {
+ dbgmsg(dev, "of_parent_xbase: %#llx\n",
+ of_parent_xbase);
+ pci_cons->nexus_desc = nexus_desc =
+ ioremap_wc(of_parent_xbase,
+ of_parent_sz);
+ }
+ }
+
+ /* Release reference on parent */
+ of_node_put(of_parent);
+ /* ======================================================= */
+
+ if (!nexus_desc) {
+ dev_err(dev,
+ "Invalid parent configuration base address\n");
+ goto exit;
+ }
+
+ /* Verify/use existing configuration (i.e. from U-Boot) */
+
+ if (readq(&nexus_desc->magic) !=
+ cpu_to_le64(OCTEONTX_PCIE_CONSOLE_NEXUS_MAGIC)) {
+ dev_err(dev, "Invalid nexus signature\n");
+ goto exit;
+ }
+
+ if (cons_addr !=
+ le64_to_cpu(readq(&nexus_desc->console_addr[cons_num]))) {
+ dev_err(dev,
+ "Console %d base address mismatch %#llx/%#llx\n"
+ , cons_num, cons_addr,
+ le64_to_cpu(readq(&nexus_desc->console_addr[cons_num]))
+ );
+ goto exit;
+ }
+
+ if (le32_to_cpu(readl(&nexus_desc->in_use)) & (1 << cons_num)) {
+ dev_err(dev, "Console %d already in-use\n", cons_num);
+ goto exit;
+ }
+
+ if (octeontx_console_init(dev, pci_cons, cons_num, cons_addr,
+ cons_size)) {
+ dev_err(dev,
+ "Error initializing octeontx pci console\n");
+ goto exit;
+ }
+
+ dev_info(dev,
+ "Initialized console %d, address %#llx, size: %#llx\n",
+ cons_num, cons_addr, cons_size);
+
+ old_use_mask = new_use_mask = 0;
+
+ if (!octeontx_console_acquire(pci_cons->nexus_desc, cons_num,
+ true, &old_use_mask,
+ &new_use_mask)) {
+ dev_err(dev,
+ "Console acquisition failed, old: %#llx, new: %#llx\n",
+ old_use_mask, new_use_mask);
+ goto exit;
+ }
+
+ pci_cons->octeontx_console_acquired = true;
+
+ dbgmsg(dev, "Console acquisition - old: %#llx, new: %#llx\n",
+ old_use_mask, new_use_mask);
+
+ /* initialize linux console state */
+ len = sizeof(pci_cons->cons.name);
+ strncpy(pci_cons->cons.name, "pci", len - 1);
+ pci_cons->cons.name[len - 1] = 0;
+ pci_cons->device = dev;
+ pci_cons->cons.write = pci_console_dev_write;
+ pci_cons->cons.device = pci_console_dev_device;
+ pci_cons->cons.setup = pci_console_dev_setup;
+ pci_cons->cons.data = pci_cons;
+ pci_cons->cons.index = cons_num;
+ pci_cons->cons.flags = CON_PRINTBUFFER;
+
+ register_console(&pci_cons->cons);
+
+ ret = 0;
+
+exit:
+ return ret;
+}
+
+/*
+ * Main de-initialization function for pci_console device instance.
+ */
+static int pci_console_de_init(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console *pci_cons = platform_get_drvdata(pdev);
+ u64 new_use_mask, old_use_mask;
+ int cons_num;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ cons_num = pci_cons->cons.index;
+
+ if (pci_cons->tty.drv) {
+ tty_unregister_driver(pci_cons->tty.drv);
+ put_tty_driver(pci_cons->tty.drv);
+ }
+
+ if (pci_cons->cons.flags & CON_ENABLED) {
+ if (unregister_console(&pci_cons->cons))
+ dev_err(dev,
+ "Error unregistering pci console %d\n",
+ cons_num);
+ }
+
+ octeontx_console_de_init(dev, pci_cons, cons_num, 0, 0);
+
+ if (pci_cons->octeontx_console_acquired) {
+ old_use_mask = new_use_mask = 0;
+ if (!octeontx_console_acquire(pci_cons->nexus_desc,
+ cons_num, false, &old_use_mask,
+ &new_use_mask))
+ dev_err(dev,
+ "Console release failed, old: %#llx, new: %#llx\n",
+ old_use_mask, new_use_mask);
+ else
+ dbgmsg(dev,
+ "Console release - old: %#llx, new: %#llx\n",
+ old_use_mask, new_use_mask);
+
+ iounmap(pci_cons->nexus_desc);
+ pci_cons->nexus_desc = NULL;
+ }
+
+ return 0;
+}
+
+static int pci_console_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console *pci_cons;
+ int ret;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ pci_cons = NULL;
+
+ ret = -ENODEV;
+
+ /* allocate device structure */
+ pci_cons = devm_kzalloc(dev, sizeof(*pci_cons), GFP_KERNEL);
+
+ if (pci_cons == NULL) {
+ ret = -ENOMEM;
+ dev_err(dev, "Unable to allocate drv context.\n");
+ goto exit;
+ }
+
+ platform_set_drvdata(pdev, pci_cons);
+
+ ret = pci_console_init(pdev);
+
+ /* a negative value indicates an error */
+ if (ret < 0)
+ dev_err(dev, "Error initializing pci console\n");
+
+exit:
+ if (ret) {
+ pci_console_de_init(pdev);
+
+ if (pci_cons != NULL)
+ devm_kfree(dev, pci_cons);
+ }
+
+ return ret ? -ENODEV : 0;
+}
+
+static void pci_console_shutdown(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+}
+
+static int pci_console_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pci_console *pci_cons = platform_get_drvdata(pdev);
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ pci_console_de_init(pdev);
+
+ devm_kfree(dev, pci_cons);
+
+ return 0;
+}
+
+static const struct of_device_id pci_console_of_match[] = {
+ { .compatible = "marvell,pci-console", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pci_console_of_match);
+
+static const struct platform_device_id pci_console_pdev_match[] = {
+ { .name = DRV_NAME, },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, pci_console_pdev_match);
+
+static struct platform_driver pci_console_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = pci_console_of_match,
+ },
+ .probe = pci_console_probe,
+ .remove = pci_console_remove,
+ .shutdown = pci_console_shutdown,
+ .id_table = pci_console_pdev_match,
+};
+
+/* ========================= pci console TTY driver ========================= */
+
+/*
+ * Linux TTY driver timer callback (used to poll for data).
+ */
+void pci_console_dev_tty_poll(struct timer_list *timer)
+{
+#define MAX_BUFFERED_INP_DATA 0x100
+ struct pci_console *pci_cons = from_timer(pci_cons, timer, tty.poll_timer);
+ struct octeontx_pcie_console __iomem *ring_descr;
+ u8 __iomem *input_ring;
+ u8 buf[MAX_BUFFERED_INP_DATA];
+ int cnt;
+ u32 sz, rd_idx, wr_idx, avail;
+
+ BUILD_BUG_ON(PCI_CONS_TTY_POLL_INTERVAL_JIFFIES > HZ);
+ if (!(pci_cons->tty.stats.poll_count++ %
+ (HZ / PCI_CONS_TTY_POLL_INTERVAL_JIFFIES))) {
+ dbgmsg(pci_cons->device,
+ "timer poll count: %u, dropped: %u, pushed: %u\n",
+ pci_cons->tty.stats.poll_count,
+ pci_cons->tty.stats.dropped_count,
+ pci_cons->tty.stats.pushed_count);
+ }
+
+ ring_descr = pci_cons->ring_descr;
+ input_ring = pci_cons->input_ring;
+ sz = le32_to_cpu(readl(&ring_descr->input_buf_size));
+ rd_idx = le32_to_cpu(readl(&ring_descr->input_read_index));
+ wr_idx = le32_to_cpu(readl(&ring_descr->input_write_index));
+ avail = buffer_pending_bytes(sz, wr_idx, rd_idx);
+
+ while ((s32)avail > 0) {
+
+ if (rd_idx > wr_idx)
+ cnt = min(avail, sz - rd_idx);
+ else
+ cnt = min(avail, wr_idx - rd_idx);
+
+ cnt = min(cnt, MAX_BUFFERED_INP_DATA);
+ memcpy_fromio(buf, &input_ring[rd_idx], cnt);
+ cnt = tty_insert_flip_string(&pci_cons->tty.port, buf, cnt);
+ if (!cnt) {
+ pci_cons->tty.stats.dropped_count += cnt;
+ break;
+ }
+
+ rd_idx = (rd_idx + cnt) % sz;
+ avail -= cnt;
+
+ pci_cons->tty.stats.pushed_count += cnt;
+
+ tty_flip_buffer_push(&pci_cons->tty.port);
+ }
+ /* The read index is used by another process (remote PCI) to
+ * indicate which data have been consumed from the ring buffer.
+ * Use a barrier here to ensure that all such data
+ * has been copied from the ring buffer prior to updating the
+ * read index in the descriptor.
+ */
+ mb();
+ writel(cpu_to_le32(rd_idx), &ring_descr->input_read_index);
+
+ mod_timer(&pci_cons->tty.poll_timer,
+ jiffies + PCI_CONS_TTY_POLL_INTERVAL_JIFFIES);
+}
+
+/*
+ * Linux TTY driver callback.
+ */
+static int pci_console_dev_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct pci_console *pci_cons = tty->driver->driver_state;
+ struct device *dev = pci_cons->device;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ if (!pci_cons->tty.open_count++) {
+ dbgmsg(dev, "Scheduling timer...\n");
+ timer_setup(&pci_cons->tty.poll_timer,
+ pci_console_dev_tty_poll, 0);
+ mod_timer(&pci_cons->tty.poll_timer,
+ jiffies + PCI_CONS_TTY_POLL_INTERVAL_JIFFIES);
+ }
+
+ return 0;
+}
+
+/*
+ * Linux TTY driver callback.
+ */
+static void pci_console_dev_tty_close(struct tty_struct *tty,
+ struct file *filp)
+{
+ struct pci_console *pci_cons = tty->driver->driver_state;
+ struct device *dev = pci_cons->device;
+
+ dbgmsg(dev, "%s: entry\n", __func__);
+
+ if (--pci_cons->tty.open_count == 0) {
+ dbgmsg(dev, "Deleting timer...\n");
+ del_timer(&pci_cons->tty.poll_timer);
+ }
+}
+
+/*
+ * Linux TTY driver callback.
+ */
+static int pci_console_dev_tty_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ struct pci_console *pci_cons = tty->driver->driver_state;
+ struct device *dev = pci_cons->device;
+ struct octeontx_pcie_console __iomem *ring_descr;
+ u8 __iomem *output_ring;
+ u32 cons_idx;
+
+ ring_descr = pci_cons->ring_descr;
+ output_ring = pci_cons->output_ring;
+
+ cons_idx = le32_to_cpu(readl(&ring_descr->host.cons_idx));
+
+ return octeontx_console_write(dev, buf, count, ring_descr,
+ output_ring,
+ &pci_cons->excl_lock[cons_idx]);
+}
+
+static int pci_console_dev_tty_write_room(struct tty_struct *tty)
+{
+ struct pci_console *pci_cons = tty->driver->driver_state;
+
+ /* Assume maximum space is available; write function will wait for
+ * available room, if necessary.
+ */
+ return pci_cons->ring_descr->output_buf_size - 1;
+}
+
+static int pci_console_dev_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct pci_console *pci_cons = tty->driver->driver_state;
+
+ (void)pci_cons;
+
+ /* We do not buffer any data - zero chars in buffer */
+ return 0;
+}
+
+static void pci_console_dev_tty_send_xchar(struct tty_struct *tty, char ch)
+{
+ pci_console_dev_tty_write(tty, (const u8 *)&ch, sizeof(ch));
+}
+