From 934e91d14d093bf0b3f931e0964fc0842ba06657 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 18:24:48 +0800 Subject: [PATCH 01/41] dt-bindings: mailbox: Document Hisilicon mailbox driver Document the new compatible for Hisilicon mailbox driver. Signed-off-by: Leo Yan --- .../bindings/mailbox/hisi-mailbox.txt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Documentation/devicetree/bindings/mailbox/hisi-mailbox.txt diff --git a/Documentation/devicetree/bindings/mailbox/hisi-mailbox.txt b/Documentation/devicetree/bindings/mailbox/hisi-mailbox.txt new file mode 100644 index 00000000000000..898823d97ab3cd --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/hisi-mailbox.txt @@ -0,0 +1,57 @@ +Hisilicon Mailbox Driver +======================== + +The Hisilicon mailbox supports up to 32 channels. Each channel +is unidirectional with a maximum message size of 8 words. I/O is +performed using register access (there is no DMA) and the cell +raises an interrupt when messages are received. + +Mailbox Device Node: +==================== + +Required properties: +-------------------- +- compatible: Shall be "hisilicon,hi6220-mbox" +- reg: Contains the mailbox register address range (base + address and length); the first item is for IPC + registers, the second item is shared buffer for + slots. +- #mbox-cells Common mailbox binding property to identify the number + of cells required for the mailbox specifier. Should be 1. +- interrupts: Contains the interrupt information for the mailbox + device. The format is dependent on which interrupt + controller the SoCs use. + +Example: +-------- + + mailbox: mailbox@F7510000 { + #mbox-cells = <1>; + compatible = "hisilicon,hi6220-mbox"; + reg = <0x0 0xF7510000 0x0 0x1000>, /* IPC_S */ + <0x0 0x06DFF800 0x0 0x0800>; /* Mailbox */ + interrupt-parent = <&gic>; + interrupts = <0 94 4>; + }; + + +Mailbox client +=============== + +"mboxes" and the optional "mbox-names" (please see +Documentation/devicetree/bindings/mailbox/mailbox.txt for details). Each value +of the mboxes property should contain a phandle to the mailbox controller +device node and second argument is the channel index. It must be 0 (hardware +support only one channel). The equivalent "mbox-names" property value can be +used to give a name to the communication channel to be used by the client user. + +Example: +-------- + + stub_clock: stub_clock { + compatible = "hisilicon,hi6220-stub-clk"; + hisilicon,hi6220-clk-sram = <&sram>; + #clock-cells = <1>; + mbox-names = "mbox-tx"; + mboxes = <&mailbox 1>; + }; From 264d6093a87a96a36cd66240469ec6db561e449f Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 17:32:32 +0800 Subject: [PATCH 02/41] mailbox: Hisilicon: add mailbox driver Add Hisilicon mailbox's common driver, it registers mailbox channels into framework; it also invokes low level callback functions for register's related operations. Enhance rx channel's message queue, which is based on the code in drivers/mailbox/omap-mailbox.c. Enable Hi6220 mailbox driver as the first platform to use this framework. Hi6220's mailbox communicates with MCU; for sending data, it can support two methods for low level implementation: one is to use interrupt as acknowledge, another is automatic mode which without any acknowledge. These two methods have been supported in the driver; for receiving data, it will depend on the interrupt to notify the channel has incoming message. Now mailbox driver is used to send message to MCU to control dynamic voltage and frequency scaling for CPU, GPU and DDR. Signed-off-by: Leo Yan --- drivers/mailbox/Kconfig | 3 + drivers/mailbox/Makefile | 2 + drivers/mailbox/hisilicon/Kconfig | 13 + drivers/mailbox/hisilicon/Makefile | 2 + drivers/mailbox/hisilicon/common.c | 282 +++++++++++++++++++ drivers/mailbox/hisilicon/common.h | 114 ++++++++ drivers/mailbox/hisilicon/hi6220-mailbox.c | 305 +++++++++++++++++++++ 7 files changed, 721 insertions(+) create mode 100644 drivers/mailbox/hisilicon/Kconfig create mode 100644 drivers/mailbox/hisilicon/Makefile create mode 100644 drivers/mailbox/hisilicon/common.c create mode 100644 drivers/mailbox/hisilicon/common.h create mode 100644 drivers/mailbox/hisilicon/hi6220-mailbox.c diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 84b0a2d74d60b7..202113346bec9a 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -60,4 +60,7 @@ config ALTERA_MBOX An implementation of the Altera Mailbox soft core. It is used to send message between processors. Say Y here if you want to use the Altera mailbox support. + +source "drivers/mailbox/hisilicon/Kconfig" + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index b18201e97e2988..15c927c5c457a8 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -11,3 +11,5 @@ obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o obj-$(CONFIG_PCC) += pcc.o obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o + +obj-$(CONFIG_HISI_MBOX) += hisilicon/ diff --git a/drivers/mailbox/hisilicon/Kconfig b/drivers/mailbox/hisilicon/Kconfig new file mode 100644 index 00000000000000..87548a90af3a4f --- /dev/null +++ b/drivers/mailbox/hisilicon/Kconfig @@ -0,0 +1,13 @@ +config HISI_MBOX + bool "Hisilicon's Mailbox" + depends on ARCH_HISI || OF + help + Support for mailbox drivers on Hisilicon series of SoCs. + +config HI6220_MBOX + tristate "Hi6220 Mailbox Controller" + depends on HISI_MBOX + help + An implementation of the hi6220 mailbox. It is used to send message + between application processors and MCU. Say Y here if you want to build + the Hi6220 mailbox controller driver. diff --git a/drivers/mailbox/hisilicon/Makefile b/drivers/mailbox/hisilicon/Makefile new file mode 100644 index 00000000000000..59135b0b41d9b4 --- /dev/null +++ b/drivers/mailbox/hisilicon/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_HISI_MBOX) += common.o +obj-$(CONFIG_HI6220_MBOX) += hi6220-mailbox.o diff --git a/drivers/mailbox/hisilicon/common.c b/drivers/mailbox/hisilicon/common.c new file mode 100644 index 00000000000000..c3c8e49752671a --- /dev/null +++ b/drivers/mailbox/hisilicon/common.c @@ -0,0 +1,282 @@ +/* + * Hisilicon mailbox common driver + * + * This is skeleton driver for Hisilicon's mailbox, it registers mailbox + * channels into framework; it also need invoke low level's callback + * functions for low level operations. RX channel's message queue is + * based on the code written in drivers/mailbox/omap-mailbox.c. + + * Copyright (c) 2015 Hisilicon Limited. + * Copyright (c) 2015 Linaro Limited. + * + * Author: Leo Yan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#include "common.h" + +#define MBOX_MSG_LEN (32) +#define MBOX_MSG_NUM (16) +#define MBOX_MSG_FIFO_SIZE (MBOX_MSG_LEN * MBOX_MSG_NUM) + +static bool hisi_mbox_last_tx_done(struct mbox_chan *chan) +{ + struct hisi_mbox_chan *mchan = chan->con_priv; + struct hisi_mbox_hw *mbox_hw = mchan->mbox_hw; + + /* Only set idle state for polling mode */ + BUG_ON(mbox_hw->tx_irq_mode); + + return mbox_hw->ops->tx_is_done(mchan); +} + +static int hisi_mbox_send_data(struct mbox_chan *chan, void *msg) +{ + struct hisi_mbox_chan *mchan = chan->con_priv; + struct hisi_mbox_hw *mbox_hw = mchan->mbox_hw; + + return mbox_hw->ops->tx(mchan, msg, MBOX_MSG_LEN); +} + +static void hisi_mbox_rx_work(struct work_struct *work) +{ + struct hisi_mbox_queue *mq = + container_of(work, struct hisi_mbox_queue, work); + struct mbox_chan *chan = mq->chan; + struct hisi_mbox_chan *mchan = chan->con_priv; + struct hisi_mbox_hw *mbox_hw = mchan->mbox_hw; + + char msg[MBOX_MSG_LEN]; + int len; + + while (kfifo_len(&mq->fifo) >= sizeof(msg)) { + len = kfifo_out(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); + WARN_ON(len != sizeof(msg)); + + mbox_chan_received_data(chan, (void *)msg); + spin_lock_irq(&mq->lock); + if (mq->full) { + mq->full = false; + mbox_hw->ops->enable_irq(mchan); + } + spin_unlock_irq(&mq->lock); + } +} + +static void hisi_mbox_tx_interrupt(struct mbox_chan *chan) +{ + struct hisi_mbox_chan *mchan = chan->con_priv; + struct hisi_mbox_hw *mbox_hw = mchan->mbox_hw; + + mbox_hw->ops->clear_irq(mchan); + mbox_hw->ops->ack(mchan); + + mbox_chan_txdone(chan, 0); +} + +static void hisi_mbox_rx_interrupt(struct mbox_chan *chan) +{ + struct hisi_mbox_chan *mchan = chan->con_priv; + struct hisi_mbox_queue *mq = mchan->mq; + struct hisi_mbox_hw *mbox_hw = mchan->mbox_hw; + char msg[MBOX_MSG_LEN]; + int len; + + if (unlikely(kfifo_avail(&mq->fifo) < sizeof(msg))) { + mbox_hw->ops->disable_irq(mchan); + mq->full = true; + goto nomem; + } + + mbox_hw->ops->rx(mchan, msg, sizeof(msg)); + + len = kfifo_in(&mq->fifo, (unsigned char *)&msg, sizeof(msg)); + WARN_ON(len != sizeof(msg)); + + mbox_hw->ops->ack(mchan); +nomem: + schedule_work(&mq->work); +} + +static irqreturn_t hisi_mbox_interrupt(int irq, void *p) +{ + struct hisi_mbox *mbox = p; + struct hisi_mbox_hw *mbox_hw = mbox->hw; + struct hisi_mbox_chan *mchan; + struct mbox_chan *chan; + unsigned int state; + unsigned int intr_bit; + + state = mbox_hw->ops->get_pending_irq(mbox_hw); + if (!state) { + dev_warn(mbox_hw->dev, "%s: spurious interrupt\n", + __func__); + return IRQ_HANDLED; + } + + while (state) { + intr_bit = __ffs(state); + state &= (state - 1); + + chan = mbox->irq_map_chan[intr_bit]; + if (!chan) { + dev_warn(mbox_hw->dev, "%s: unexpected irq vector %d\n", + __func__, intr_bit); + continue; + } + + mchan = chan->con_priv; + if (mchan->dir == MBOX_TX) + hisi_mbox_tx_interrupt(chan); + else + hisi_mbox_rx_interrupt(chan); + } + + return IRQ_HANDLED; +} + +static struct hisi_mbox_queue *hisi_mbox_queue_alloc( + struct mbox_chan *chan, + void (*work)(struct work_struct *)) +{ + struct hisi_mbox_queue *mq; + + if (!work) + return NULL; + + mq = kzalloc(sizeof(struct hisi_mbox_queue), GFP_KERNEL); + if (!mq) + return NULL; + + spin_lock_init(&mq->lock); + + if (kfifo_alloc(&mq->fifo, MBOX_MSG_FIFO_SIZE, GFP_KERNEL)) + goto error; + + mq->chan = chan; + INIT_WORK(&mq->work, work); + return mq; + +error: + kfree(mq); + return NULL; +} + +static void hisi_mbox_queue_free(struct hisi_mbox_queue *mq) +{ + kfifo_free(&mq->fifo); + kfree(mq); +} + +static int hisi_mbox_startup(struct mbox_chan *chan) +{ + struct hisi_mbox_chan *mchan = chan->con_priv; + struct hisi_mbox_hw *mbox_hw = mchan->mbox_hw; + struct hisi_mbox *mbox = mchan->mbox_hw->parent; + + struct hisi_mbox_queue *mq; + unsigned int irq = mchan->local_irq; + + mq = hisi_mbox_queue_alloc(chan, hisi_mbox_rx_work); + if (!mq) + return -ENOMEM; + mchan->mq = mq; + + mbox->irq_map_chan[irq] = (void *)chan; + return mbox_hw->ops->startup(mchan); +} + +static void hisi_mbox_shutdown(struct mbox_chan *chan) +{ + struct hisi_mbox_chan *mchan = chan->con_priv; + struct hisi_mbox_hw *mbox_hw = mchan->mbox_hw; + struct hisi_mbox *mbox = mbox_hw->parent; + unsigned int irq = mchan->local_irq; + + mbox_hw->ops->shutdown(mchan); + + mbox->irq_map_chan[irq] = NULL; + flush_work(&mchan->mq->work); + hisi_mbox_queue_free(mchan->mq); +} + +static struct mbox_chan_ops hisi_mbox_chan_ops = { + .send_data = hisi_mbox_send_data, + .startup = hisi_mbox_startup, + .shutdown = hisi_mbox_shutdown, + .last_tx_done = hisi_mbox_last_tx_done, +}; + +int hisi_mbox_register(struct hisi_mbox_hw *mbox_hw) +{ + struct device *dev = mbox_hw->dev; + struct hisi_mbox *mbox; + int i, err; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + mbox->hw = mbox_hw; + mbox->chan = devm_kzalloc(dev, + mbox_hw->chan_num * sizeof(struct mbox_chan), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + err = devm_request_irq(dev, mbox_hw->irq, hisi_mbox_interrupt, 0, + dev_name(dev), mbox); + if (err) { + dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n", + err); + return -ENODEV; + } + + for (i = 0; i < mbox_hw->chan_num; i++) { + mbox->chan[i].con_priv = &mbox_hw->chan[i]; + mbox->irq_map_chan[i] = NULL; + } + + mbox->controller.dev = dev; + mbox->controller.chans = &mbox->chan[0]; + mbox->controller.num_chans = mbox_hw->chan_num; + mbox->controller.ops = &hisi_mbox_chan_ops; + + if (mbox_hw->tx_irq_mode) + mbox->controller.txdone_irq = true; + else { + mbox->controller.txdone_poll = true; + mbox->controller.txpoll_period = 5; + } + + err = mbox_controller_register(&mbox->controller); + if (err) { + dev_err(dev, "Failed to register mailbox %d\n", err); + return err; + } + + mbox_hw->parent = mbox; + return 0; +} +EXPORT_SYMBOL_GPL(hisi_mbox_register); + +void hisi_mbox_unregister(struct hisi_mbox_hw *mbox_hw) +{ + struct hisi_mbox *mbox = mbox_hw->parent; + + mbox_controller_unregister(&mbox->controller); +} +EXPORT_SYMBOL_GPL(hisi_mbox_unregister); diff --git a/drivers/mailbox/hisilicon/common.h b/drivers/mailbox/hisilicon/common.h new file mode 100644 index 00000000000000..c2199e262d3ba5 --- /dev/null +++ b/drivers/mailbox/hisilicon/common.h @@ -0,0 +1,114 @@ +/* + * Hisilicon mailbox definition + * + * Copyright (c) 2015 Hisilicon Limited. + * Copyright (c) 2015 Linaro Limited. + * + * Author: Leo Yan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __HISI_MBOX_H +#define __HISI_MBOX_H + +#include +#include +#include +#include +#include + +#define MBOX_MAX_CHANS 32 +#define MBOX_RX 0x0 +#define MBOX_TX 0x1 + +struct hisi_mbox_queue { + spinlock_t lock; + struct kfifo fifo; + struct work_struct work; + struct mbox_chan *chan; + bool full; +}; + +struct hisi_mbox_chan { + + /* + * Description for channel's hardware info: + * - direction; + * - peer core id for communication; + * - local irq vector or number; + * - remoted irq vector or number for peer core; + */ + unsigned int dir; + unsigned int peer_core; + unsigned int remote_irq; + unsigned int local_irq; + + /* + * Slot address is cached value derived from index + * within buffer for every channel + */ + void __iomem *slot; + + /* For rx's fifo operations */ + struct hisi_mbox_queue *mq; + + struct hisi_mbox_hw *mbox_hw; +}; + +struct hisi_mbox_ops { + int (*startup)(struct hisi_mbox_chan *chan); + void (*shutdown)(struct hisi_mbox_chan *chan); + + int (*rx)(struct hisi_mbox_chan *chan, void *msg, int len); + int (*tx)(struct hisi_mbox_chan *chan, void *msg, int len); + int (*tx_is_done)(struct hisi_mbox_chan *chan); + int (*ack)(struct hisi_mbox_chan *chan); + + u32 (*get_pending_irq)(struct hisi_mbox_hw *mbox_hw); + + void (*clear_irq)(struct hisi_mbox_chan *chan); + void (*enable_irq)(struct hisi_mbox_chan *chan); + void (*disable_irq)(struct hisi_mbox_chan *chan); +}; + +struct hisi_mbox_hw { + struct device *dev; + + unsigned int irq; + + /* flag of enabling tx's irq mode */ + bool tx_irq_mode; + + /* region for ipc event */ + void __iomem *ipc; + + /* region for share mem */ + void __iomem *buf; + + unsigned int chan_num; + struct hisi_mbox_chan *chan; + struct hisi_mbox_ops *ops; + struct hisi_mbox *parent; +}; + +struct hisi_mbox { + struct hisi_mbox_hw *hw; + + void *irq_map_chan[MBOX_MAX_CHANS]; + struct mbox_chan *chan; + struct mbox_controller controller; +}; + +extern int hisi_mbox_register(struct hisi_mbox_hw *mbox_hw); +extern void hisi_mbox_unregister(struct hisi_mbox_hw *mbox_hw); + +#endif /* __HISI_MBOX_H */ diff --git a/drivers/mailbox/hisilicon/hi6220-mailbox.c b/drivers/mailbox/hisilicon/hi6220-mailbox.c new file mode 100644 index 00000000000000..1eb94892ddff46 --- /dev/null +++ b/drivers/mailbox/hisilicon/hi6220-mailbox.c @@ -0,0 +1,305 @@ +/* + * Hisilicon's Hi6220 mailbox low level driver + * + * Copyright (c) 2015 Hisilicon Limited. + * Copyright (c) 2015 Linaro Limited. + * + * Author: Leo Yan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#define HI6220_MBOX_CHAN_NUM 2 +#define HI6220_MBOX_CHAN_SLOT_SIZE (1 << 6) + +/* Status & Mode Register */ +#define HI6220_MBOX_MODE_REG (0x0) + +#define HI6220_MBOX_STATUS_MASK (0xF << 4) +#define HI6220_MBOX_STATUS_IDLE (0x1 << 4) +#define HI6220_MBOX_STATUS_TX (0x2 << 4) +#define HI6220_MBOX_STATUS_RX (0x4 << 4) +#define HI6220_MBOX_STATUS_ACK (0x8 << 4) +#define HI6220_MBOX_ACK_CONFIG_MASK (0x1 << 0) +#define HI6220_MBOX_ACK_AUTOMATIC (0x1 << 0) +#define HI6220_MBOX_ACK_IRQ (0x0 << 0) + +/* Data Registers */ +#define HI6220_MBOX_DATA_REG(i) (0x4 + (i << 2)) + +/* ACPU Interrupt Register */ +#define HI6220_MBOX_ACPU_INT_RAW_REG (0x400) +#define HI6220_MBOX_ACPU_INT_MSK_REG (0x404) +#define HI6220_MBOX_ACPU_INT_STAT_REG (0x408) +#define HI6220_MBOX_ACPU_INT_CLR_REG (0x40c) +#define HI6220_MBOX_ACPU_INT_ENA_REG (0x500) +#define HI6220_MBOX_ACPU_INT_DIS_REG (0x504) + +/* MCU Interrupt Register */ +#define HI6220_MBOX_MCU_INT_RAW_REG (0x420) + +/* Core Id */ +#define HI6220_CORE_ACPU (0x0) +#define HI6220_CORE_MCU (0x2) + +static u32 hi6220_mbox_get_status(struct hisi_mbox_chan *chan) +{ + u32 status; + + status = readl(chan->slot + HI6220_MBOX_MODE_REG); + return status & HI6220_MBOX_STATUS_MASK; +} + +static void hi6220_mbox_set_status(struct hisi_mbox_chan *chan, u32 val) +{ + u32 status; + + status = readl(chan->slot + HI6220_MBOX_MODE_REG); + status &= ~HI6220_MBOX_STATUS_MASK; + status |= val; + writel(status, chan->slot + HI6220_MBOX_MODE_REG); +} + +static void hi6220_mbox_set_mode(struct hisi_mbox_chan *chan, u32 val) +{ + u32 mode; + + mode = readl(chan->slot + HI6220_MBOX_MODE_REG); + mode &= ~HI6220_MBOX_ACK_CONFIG_MASK; + mode |= val; + writel(mode, chan->slot + HI6220_MBOX_MODE_REG); +} + +static void hi6220_mbox_clear_irq(struct hisi_mbox_chan *chan) +{ + struct hisi_mbox_hw *mbox_hw = chan->mbox_hw; + int irq = chan->local_irq; + + writel(1 << irq, mbox_hw->ipc + HI6220_MBOX_ACPU_INT_CLR_REG); +} + +static void hi6220_mbox_enable_irq(struct hisi_mbox_chan *chan) +{ + struct hisi_mbox_hw *mbox_hw = chan->mbox_hw; + int irq = chan->local_irq; + + writel(1 << irq, mbox_hw->ipc + HI6220_MBOX_ACPU_INT_ENA_REG); +} + +static void hi6220_mbox_disable_irq(struct hisi_mbox_chan *chan) +{ + struct hisi_mbox_hw *mbox_hw = chan->mbox_hw; + int irq = chan->local_irq; + + writel(1 << irq, mbox_hw->ipc + HI6220_MBOX_ACPU_INT_DIS_REG); +} + +static u32 hi6220_mbox_get_pending_irq(struct hisi_mbox_hw *mbox_hw) +{ + return readl(mbox_hw->ipc + HI6220_MBOX_ACPU_INT_STAT_REG); +} + +static int hi6220_mbox_startup(struct hisi_mbox_chan *chan) +{ + hi6220_mbox_enable_irq(chan); + return 0; +} + +static void hi6220_mbox_shutdown(struct hisi_mbox_chan *chan) +{ + hi6220_mbox_disable_irq(chan); +} + +static int hi6220_mbox_rx(struct hisi_mbox_chan *chan, void *msg, int len) +{ + int *buf = msg; + int i; + + for (i = 0; i < (len >> 2); i++) + buf[i] = readl(chan->slot + HI6220_MBOX_DATA_REG(i)); + + /* clear IRQ source */ + hi6220_mbox_clear_irq(chan); + hi6220_mbox_set_status(chan, HI6220_MBOX_STATUS_IDLE); + return len; +} + +static int hi6220_mbox_tx(struct hisi_mbox_chan *chan, void *msg, int len) +{ + struct hisi_mbox_hw *mbox_hw = chan->mbox_hw; + int *buf = msg; + int irq = chan->remote_irq, i; + + hi6220_mbox_set_status(chan, HI6220_MBOX_STATUS_TX); + + if (mbox_hw->tx_irq_mode) + hi6220_mbox_set_mode(chan, HI6220_MBOX_ACK_IRQ); + else + hi6220_mbox_set_mode(chan, HI6220_MBOX_ACK_AUTOMATIC); + + for (i = 0; i < (len >> 2); i++) + writel(buf[i], chan->slot + HI6220_MBOX_DATA_REG(i)); + + /* trigger remote request */ + writel(1 << irq, mbox_hw->ipc + HI6220_MBOX_MCU_INT_RAW_REG); + return 0; +} + +static int hi6220_mbox_tx_is_done(struct hisi_mbox_chan *chan) +{ + int status; + + status = hi6220_mbox_get_status(chan); + return (status == HI6220_MBOX_STATUS_IDLE); +} + +static int hi6220_mbox_ack(struct hisi_mbox_chan *chan) +{ + hi6220_mbox_set_status(chan, HI6220_MBOX_STATUS_IDLE); + return 0; +} + +static struct hisi_mbox_ops hi6220_mbox_ops = { + .startup = hi6220_mbox_startup, + .shutdown = hi6220_mbox_shutdown, + + .clear_irq = hi6220_mbox_clear_irq, + .enable_irq = hi6220_mbox_enable_irq, + .disable_irq = hi6220_mbox_disable_irq, + .get_pending_irq = hi6220_mbox_get_pending_irq, + + .rx = hi6220_mbox_rx, + .tx = hi6220_mbox_tx, + .tx_is_done = hi6220_mbox_tx_is_done, + .ack = hi6220_mbox_ack, +}; + +static void hi6220_mbox_init_hw(struct hisi_mbox_hw *mbox_hw) +{ + struct hisi_mbox_chan init_data[HI6220_MBOX_CHAN_NUM] = { + { MBOX_RX, HI6220_CORE_MCU, 1, 10 }, + { MBOX_TX, HI6220_CORE_MCU, 0, 11 }, + }; + struct hisi_mbox_chan *chan = mbox_hw->chan; + int i; + + for (i = 0; i < HI6220_MBOX_CHAN_NUM; i++) { + memcpy(&chan[i], &init_data[i], sizeof(*chan)); + chan[i].slot = mbox_hw->buf + HI6220_MBOX_CHAN_SLOT_SIZE; + chan[i].mbox_hw = mbox_hw; + } + + /* mask and clear all interrupt vectors */ + writel(0x0, mbox_hw->ipc + HI6220_MBOX_ACPU_INT_MSK_REG); + writel(~0x0, mbox_hw->ipc + HI6220_MBOX_ACPU_INT_CLR_REG); + + /* use interrupt for tx's ack */ + mbox_hw->tx_irq_mode = true; + + /* set low level ops */ + mbox_hw->ops = &hi6220_mbox_ops; +} + +static const struct of_device_id hi6220_mbox_of_match[] = { + { .compatible = "hisilicon,hi6220-mbox", }, + {}, +}; +MODULE_DEVICE_TABLE(of, hi6220_mbox_of_match); + +static int hi6220_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hisi_mbox_hw *mbox_hw; + struct resource *res; + int err; + + mbox_hw = devm_kzalloc(dev, sizeof(*mbox_hw), GFP_KERNEL); + if (!mbox_hw) + return -ENOMEM; + + mbox_hw->dev = dev; + mbox_hw->chan_num = HI6220_MBOX_CHAN_NUM; + mbox_hw->chan = devm_kzalloc(dev, + mbox_hw->chan_num * sizeof(struct hisi_mbox_chan), GFP_KERNEL); + if (!mbox_hw->chan) + return -ENOMEM; + + mbox_hw->irq = platform_get_irq(pdev, 0); + if (mbox_hw->irq < 0) + return mbox_hw->irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mbox_hw->ipc = devm_ioremap_resource(dev, res); + if (IS_ERR(mbox_hw->ipc)) { + dev_err(dev, "ioremap ipc failed\n"); + return PTR_ERR(mbox_hw->ipc); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mbox_hw->buf = devm_ioremap_resource(dev, res); + if (IS_ERR(mbox_hw->buf)) { + dev_err(dev, "ioremap buffer failed\n"); + return PTR_ERR(mbox_hw->buf); + } + + hi6220_mbox_init_hw(mbox_hw); + + err = hisi_mbox_register(mbox_hw); + if (err) + return err; + + platform_set_drvdata(pdev, mbox_hw); + dev_info(dev, "Mailbox enabled\n"); + return 0; +} + +static int hi6220_mbox_remove(struct platform_device *pdev) +{ + struct hisi_mbox_hw *mbox_hw = platform_get_drvdata(pdev); + + hisi_mbox_unregister(mbox_hw); + return 0; +} + +static struct platform_driver hi6220_mbox_driver = { + .driver = { + .name = "hi6220-mbox", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(hi6220_mbox_of_match), + }, + .probe = hi6220_mbox_probe, + .remove = hi6220_mbox_remove, +}; + +static int __init hi6220_mbox_init(void) +{ + return platform_driver_register(&hi6220_mbox_driver); +} +core_initcall(hi6220_mbox_init); + +static void __exit hi6220_mbox_exit(void) +{ + platform_driver_unregister(&hi6220_mbox_driver); +} +module_exit(hi6220_mbox_exit); + +MODULE_AUTHOR("Leo Yan "); +MODULE_DESCRIPTION("Hi6220 mailbox driver"); +MODULE_LICENSE("GPL v2"); From 719f114ebd0eb62a1e23c06f2642c6423d2cd6b5 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 20:39:41 +0800 Subject: [PATCH 03/41] arm64: dts: add Hi6220 mailbox node On Hi6220, below memory regions in DDR have specific purpose: - 0x05e0,0000 - 0x05ef,ffff: For MCU firmware using at runtime; - 0x0740,f000 - 0x0740,ffff: For MCU firmware's section; - 0x06df,f000 - 0x06df,ffff: For mailbox message data. This patch reserves these memory regions and add device node for mailbox in dts. Signed-off-by: Leo Yan --- .../arm64/boot/dts/hisilicon/hi6220-hikey.dts | 19 +++++++++++++++++-- arch/arm64/boot/dts/hisilicon/hi6220.dtsi | 8 ++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts index a94da847c069bc..6ce3ca44672dde 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts +++ b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts @@ -7,8 +7,6 @@ /dts-v1/; -/memreserve/ 0x0740f000 0x1000; - #include "hi6220.dtsi" / { @@ -28,4 +26,21 @@ device_type = "memory"; reg = <0x0 0x07400000 0x0 0x38c00000>; }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + mcu-buf@05e00000 { + no-map; + reg = <0x0 0x0740f000 0x0 0x00001000>, /* MCU firmware section */ + <0x0 0x05e00000 0x0 0x00100000>; /* MCU firmware buffer */ + }; + + mbox-buf@06dff000 { + no-map; + reg = <0x0 0x06dff000 0x0 0x00001000>; /* Mailbox message buf */ + }; + }; }; diff --git a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi index 6c5720ea78b3bc..1fcca3eb542935 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi +++ b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi @@ -224,5 +224,13 @@ clocks = <&clock_ao HI6220_UART0_PCLK>, <&clock_ao HI6220_UART0_PCLK>; clock-names = "uartclk", "apb_pclk"; }; + + mailbox: mailbox { + #mbox-cells = <1>; + compatible = "hisilicon,hi6220-mbox"; + reg = <0x0 0xf7510000 0x0 0x1000>, /* IPC_S */ + <0x0 0x06dff800 0x0 0x0800>; /* Mailbox buffer */ + interrupts = ; + }; }; }; From 421c9b2c5c081d4d448da98838dec788fa5e1631 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 11:15:56 +0800 Subject: [PATCH 04/41] Revert "clk: hi6220: enable stub clock on the platform" This reverts commit 6d9fac1b34f431feb21d2f066a736729a79af86e. --- drivers/clk/hisilicon/clk-hi6220.c | 19 ------------------- include/dt-bindings/clock/hi6220-clock.h | 3 --- 2 files changed, 22 deletions(-) diff --git a/drivers/clk/hisilicon/clk-hi6220.c b/drivers/clk/hisilicon/clk-hi6220.c index ab0566a26b166f..d820614bf5eac9 100644 --- a/drivers/clk/hisilicon/clk-hi6220.c +++ b/drivers/clk/hisilicon/clk-hi6220.c @@ -287,22 +287,3 @@ static void __init hi6220_clk_power_init(struct device_node *np) ARRAY_SIZE(hi6220_div_clks_power), clk_data); } CLK_OF_DECLARE(hi6220_clk_power, "hisilicon,hi6220-clock-power", hi6220_clk_power_init); - -static struct hisi_stub_clock hi6220_stub_clks[] __initdata = { - { HISI_STUB_ACPU0, "acpu0", NULL, CLK_IS_ROOT | CLK_GET_RATE_NOCACHE, }, -}; - -static void __init hi6220_clk_stub_init(struct device_node *np) -{ - struct hisi_clock_data *clk_data; - - clk_data = hisi_clk_alloc_data(np, HISI_STUB_NR_CLKS); - if (!clk_data) - return; - - hisi_clk_register_stub(hi6220_stub_clks, - ARRAY_SIZE(hi6220_stub_clks), clk_data, np); - return; - -} -CLK_OF_DECLARE(hi6220_clk_stub, "hisilicon,hisi-clock-stub", hi6220_clk_stub_init); diff --git a/include/dt-bindings/clock/hi6220-clock.h b/include/dt-bindings/clock/hi6220-clock.h index a71cf399a780a4..673805ec50f8b8 100644 --- a/include/dt-bindings/clock/hi6220-clock.h +++ b/include/dt-bindings/clock/hi6220-clock.h @@ -175,7 +175,4 @@ #define HI6220_DDRC_AXI1 11 #define HI6220_POWER_NR_CLKS 16 - -#include - #endif From a99670362f21a5285d9c08252b2e52cdb3773f87 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 11:16:17 +0800 Subject: [PATCH 05/41] Revert "clk: hisi: add stub clock register function" This reverts commit f50c04e578b4f90bf544de63ec209422883b8cd2. --- drivers/clk/hisilicon/clk.c | 29 ----------------------------- drivers/clk/hisilicon/clk.h | 11 ----------- 2 files changed, 40 deletions(-) diff --git a/drivers/clk/hisilicon/clk.c b/drivers/clk/hisilicon/clk.c index 013b3c22976cf9..dd2087b259d287 100644 --- a/drivers/clk/hisilicon/clk.c +++ b/drivers/clk/hisilicon/clk.c @@ -273,32 +273,3 @@ void __init hi6220_clk_register_divider(struct hi6220_divider_clock *clks, data->clk_data.clks[clks[i].id] = clk; } } - -void __init hisi_clk_register_stub(struct hisi_stub_clock *clks, int nums, - struct hisi_clock_data *data, - struct device_node *np) -{ - struct clk *clk; - int i; - - for (i = 0; i < nums; i++) { - clk = hisi_register_stub_clk(np, - clks[i].id, - clks[i].name, - clks[i].parent_name, - clks[i].flags, - &hisi_clk_lock); - if (IS_ERR(clk)) { - pr_err("%s: failed to register clock %s\n", - __func__, clks[i].name); - continue; - } - - if (clks[i].alias) - clk_register_clkdev(clk, clks[i].name, NULL); - - data->clk_data.clks[clks[i].id] = clk; - } - - return; -} diff --git a/drivers/clk/hisilicon/clk.h b/drivers/clk/hisilicon/clk.h index ef9b508920d225..343eea44a89684 100644 --- a/drivers/clk/hisilicon/clk.h +++ b/drivers/clk/hisilicon/clk.h @@ -102,14 +102,6 @@ struct hisi_gate_clock { const char *alias; }; -struct hisi_stub_clock { - unsigned int id; - char *name; - const char *parent_name; - unsigned long flags; - const char *alias; -}; - struct clk *hisi_register_clkgate_sep(struct device *, const char *, const char *, unsigned long, void __iomem *, u8, @@ -138,8 +130,5 @@ void __init hisi_clk_register_gate_sep(struct hisi_gate_clock *, int, struct hisi_clock_data *); void __init hi6220_clk_register_divider(struct hi6220_divider_clock *, int, struct hisi_clock_data *); -void __init hisi_clk_register_stub(struct hisi_stub_clock *clks, int nums, - struct hisi_clock_data *data, - struct device_node *np); #endif /* __HISI_CLK_H */ From 0b4d019abb9569b9c1a591a6e5825e191e42342c Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 11:17:17 +0800 Subject: [PATCH 06/41] Revert "clk: hisi: add stub clk driver" This reverts commit 4a70aa68c956940618cc0c4eb63cddc188e84fbd. --- drivers/clk/hisilicon/Makefile | 2 +- drivers/clk/hisilicon/clk-stub.c | 284 -------------------- drivers/clk/hisilicon/clk.h | 3 - include/dt-bindings/clock/hisi,stub-clock.h | 26 -- 4 files changed, 1 insertion(+), 314 deletions(-) delete mode 100644 drivers/clk/hisilicon/clk-stub.c delete mode 100644 include/dt-bindings/clock/hisi,stub-clock.h diff --git a/drivers/clk/hisilicon/Makefile b/drivers/clk/hisilicon/Makefile index 02aeb779e69a1d..bbf0539a5d3430 100644 --- a/drivers/clk/hisilicon/Makefile +++ b/drivers/clk/hisilicon/Makefile @@ -2,7 +2,7 @@ # Hisilicon Clock specific Makefile # -obj-y += clk.o clkgate-separated.o clk-stub.o +obj-y += clk.o clkgate-separated.o obj-$(CONFIG_ARCH_HI3xxx) += clk-hi3620.o obj-$(CONFIG_ARCH_HIP04) += clk-hip04.o diff --git a/drivers/clk/hisilicon/clk-stub.c b/drivers/clk/hisilicon/clk-stub.c deleted file mode 100644 index 897d6d3f336e2f..00000000000000 --- a/drivers/clk/hisilicon/clk-stub.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Hisilicon stub clock driver - * - * Copyright (c) 2015 Hisilicon Limited. - * Copyright (c) 2015 Linaro Limited. - * - * Author: Leo Yan - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* CPU dynamic frequency scaling */ -#define ACPU_DFS_FREQ_MAX (0x1724) -#define ACPU_DFS_FLAG (0x1AF4) -#define ACPU_DFS_FREQ_REQ (0x1AF8) -#define ACPU_DFS_FREQ_LMT (0x1AFC) - -#define ACPU_DFS_LOCK_FLAG (0xAEAEAEAE) - -/* Multi-core communication */ -#define MC_CORE_ACPU 0x2 -#define MC_COM_CPU_RAW_INT_OFFSET(i) (0x400 + (i << 4)) -#define MC_COM_INT_ACPU_DFS 15 - -#define to_stub_clk(hw) container_of(hw, struct hisi_stub_clk, hw) - -struct hisi_stub_clk { - struct clk_hw hw; - - /* - * hi6220: - * - 0: A53; 1: A53; 2: gpu; 3: ddr; - */ - u32 id; - u32 rate; - spinlock_t *lock; -}; - -static int initialized_stub_clk = 0; -static struct regmap *mc_map = NULL; -static struct regmap *dfs_map = NULL; - -static unsigned int hisi_acpu_get_freq(void) -{ - unsigned int freq; - - regmap_read(dfs_map, ACPU_DFS_FREQ_REQ, &freq); - return freq; -} - -static int hisi_acpu_set_freq(unsigned int freq) -{ - /* set the frequency in sram */ - regmap_write(dfs_map, ACPU_DFS_FREQ_REQ, freq); - - /* send request to power controller */ - regmap_write(mc_map, MC_COM_CPU_RAW_INT_OFFSET(MC_CORE_ACPU), - (1 << MC_COM_INT_ACPU_DFS)); - return 0; -} - -static int hisi_acpu_round_freq(unsigned int freq) -{ - unsigned int limit_flag, limit_freq = UINT_MAX; - unsigned int max_freq; - - /* check the constrainted frequency */ - regmap_read(dfs_map, ACPU_DFS_FLAG, &limit_flag); - if (limit_flag == ACPU_DFS_LOCK_FLAG) - regmap_read(dfs_map, ACPU_DFS_FREQ_LMT, &limit_freq); - - /* check the supported maximum frequency */ - regmap_read(dfs_map, ACPU_DFS_FREQ_MAX, &max_freq); - - /* calculate the real maximum frequency */ - max_freq = min(max_freq, limit_freq); - - if (WARN_ON(freq > max_freq)) - freq = max_freq; - - return freq; -} - -static unsigned long hisi_stub_clk_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - u32 rate = 0; - struct hisi_stub_clk *stub_clk = to_stub_clk(hw); - unsigned long flags; - - BUG_ON(!stub_clk->lock); - - spin_lock_irqsave(stub_clk->lock, flags); - - switch (stub_clk->id) { - case HISI_STUB_ACPU0: - rate = hisi_acpu_get_freq(); - - /* convert from KHz to Hz */ - rate *= 1000; - break; - - default: - pr_err("%s: un-supported clock id %d\n", __func__, - stub_clk->id); - break; - } - - spin_unlock_irqrestore(stub_clk->lock, flags); - return rate; -} - -static int hisi_stub_clk_set_rate(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - struct hisi_stub_clk *stub_clk = to_stub_clk(hw); - unsigned long flags; - unsigned long new_rate = rate / 1000; /* Khz */ - int ret = 0; - - BUG_ON(!stub_clk->lock); - - spin_lock_irqsave(stub_clk->lock, flags); - - switch (stub_clk->id) { - case HISI_STUB_ACPU0: - ret = hisi_acpu_set_freq(new_rate); - if (ret < 0) { - spin_unlock_irqrestore(stub_clk->lock, flags); - return ret; - } - - break; - - default: - pr_err("%s: un-supported clock id %d\n", __func__, - stub_clk->id); - break; - } - - stub_clk->rate = new_rate; - spin_unlock_irqrestore(stub_clk->lock, flags); - - pr_debug("%s: set rate=%ldKhz\n", __func__, new_rate); - return ret; -} - -static long hisi_stub_clk_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) -{ - struct hisi_stub_clk *stub_clk = to_stub_clk(hw); - unsigned long flags; - unsigned long new_rate = rate / 1000; /* Khz */ - - BUG_ON(!stub_clk->lock); - - spin_lock_irqsave(stub_clk->lock, flags); - - switch (stub_clk->id) { - case HISI_STUB_ACPU0: - new_rate = hisi_acpu_round_freq(new_rate); - - /* convert from KHz to Hz */ - new_rate *= 1000; - break; - - default: - pr_err("%s: un-supported clock id %d\n", __func__, - stub_clk->id); - break; - } - - spin_unlock_irqrestore(stub_clk->lock, flags); - return new_rate; -} - -static struct clk_ops hisi_stub_clk_ops = { - .recalc_rate = hisi_stub_clk_recalc_rate, - .round_rate = hisi_stub_clk_round_rate, - .set_rate = hisi_stub_clk_set_rate, -}; - -static int hisi_stub_clk_init(struct device_node *np) -{ - int ret = 0; - int max_freq; - - dfs_map = syscon_regmap_lookup_by_phandle(np, - "hisilicon,clk-stub-sram"); - if (IS_ERR(dfs_map)) { - ret = PTR_ERR(dfs_map); - pr_err("failed to get sram regmap: %d\n", ret); - return ret; - } - - mc_map = syscon_regmap_lookup_by_phandle(np, - "hisilicon,clk-stub-mc"); - if (IS_ERR(mc_map)) { - ret = PTR_ERR(mc_map); - pr_err("failed to get multi-core comm regmap: %d\n", ret); - return ret; - } - - /* initialize buffer to zero */ - regmap_write(dfs_map, ACPU_DFS_FLAG, 0x0); - regmap_write(dfs_map, ACPU_DFS_FREQ_REQ, 0x0); - regmap_write(dfs_map, ACPU_DFS_FREQ_LMT, 0x0); - - /* At boot time, set to maximum frequency */ - regmap_read(dfs_map, ACPU_DFS_FREQ_MAX, &max_freq); - hisi_acpu_set_freq(max_freq); - - return ret; -} - -static struct clk *_register_stub_clk(struct device *dev, unsigned int id, - const char *name, const char *parent_name, unsigned long flags, - spinlock_t *lock) -{ - struct hisi_stub_clk *stub_clk; - struct clk *clk; - struct clk_init_data init; - - stub_clk = kzalloc(sizeof(*stub_clk), GFP_KERNEL); - if (!stub_clk) { - pr_err("%s: fail to alloc stub clk!\n", __func__); - return ERR_PTR(-ENOMEM); - } - - init.name = name; - init.ops = &hisi_stub_clk_ops; - init.parent_names = parent_name ? &parent_name : NULL; - init.num_parents = parent_name ? 1 : 0; - init.flags = flags; - - stub_clk->hw.init = &init; - stub_clk->id = id; - stub_clk->lock = lock; - - clk = clk_register(dev, &stub_clk->hw); - if (IS_ERR(clk)) { - pr_err("%s: fail to register stub clk %s!\n", __func__, name); - kfree(stub_clk); - } - - return clk; -} - -struct clk *hisi_register_stub_clk(struct device_node *np, - unsigned int id, const char *name, const char *parent_name, - unsigned long flags, spinlock_t *lock) -{ - int ret; - struct clk *clk; - - pr_debug("[%s]: clk name = %s...\n", __func__, name); - - if (!initialized_stub_clk) { - ret = hisi_stub_clk_init(np); - if (ret) - return ERR_PTR(-EINVAL); - - initialized_stub_clk = 1; - } - - clk = _register_stub_clk(NULL, id, name, parent_name, flags, lock); - return clk; -} diff --git a/drivers/clk/hisilicon/clk.h b/drivers/clk/hisilicon/clk.h index 343eea44a89684..056840fe8c6fe1 100644 --- a/drivers/clk/hisilicon/clk.h +++ b/drivers/clk/hisilicon/clk.h @@ -109,9 +109,6 @@ struct clk *hisi_register_clkgate_sep(struct device *, const char *, struct clk *hi6220_register_clkdiv(struct device *dev, const char *name, const char *parent_name, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u32 mask_bit, spinlock_t *lock); -struct clk *hisi_register_stub_clk(struct device_node *np, - unsigned int id, const char *name, const char *parent_name, - unsigned long flags, spinlock_t *lock); struct hisi_clock_data __init *hisi_clk_init(struct device_node *, int); struct hisi_clock_data __init *hisi_clk_alloc_data(struct device_node *np, diff --git a/include/dt-bindings/clock/hisi,stub-clock.h b/include/dt-bindings/clock/hisi,stub-clock.h deleted file mode 100644 index 637d7f54c60dff..00000000000000 --- a/include/dt-bindings/clock/hisi,stub-clock.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * This header provides constants for Hisilicon stub clock driver. - * - * Copyright (c) 2015 Hisilicon Limited. - * Copyright (c) 2015 Linaro Limited. - * - * Author: Leo Yan - * - * 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. - */ - -#ifndef _DT_BINDINGS_HISI_STUB_CLOCK_H -#define _DT_BINDINGS_HISI_STUB_CLOCK_H - -/* Stub Clocks */ -#define HISI_STUB_ACPU0 0 -#define HISI_STUB_ACPU1 1 -#define HISI_STUB_GPU 2 -#define HISI_STUB_DDR_MIN 3 -#define HISI_STUB_DDR_MAX 4 -#define HISI_STUB_DDR 5 -#define HISI_STUB_NR_CLKS 6 - -#endif /* _DT_BINDINGS_HISI_STUB_CLOCK_H */ From ec4f1a8c969bdc5f3d8ba46f9d9985c9f32d60b2 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Mon, 30 Mar 2015 10:34:53 +0800 Subject: [PATCH 07/41] clk: hisi: refine parameter checking for init *of_iomap()* will check the device node pointer, and if the pointer is NULL it will return error code. So refine clock's init flow by checking the device node with this simple way; and polish a little for the print out message. Signed-off-by: Leo Yan --- drivers/clk/hisilicon/clk.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/clk/hisilicon/clk.c b/drivers/clk/hisilicon/clk.c index dd2087b259d287..237b24c20c5e23 100644 --- a/drivers/clk/hisilicon/clk.c +++ b/drivers/clk/hisilicon/clk.c @@ -72,15 +72,9 @@ struct hisi_clock_data __init *hisi_clk_init(struct device_node *np, struct hisi_clock_data *clk_data; void __iomem *base; - if (np) { - base = of_iomap(np, 0); - if (!base) { - pr_err("failed to map Hisilicon clock registers\n"); - return NULL; - } - printk("%s: base %p\n", __func__, base); - } else { - pr_err("failed to find Hisilicon clock node in DTS\n"); + base = of_iomap(np, 0); + if (!base) { + pr_err("%s: failed to map clock registers\n", __func__); return NULL; } From ce6c0db1b5391284044cf62d7b04e96afd08f0df Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 16:59:43 +0800 Subject: [PATCH 08/41] clk: Hi6220: add stub clock driver On Hi6220, there have some clocks which can use mailbox channel to send messages to power controller to change frequency; this includes CPU, GPU and DDR clocks. For dynamic frequency scaling, firstly need write the frequency value to SRAM region, and then send message to mailbox to trigger power controller to handle this requirement. This driver will use syscon APIs to pass SRAM memory region and use common mailbox APIs for channels accessing. This init driver will support cpu frequency change firstly. Signed-off-by: Leo Yan --- drivers/clk/hisilicon/Kconfig | 2 +- drivers/clk/hisilicon/Makefile | 2 +- drivers/clk/hisilicon/clk-hi6220-stub.c | 276 ++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 drivers/clk/hisilicon/clk-hi6220-stub.c diff --git a/drivers/clk/hisilicon/Kconfig b/drivers/clk/hisilicon/Kconfig index e3ead46ae2a440..52707b9f53f4b6 100644 --- a/drivers/clk/hisilicon/Kconfig +++ b/drivers/clk/hisilicon/Kconfig @@ -1,5 +1,5 @@ config COMMON_CLK_HI6220 tristate "Hi6220 Clock Driver" - depends on OF && ARCH_HISI + depends on (ARCH_HISI || COMPILE_TEST) && MAILBOX help Build the Hisilicon Hi6220 clock driver based on the common clock framework. diff --git a/drivers/clk/hisilicon/Makefile b/drivers/clk/hisilicon/Makefile index bbf0539a5d3430..18d76270a97531 100644 --- a/drivers/clk/hisilicon/Makefile +++ b/drivers/clk/hisilicon/Makefile @@ -7,4 +7,4 @@ obj-y += clk.o clkgate-separated.o obj-$(CONFIG_ARCH_HI3xxx) += clk-hi3620.o obj-$(CONFIG_ARCH_HIP04) += clk-hip04.o obj-$(CONFIG_ARCH_HIX5HD2) += clk-hix5hd2.o -obj-$(CONFIG_COMMON_CLK_HI6220) += clkdivider-hi6220.o clk-hi6220.o +obj-$(CONFIG_COMMON_CLK_HI6220) += clkdivider-hi6220.o clk-hi6220.o clk-hi6220-stub.o diff --git a/drivers/clk/hisilicon/clk-hi6220-stub.c b/drivers/clk/hisilicon/clk-hi6220-stub.c new file mode 100644 index 00000000000000..9337d437beb172 --- /dev/null +++ b/drivers/clk/hisilicon/clk-hi6220-stub.c @@ -0,0 +1,276 @@ +/* + * Hi6220 stub clock driver + * + * Copyright (c) 2015 Hisilicon Limited. + * Copyright (c) 2015 Linaro Limited. + * + * Author: Leo Yan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Stub clocks id */ +#define HI6220_STUB_ACPU0 0 +#define HI6220_STUB_ACPU1 1 +#define HI6220_STUB_GPU 2 +#define HI6220_STUB_DDR 5 + +/* Mailbox message */ +#define HI6220_MBOX_MSG_LEN 8 + +#define HI6220_MBOX_FREQ 0xA +#define HI6220_MBOX_CMD_SET 0x3 +#define HI6220_MBOX_OBJ_AP 0x0 + +/* CPU dynamic frequency scaling */ +#define ACPU_DFS_FREQ_MAX 0x1724 +#define ACPU_DFS_CUR_FREQ 0x17CC +#define ACPU_DFS_FLAG 0x1B30 +#define ACPU_DFS_FREQ_REQ 0x1B34 +#define ACPU_DFS_FREQ_LMT 0x1B38 +#define ACPU_DFS_LOCK_FLAG 0xAEAEAEAE + +#define to_stub_clk(hw) container_of(hw, struct hi6220_stub_clk, hw) + +struct hi6220_stub_clk { + u32 id; + + struct device *dev; + struct clk_hw hw; + + struct regmap *dfs_map; + struct mbox_client cl; + struct mbox_chan *mbox; +}; + +struct hi6220_mbox_msg { + unsigned char type; + unsigned char cmd; + unsigned char obj; + unsigned char src; + unsigned char para[4]; +}; + +union hi6220_mbox_data { + unsigned int data[HI6220_MBOX_MSG_LEN]; + struct hi6220_mbox_msg msg; +}; + +static unsigned int hi6220_acpu_get_freq(struct hi6220_stub_clk *stub_clk) +{ + unsigned int freq; + + regmap_read(stub_clk->dfs_map, ACPU_DFS_CUR_FREQ, &freq); + return freq; +} + +static int hi6220_acpu_set_freq(struct hi6220_stub_clk *stub_clk, + unsigned int freq) +{ + union hi6220_mbox_data data; + + /* set the frequency in sram */ + regmap_write(stub_clk->dfs_map, ACPU_DFS_FREQ_REQ, freq); + + /* compound mailbox message */ + data.msg.type = HI6220_MBOX_FREQ; + data.msg.cmd = HI6220_MBOX_CMD_SET; + data.msg.obj = HI6220_MBOX_OBJ_AP; + data.msg.src = HI6220_MBOX_OBJ_AP; + + mbox_send_message(stub_clk->mbox, &data); + return 0; +} + +static int hi6220_acpu_round_freq(struct hi6220_stub_clk *stub_clk, + unsigned int freq) +{ + unsigned int limit_flag, limit_freq = UINT_MAX; + unsigned int max_freq; + + /* check the constrained frequency */ + regmap_read(stub_clk->dfs_map, ACPU_DFS_FLAG, &limit_flag); + if (limit_flag == ACPU_DFS_LOCK_FLAG) + regmap_read(stub_clk->dfs_map, ACPU_DFS_FREQ_LMT, &limit_freq); + + /* check the supported maximum frequency */ + regmap_read(stub_clk->dfs_map, ACPU_DFS_FREQ_MAX, &max_freq); + + /* calculate the real maximum frequency */ + max_freq = min(max_freq, limit_freq); + + if (WARN_ON(freq > max_freq)) + freq = max_freq; + + return freq; +} + +static unsigned long hi6220_stub_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + u32 rate = 0; + struct hi6220_stub_clk *stub_clk = to_stub_clk(hw); + + switch (stub_clk->id) { + case HI6220_STUB_ACPU0: + rate = hi6220_acpu_get_freq(stub_clk); + + /* convert from kHz to Hz */ + rate *= 1000; + break; + + default: + dev_err(stub_clk->dev, "%s: un-supported clock id %d\n", + __func__, stub_clk->id); + break; + } + + return rate; +} + +static int hi6220_stub_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct hi6220_stub_clk *stub_clk = to_stub_clk(hw); + unsigned long new_rate = rate / 1000; /* kHz */ + int ret = 0; + + switch (stub_clk->id) { + case HI6220_STUB_ACPU0: + ret = hi6220_acpu_set_freq(stub_clk, new_rate); + if (ret < 0) + return ret; + + break; + + default: + dev_err(stub_clk->dev, "%s: un-supported clock id %d\n", + __func__, stub_clk->id); + break; + } + + pr_debug("%s: set rate=%ldkHz\n", __func__, new_rate); + return ret; +} + +static long hi6220_stub_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct hi6220_stub_clk *stub_clk = to_stub_clk(hw); + unsigned long new_rate = rate / 1000; /* kHz */ + + switch (stub_clk->id) { + case HI6220_STUB_ACPU0: + new_rate = hi6220_acpu_round_freq(stub_clk, new_rate); + + /* convert from kHz to Hz */ + new_rate *= 1000; + break; + + default: + dev_err(stub_clk->dev, "%s: un-supported clock id %d\n", + __func__, stub_clk->id); + break; + } + + return new_rate; +} + +static const struct clk_ops hi6220_stub_clk_ops = { + .recalc_rate = hi6220_stub_clk_recalc_rate, + .round_rate = hi6220_stub_clk_round_rate, + .set_rate = hi6220_stub_clk_set_rate, +}; + +static int hi6220_stub_clk_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct clk_init_data init; + struct hi6220_stub_clk *stub_clk; + struct clk *clk; + struct device_node *np = pdev->dev.of_node; + int ret; + + stub_clk = devm_kzalloc(dev, sizeof(*stub_clk), GFP_KERNEL); + if (!stub_clk) + return -ENOMEM; + + stub_clk->dfs_map = syscon_regmap_lookup_by_phandle(np, + "hisilicon,hi6220-clk-sram"); + if (IS_ERR(stub_clk->dfs_map)) { + dev_err(dev, "failed to get sram regmap\n"); + return PTR_ERR(stub_clk->dfs_map); + } + + stub_clk->hw.init = &init; + stub_clk->dev = dev; + stub_clk->id = HI6220_STUB_ACPU0; + + /* Use mailbox client with blocking mode */ + stub_clk->cl.dev = dev; + stub_clk->cl.tx_done = NULL; + stub_clk->cl.tx_block = true; + stub_clk->cl.tx_tout = 500; + stub_clk->cl.knows_txdone = false; + + /* Allocate mailbox channel */ + stub_clk->mbox = mbox_request_channel(&stub_clk->cl, 0); + if (IS_ERR(stub_clk->mbox)) { + dev_err(dev, "failed get mailbox channel\n"); + return PTR_ERR(stub_clk->mbox); + }; + + init.name = "acpu0"; + init.ops = &hi6220_stub_clk_ops; + init.num_parents = 0; + init.flags = CLK_IS_ROOT; + + clk = devm_clk_register(dev, &stub_clk->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = of_clk_add_provider(np, of_clk_src_simple_get, clk); + if (ret) { + dev_err(dev, "failed to register OF clock provider\n"); + return ret; + } + + /* initialize buffer to zero */ + regmap_write(stub_clk->dfs_map, ACPU_DFS_FLAG, 0x0); + regmap_write(stub_clk->dfs_map, ACPU_DFS_FREQ_REQ, 0x0); + regmap_write(stub_clk->dfs_map, ACPU_DFS_FREQ_LMT, 0x0); + + dev_dbg(dev, "Registered clock '%s'\n", init.name); + return 0; +} + +static const struct of_device_id hi6220_stub_clk_of_match[] = { + { .compatible = "hisilicon,hi6220-stub-clk", }, + {} +}; + +static struct platform_driver hi6220_stub_clk_driver = { + .driver = { + .name = "hi6220-stub-clk", + .of_match_table = hi6220_stub_clk_of_match, + }, + .probe = hi6220_stub_clk_probe, +}; + +static int __init hi6220_stub_clk_init(void) +{ + return platform_driver_register(&hi6220_stub_clk_driver); +} +subsys_initcall(hi6220_stub_clk_init); From c7f10e40521e06359adff4c29dbaac388bbd65b3 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 7 Jul 2015 20:31:35 +0800 Subject: [PATCH 09/41] arm64: dts: add Hi6220's stub clock node Enable SRAM node and stub clock node for Hi6220; furthermore add the CPU's clock so it will be used by cpufreq-dt driver. Signed-off-by: Leo Yan --- arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts | 2 +- arch/arm64/boot/dts/hisilicon/hi6220.dtsi | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts index 6ce3ca44672dde..54964bf4e3b1b7 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts +++ b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts @@ -11,7 +11,7 @@ / { model = "HiKey Development Board"; - compatible = "hisilicon,hi6220-hikey"; + compatible = "hisilicon,hi6220-hikey", "hisilicon,hi6220"; #address-cells = <2>; #size-cells = <2>; interrupt-parent = <&gic>; diff --git a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi index 1fcca3eb542935..fddf37fca0b075 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi +++ b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi @@ -4,7 +4,9 @@ * Copyright (C) 2015, Hisilicon Ltd. */ +#include #include +#include / { psci { @@ -21,7 +23,7 @@ device_type = "cpu"; reg = <0x0 0x0>; enable-method = "psci"; - clocks = <&clock_stub HISI_STUB_ACPU0>; + clocks = <&stub_clock 0>; clock-latency = <0>; operating-points = < /* kHz */ @@ -144,9 +146,9 @@ interrupt-parent = <&gic>; ranges; - sram: sram { - compatible = "hisilicon,sram", "syscon"; - reg = <0x0 0xFFF80000 0x0 0x12000>; + sram: sram@fff80000 { + compatible = "hisilicon,hi6220-sramctrl", "syscon"; + reg = <0x0 0xfff80000 0x0 0x12000>; }; ipc_s: ipc_s { @@ -210,11 +212,11 @@ }; }; - clock_stub: clock_stub { - compatible = "hisilicon,hisi-clock-stub"; - hisilicon,clk-stub-sram = <&sram>; - hisilicon,clk-stub-mc = <&ipc_s>; + stub_clock: stub_clock { + compatible = "hisilicon,hi6220-stub-clk"; + hisilicon,hi6220-clk-sram = <&sram>; #clock-cells = <1>; + mboxes = <&mailbox 1>; }; uart0: uart@f8015000 { /* console */ From c54678d40f64f0b42907f9e1ccd37f30eeef6def Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Sat, 11 Jul 2015 16:37:25 +0800 Subject: [PATCH 10/41] arm64: defconfig: enable config for hi6220 mailbox Signed-off-by: Leo Yan --- arch/arm64/configs/defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 3db2ff908d0441..24a569c04f5cb9 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -466,6 +466,9 @@ CONFIG_VIRTIO_MMIO=y CONFIG_COMMON_CLK_QCOM=y CONFIG_MSM_GCC_8916=y CONFIG_STAGING=y +CONFIG_MAILBOX=y +CONFIG_HISI_MBOX=y +CONFIG_HI6220_MBOX=y CONFIG_ASHMEM=y CONFIG_ANDROID_TIMED_GPIO=y CONFIG_ANDROID_LOW_MEMORY_KILLER=y From 793d8c4929ca4474782a218683e4c111221ddb75 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Mon, 18 May 2015 13:44:43 +0100 Subject: [PATCH 11/41] ARM: introduce sp804_timer_disable and remove arm_timer.h inclusion The header asm/hardware/arm_timer.h is included in various machine specific files to access TIMER_CTRL and initialise to a known state. This patch introduces a new function sp804_timer_disable to disable the SP804 timers and uses the same for initialising the timers to known(off) state, thereby removing the dependency on the header asm/hardware/arm_timer.h This change is in prepartion to move sp804 timer support out of arch/arm so that it can be used on ARM64 platforms. Signed-off-by: Sudeep Holla Cc: Russell King Cc: Daniel Lezcano Cc: Arnd Bergmann Cc: Olof Johansson --- arch/arm/common/timer-sp.c | 5 +++++ arch/arm/include/asm/hardware/timer-sp.h | 1 + arch/arm/mach-integrator/integrator_ap.c | 1 - arch/arm/mach-realview/core.c | 9 ++++----- arch/arm/mach-versatile/core.c | 9 ++++----- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/arch/arm/common/timer-sp.c b/arch/arm/common/timer-sp.c index 19211324772f38..000aea3722bc2f 100644 --- a/arch/arm/common/timer-sp.c +++ b/arch/arm/common/timer-sp.c @@ -71,6 +71,11 @@ static u64 notrace sp804_read(void) return ~readl_relaxed(sched_clock_base + TIMER_VALUE); } +void __init sp804_timer_disable(void __iomem *base) +{ + writel(0, base + TIMER_CTRL); +} + void __init __sp804_clocksource_and_sched_clock_init(void __iomem *base, const char *name, struct clk *clk, diff --git a/arch/arm/include/asm/hardware/timer-sp.h b/arch/arm/include/asm/hardware/timer-sp.h index bb28af7c32deb0..05eaefa46742ec 100644 --- a/arch/arm/include/asm/hardware/timer-sp.h +++ b/arch/arm/include/asm/hardware/timer-sp.h @@ -4,6 +4,7 @@ void __sp804_clocksource_and_sched_clock_init(void __iomem *, const char *, struct clk *, int); void __sp804_clockevents_init(void __iomem *, unsigned int, struct clk *, const char *); +void sp804_timer_disable(void __iomem *); static inline void sp804_clocksource_init(void __iomem *base, const char *name) { diff --git a/arch/arm/mach-integrator/integrator_ap.c b/arch/arm/mach-integrator/integrator_ap.c index 30003ba447a580..5b0e363fe5ba52 100644 --- a/arch/arm/mach-integrator/integrator_ap.c +++ b/arch/arm/mach-integrator/integrator_ap.c @@ -37,7 +37,6 @@ #include #include -#include #include #include /* HZ */ #include diff --git a/arch/arm/mach-realview/core.c b/arch/arm/mach-realview/core.c index c309593abdb223..c611f489bdd25c 100644 --- a/arch/arm/mach-realview/core.c +++ b/arch/arm/mach-realview/core.c @@ -38,7 +38,6 @@ #include #include #include -#include #include #include @@ -381,10 +380,10 @@ void __init realview_timer_init(unsigned int timer_irq) /* * Initialise to a known state (all timers off) */ - writel(0, timer0_va_base + TIMER_CTRL); - writel(0, timer1_va_base + TIMER_CTRL); - writel(0, timer2_va_base + TIMER_CTRL); - writel(0, timer3_va_base + TIMER_CTRL); + sp804_timer_disable(timer0_va_base); + sp804_timer_disable(timer1_va_base); + sp804_timer_disable(timer2_va_base); + sp804_timer_disable(timer3_va_base); sp804_clocksource_init(timer3_va_base, "timer3"); sp804_clockevents_init(timer0_va_base, timer_irq, "timer0"); diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index 6ea09fe5342671..f98c1961be6a06 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -42,7 +42,6 @@ #include #include -#include #include #include @@ -798,10 +797,10 @@ void __init versatile_timer_init(void) /* * Initialise to a known state (all timers off) */ - writel(0, TIMER0_VA_BASE + TIMER_CTRL); - writel(0, TIMER1_VA_BASE + TIMER_CTRL); - writel(0, TIMER2_VA_BASE + TIMER_CTRL); - writel(0, TIMER3_VA_BASE + TIMER_CTRL); + sp804_timer_disable(TIMER0_VA_BASE); + sp804_timer_disable(TIMER1_VA_BASE); + sp804_timer_disable(TIMER2_VA_BASE); + sp804_timer_disable(TIMER3_VA_BASE); sp804_clocksource_init(TIMER3_VA_BASE, "timer3"); sp804_clockevents_init(TIMER0_VA_BASE, IRQ_TIMERINT0_1, "timer0"); From e78e0a36b0af91b63b14f0b12cc693329419066c Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Mon, 18 May 2015 13:44:44 +0100 Subject: [PATCH 12/41] ARM: move Dual-Timer SP804 driver to drivers/clocksource The ARM Dual-Timer SP804 module is peripheral found not only on ARM32 platforms but also on ARM64 platforms. This patch moves the driver out of arch/arm to driver/clocksource so that it can be used on ARM64 platforms also. Signed-off-by: Sudeep Holla Cc: Russell King Cc: Daniel Lezcano Cc: Thomas Gleixner Cc: Rob Herring Cc: Arnd Bergmann Cc: Catalin Marinas Cc: Olof Johansson --- arch/arm/Kconfig | 5 ----- arch/arm/common/Makefile | 1 - arch/arm/mach-nspire/nspire.c | 2 -- arch/arm/mach-realview/core.c | 4 ++-- arch/arm/mach-versatile/core.c | 3 ++- drivers/clocksource/Kconfig | 5 +++++ drivers/clocksource/Makefile | 1 + drivers/clocksource/timer-integrator-ap.c | 3 ++- .../hardware/arm_timer.h => drivers/clocksource/timer-sp.h | 5 ----- .../common/timer-sp.c => drivers/clocksource/timer-sp804.c | 7 ++++--- .../timer-sp.h => include/clocksource/timer-sp804.h | 4 ++++ 11 files changed, 20 insertions(+), 20 deletions(-) rename arch/arm/include/asm/hardware/arm_timer.h => drivers/clocksource/timer-sp.h (93%) rename arch/arm/common/timer-sp.c => drivers/clocksource/timer-sp804.c (98%) rename arch/arm/include/asm/hardware/timer-sp.h => include/clocksource/timer-sp804.h (90%) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 45df48ba0b128d..3a1eb1bdac03ff 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -975,11 +975,6 @@ config PLAT_PXA config PLAT_VERSATILE bool -config ARM_TIMER_SP804 - bool - select CLKSRC_MMIO - select CLKSRC_OF if OF - source "arch/arm/firmware/Kconfig" source arch/arm/mm/Kconfig diff --git a/arch/arm/common/Makefile b/arch/arm/common/Makefile index 70b1eff477b3af..6ee5959a813bab 100644 --- a/arch/arm/common/Makefile +++ b/arch/arm/common/Makefile @@ -11,7 +11,6 @@ obj-$(CONFIG_SHARP_LOCOMO) += locomo.o obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o obj-$(CONFIG_SHARP_SCOOP) += scoop.o obj-$(CONFIG_PCI_HOST_ITE8152) += it8152.o -obj-$(CONFIG_ARM_TIMER_SP804) += timer-sp.o obj-$(CONFIG_MCPM) += mcpm_head.o mcpm_entry.o mcpm_platsmp.o vlock.o CFLAGS_REMOVE_mcpm_entry.o = -pg AFLAGS_mcpm_head.o := -march=armv7-a diff --git a/arch/arm/mach-nspire/nspire.c b/arch/arm/mach-nspire/nspire.c index 3445a5686805e0..34c2a1b32e7da2 100644 --- a/arch/arm/mach-nspire/nspire.c +++ b/arch/arm/mach-nspire/nspire.c @@ -22,8 +22,6 @@ #include #include -#include - #include "mmio.h" #include "clcd.h" diff --git a/arch/arm/mach-realview/core.c b/arch/arm/mach-realview/core.c index c611f489bdd25c..44575edc44b13d 100644 --- a/arch/arm/mach-realview/core.c +++ b/arch/arm/mach-realview/core.c @@ -35,6 +35,8 @@ #include #include +#include + #include #include #include @@ -44,10 +46,8 @@ #include #include - #include #include -#include #include diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index f98c1961be6a06..23a04fe5d2add0 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -41,6 +41,8 @@ #include #include +#include + #include #include #include @@ -51,7 +53,6 @@ #include #include #include -#include #include diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 51d7865fdddb6d..0f1c0e7f86daa7 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -132,6 +132,11 @@ config ARM_GLOBAL_TIMER help This options enables support for the ARM global timer unit +config ARM_TIMER_SP804 + bool "Support for Dual Timer SP804 module" + select CLKSRC_MMIO + select CLKSRC_OF if OF + config CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK bool depends on ARM_GLOBAL_TIMER diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 5b85f6adb25834..5ca59f9b377fcf 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_MTK_TIMER) += mtk_timer.o obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o obj-$(CONFIG_ARM_GLOBAL_TIMER) += arm_global_timer.o +obj-$(CONFIG_ARM_TIMER_SP804) += timer-sp804.o obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o obj-$(CONFIG_ARCH_HAS_TICK_BROADCAST) += dummy_timer.o obj-$(CONFIG_ARCH_KEYSTONE) += timer-keystone.o diff --git a/drivers/clocksource/timer-integrator-ap.c b/drivers/clocksource/timer-integrator-ap.c index b9efd30513d562..d7d21e4dcef019 100644 --- a/drivers/clocksource/timer-integrator-ap.c +++ b/drivers/clocksource/timer-integrator-ap.c @@ -26,7 +26,8 @@ #include #include #include -#include + +#include "timer-sp.h" static void __iomem * sched_clk_base; diff --git a/arch/arm/include/asm/hardware/arm_timer.h b/drivers/clocksource/timer-sp.h similarity index 93% rename from arch/arm/include/asm/hardware/arm_timer.h rename to drivers/clocksource/timer-sp.h index d6030ff599db96..050d88561e9c6b 100644 --- a/arch/arm/include/asm/hardware/arm_timer.h +++ b/drivers/clocksource/timer-sp.h @@ -1,6 +1,3 @@ -#ifndef __ASM_ARM_HARDWARE_ARM_TIMER_H -#define __ASM_ARM_HARDWARE_ARM_TIMER_H - /* * ARM timer implementation, found in Integrator, Versatile and Realview * platforms. Not all platforms support all registers and bits in these @@ -31,5 +28,3 @@ #define TIMER_RIS 0x10 /* CVR ro */ #define TIMER_MIS 0x14 /* CVR ro */ #define TIMER_BGLOAD 0x18 /* CVR rw */ - -#endif diff --git a/arch/arm/common/timer-sp.c b/drivers/clocksource/timer-sp804.c similarity index 98% rename from arch/arm/common/timer-sp.c rename to drivers/clocksource/timer-sp804.c index 000aea3722bc2f..ca02503f17d1da 100644 --- a/arch/arm/common/timer-sp.c +++ b/drivers/clocksource/timer-sp804.c @@ -1,5 +1,5 @@ /* - * linux/arch/arm/common/timer-sp.c + * linux/drivers/clocksource/timer-sp.c * * Copyright (C) 1999 - 2003 ARM Limited * Copyright (C) 2000 Deep Blue Solutions Ltd @@ -30,8 +30,9 @@ #include #include -#include -#include +#include + +#include "timer-sp.h" static long __init sp804_get_clock_rate(struct clk *clk) { diff --git a/arch/arm/include/asm/hardware/timer-sp.h b/include/clocksource/timer-sp804.h similarity index 90% rename from arch/arm/include/asm/hardware/timer-sp.h rename to include/clocksource/timer-sp804.h index 05eaefa46742ec..1f8a1caa7cb4d4 100644 --- a/arch/arm/include/asm/hardware/timer-sp.h +++ b/include/clocksource/timer-sp804.h @@ -1,3 +1,6 @@ +#ifndef __CLKSOURCE_TIMER_SP804_H +#define __CLKSOURCE_TIMER_SP804_H + struct clk; void __sp804_clocksource_and_sched_clock_init(void __iomem *, @@ -22,3 +25,4 @@ static inline void sp804_clockevents_init(void __iomem *base, unsigned int irq, __sp804_clockevents_init(base, irq, NULL, name); } +#endif From 618219dbe62f5cd551a3b932e0810b8df1df769d Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Thu, 6 Aug 2015 00:25:22 +0800 Subject: [PATCH 13/41] arm64: Kconfig: select sp804 timer for ARCH_HISI Select sp804 timer for ARCH_HISI, which is used as broadcast timer. Signed-off-by: Leo Yan --- arch/arm64/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index d9129fa78ea1fc..7682e1ff62f289 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -265,6 +265,7 @@ config ARCH_HISI select HI6220_SYSCFG select MFD_HI655X_PMIC select REGULATOR_HI655X + select ARM_TIMER_SP804 help This enables support for Hisilicon ARMv8 Family of SoCs. From 72c941ebbefd270222e4cc345aee876f3d8ee4e5 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 21 Jul 2015 08:47:11 +0800 Subject: [PATCH 14/41] arm64: dts: add cpuidle states Add cpu and cluster level's low power state for cpuidle. Signed-off-by: Leo Yan --- arch/arm64/boot/dts/hisilicon/hi6220.dtsi | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi index fddf37fca0b075..528d332b254840 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi +++ b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi @@ -34,6 +34,7 @@ 208000 0 >; #cooling-cells = <2>; /* min followed by max */ + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu1: cpu@1 { @@ -41,6 +42,7 @@ device_type = "cpu"; reg = <0x0 0x1>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu2: cpu@2 { @@ -48,6 +50,7 @@ device_type = "cpu"; reg = <0x0 0x2>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu3: cpu@3 { @@ -55,6 +58,7 @@ device_type = "cpu"; reg = <0x0 0x3>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu4: cpu@4 { @@ -62,6 +66,7 @@ device_type = "cpu"; reg = <0x0 0x100>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu5: cpu@5 { @@ -69,6 +74,7 @@ device_type = "cpu"; reg = <0x0 0x101>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu6: cpu@6 { @@ -76,6 +82,7 @@ device_type = "cpu"; reg = <0x0 0x102>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu7: cpu@7 { @@ -83,6 +90,7 @@ device_type = "cpu"; reg = <0x0 0x103>; enable-method = "psci"; + cpu-idle-states = <&CPU_SLEEP_0_0 &CLUSTER_SLEEP_0>; }; cpu-map { @@ -115,6 +123,29 @@ }; }; }; + + idle-states { + entry-method = "arm,psci"; + + CPU_SLEEP_0_0: cpu-sleep-0-0 { + compatible = "arm,idle-state"; + local-timer-stop; + arm,psci-suspend-param = <0x0010000>; + entry-latency-us = <250>; + exit-latency-us = <500>; + min-residency-us = <950>; + }; + + CLUSTER_SLEEP_0: cluster-sleep-0 { + compatible = "arm,idle-state"; + local-timer-stop; + arm,psci-suspend-param = <0x1010000>; + entry-latency-us = <600>; + exit-latency-us = <1100>; + min-residency-us = <2700>; + wakeup-latency-us = <1500>; + }; + }; }; gic: interrupt-controller@f6800000 { From 383167d76b0f58b8b7983c8a062a1e23ca7924b4 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Thu, 6 Aug 2015 11:39:18 +0800 Subject: [PATCH 15/41] arm64: dts: add sp804 timer node for hi6220 Add sp804 timer as broadcast timer for hi6220, so can be used by cpuidle. Signed-off-by: Leo Yan --- arch/arm64/boot/dts/hisilicon/hi6220.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi index 528d332b254840..3048fa2fe8dccf 100644 --- a/arch/arm64/boot/dts/hisilicon/hi6220.dtsi +++ b/arch/arm64/boot/dts/hisilicon/hi6220.dtsi @@ -250,6 +250,15 @@ mboxes = <&mailbox 1>; }; + dual_timer0: dual_timer@f8008000 { + compatible = "arm,sp804", "arm,primecell"; + reg = <0x0 0xf8008000 0x0 0x1000>; + interrupts = , + ; + clocks = <&clock_ao 31>, <&clock_ao 31>; + clock-names = "apb_pclk", "apb_pclk"; + }; + uart0: uart@f8015000 { /* console */ compatible = "arm,pl011", "arm,primecell"; reg = <0x0 0xf8015000 0x0 0x1000>; From 5f00bd7b554909847ba6b90a581d5d513f394b35 Mon Sep 17 00:00:00 2001 From: Grygorii Strashko Date: Thu, 2 Jul 2015 18:12:33 +0100 Subject: [PATCH 16/41] ARM: psci: boot_secondary: replace __pa with virt_to_idmap On some PAE systems (e.g. TI Keystone), memory is above the 32-bit addressable limit, and the interconnect provides an aliased view of parts of physical memory in the 32-bit addressable space. This alias is strictly for boot time usage, and is not otherwise usable because of coherency limitations. In this case, virt_to_phys(secondary_startup) would return the physical address of the secondary CPU boot entry point, but on such systems, this would be above the 4GB limit. A separate function, virt_to_idmap(), has been provided to return a usable physical address for functions in the identity mapping, and this must be used in preference to virt_to_phys() or __pa() to find the physical entry point for functions in the identity mapping range. For other systems, virt_to_idmap() and virt_to_phys() return identical physical addresses. Cc: Russell King Acked-by: Santosh Shilimkar Acked-by: Nicolas Pitre Tested-by Vitaly Andrianov Signed-off-by: Grygorii Strashko [Mark: apply rmk's suggested rewording] Signed-off-by: Mark Rutland --- arch/arm/kernel/psci_smp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/kernel/psci_smp.c b/arch/arm/kernel/psci_smp.c index 28a1db4da70428..244aaddfbfda00 100644 --- a/arch/arm/kernel/psci_smp.c +++ b/arch/arm/kernel/psci_smp.c @@ -51,7 +51,7 @@ static int psci_boot_secondary(unsigned int cpu, struct task_struct *idle) { if (psci_ops.cpu_on) return psci_ops.cpu_on(cpu_logical_map(cpu), - __pa(secondary_startup)); + virt_to_idmap(&secondary_startup)); return -ENODEV; } From 30093d914f552fe0e9ac95ebf544cfd0741a1c1e Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 13 May 2015 14:12:46 +0100 Subject: [PATCH 17/41] ARM64: kernel: make cpu_ops hooks DT agnostic ARM64 CPU operations such as cpu_init and cpu_init_idle take a struct device_node pointer as a parameter, which corresponds to the device tree node of the logical cpu on which the operation has to be applied. With the advent of ACPI on arm64, where MADT static table entries are used to initialize cpus, the device tree node parameter in cpu_ops hooks become useless when booting with ACPI, since in that case cpu device tree nodes are not present and can not be used for cpu initialization. The current cpu_init hook requires a struct device_node pointer parameter because it is called while parsing the device tree to initialize CPUs, when the cpu_logical_map (that is used to match a cpu node reg property to a device tree node) for a given logical cpu id is not set up yet. This means that the cpu_init hook cannot rely on the of_get_cpu_node function to retrieve the device tree node corresponding to the logical cpu id passed in as parameter, so the cpu device tree node must be passed in as a parameter to fix this catch-22 dependency cycle. This patch reshuffles the cpu_logical_map initialization code so that the cpu_init cpu_ops hook can safely use the of_get_cpu_node function to retrieve the cpu device tree node, removing the need for the device tree node pointer parameter. In the process, the patch removes device tree node parameters from all cpu_ops hooks, in preparation for SMP DT/ACPI cpus initialization consolidation. Signed-off-by: Lorenzo Pieralisi Acked-by: Hanjun Guo Acked-by: Sudeep Holla Acked-by: Mark Rutland Tested-by: Hanjun Guo Tested-by: Mark Rutland [DT] Cc: Will Deacon Cc: Catalin Marinas --- arch/arm64/include/asm/cpu_ops.h | 26 +++++++++++--------- arch/arm64/kernel/acpi.c | 2 +- arch/arm64/kernel/cpu_ops.c | 23 +++++++++--------- arch/arm64/kernel/cpuidle.c | 7 +----- arch/arm64/kernel/psci.c | 11 ++++++--- arch/arm64/kernel/smp.c | 39 +++++++++++++++++++++--------- arch/arm64/kernel/smp_spin_table.c | 8 +++++- 7 files changed, 70 insertions(+), 46 deletions(-) diff --git a/arch/arm64/include/asm/cpu_ops.h b/arch/arm64/include/asm/cpu_ops.h index 5a31d671691439..7d8b999d1a2b65 100644 --- a/arch/arm64/include/asm/cpu_ops.h +++ b/arch/arm64/include/asm/cpu_ops.h @@ -19,15 +19,15 @@ #include #include -struct device_node; - /** * struct cpu_operations - Callback operations for hotplugging CPUs. * * @name: Name of the property as appears in a devicetree cpu node's - * enable-method property. - * @cpu_init: Reads any data necessary for a specific enable-method from the - * devicetree, for a given cpu node and proposed logical id. + * enable-method property. On systems booting with ACPI, @name + * identifies the struct cpu_operations entry corresponding to + * the boot protocol specified in the ACPI MADT table. + * @cpu_init: Reads any data necessary for a specific enable-method for a + * proposed logical id. * @cpu_prepare: Early one-time preparation step for a cpu. If there is a * mechanism for doing so, tests whether it is possible to boot * the given CPU. @@ -40,15 +40,15 @@ struct device_node; * @cpu_die: Makes a cpu leave the kernel. Must not fail. Called from the * cpu being killed. * @cpu_kill: Ensures a cpu has left the kernel. Called from another cpu. - * @cpu_init_idle: Reads any data necessary to initialize CPU idle states from - * devicetree, for a given cpu node and proposed logical id. + * @cpu_init_idle: Reads any data necessary to initialize CPU idle states for + * a proposed logical id. * @cpu_suspend: Suspends a cpu and saves the required context. May fail owing * to wrong parameters or error conditions. Called from the * CPU being suspended. Must be called with IRQs disabled. */ struct cpu_operations { const char *name; - int (*cpu_init)(struct device_node *, unsigned int); + int (*cpu_init)(unsigned int); int (*cpu_prepare)(unsigned int); int (*cpu_boot)(unsigned int); void (*cpu_postboot)(void); @@ -58,14 +58,18 @@ struct cpu_operations { int (*cpu_kill)(unsigned int cpu); #endif #ifdef CONFIG_CPU_IDLE - int (*cpu_init_idle)(struct device_node *, unsigned int); + int (*cpu_init_idle)(unsigned int); int (*cpu_suspend)(unsigned long); #endif }; extern const struct cpu_operations *cpu_ops[NR_CPUS]; -int __init cpu_read_ops(struct device_node *dn, int cpu); -void __init cpu_read_bootcpu_ops(void); +int __init cpu_read_ops(int cpu); const struct cpu_operations *cpu_get_ops(const char *name); +static inline void __init cpu_read_bootcpu_ops(void) +{ + cpu_read_ops(0); +} + #endif /* ifndef __ASM_CPU_OPS_H */ diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 8b839558838e5a..945cc1fb3c78c8 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -154,7 +154,7 @@ acpi_map_gic_cpu_interface(struct acpi_madt_generic_interrupt *processor) if (!cpu_ops[enabled_cpus]) return; - if (cpu_ops[enabled_cpus]->cpu_init(NULL, enabled_cpus)) + if (cpu_ops[enabled_cpus]->cpu_init(enabled_cpus)) return; /* map the logical cpu id to cpu MPIDR */ diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c index fb8ff9ba467a97..bbda2d267ea66a 100644 --- a/arch/arm64/kernel/cpu_ops.c +++ b/arch/arm64/kernel/cpu_ops.c @@ -52,9 +52,18 @@ const struct cpu_operations * __init cpu_get_ops(const char *name) /* * Read a cpu's enable method from the device tree and record it in cpu_ops. */ -int __init cpu_read_ops(struct device_node *dn, int cpu) +int __init cpu_read_ops(int cpu) { - const char *enable_method = of_get_property(dn, "enable-method", NULL); + const char *enable_method; + struct device_node *dn = of_get_cpu_node(cpu, NULL); + + if (!dn) { + if (!cpu) + pr_err("Failed to find device node for boot cpu\n"); + return -ENODEV; + } + + enable_method = of_get_property(dn, "enable-method", NULL); if (!enable_method) { /* * The boot CPU may not have an enable method (e.g. when @@ -75,13 +84,3 @@ int __init cpu_read_ops(struct device_node *dn, int cpu) return 0; } - -void __init cpu_read_bootcpu_ops(void) -{ - struct device_node *dn = of_get_cpu_node(0, NULL); - if (!dn) { - pr_err("Failed to find device node for boot cpu\n"); - return; - } - cpu_read_ops(dn, 0); -} diff --git a/arch/arm64/kernel/cpuidle.c b/arch/arm64/kernel/cpuidle.c index a78143a5c99ffb..f8aa16973318f5 100644 --- a/arch/arm64/kernel/cpuidle.c +++ b/arch/arm64/kernel/cpuidle.c @@ -18,15 +18,10 @@ int arm_cpuidle_init(unsigned int cpu) { int ret = -EOPNOTSUPP; - struct device_node *cpu_node = of_cpu_device_node_get(cpu); - - if (!cpu_node) - return -ENODEV; if (cpu_ops[cpu] && cpu_ops[cpu]->cpu_init_idle) - ret = cpu_ops[cpu]->cpu_init_idle(cpu_node, cpu); + ret = cpu_ops[cpu]->cpu_init_idle(cpu); - of_node_put(cpu_node); return ret; } diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index ea18cb53921e8f..efe3480a97a965 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -186,12 +186,15 @@ static int psci_migrate_info_type(void) return err; } -static int __maybe_unused cpu_psci_cpu_init_idle(struct device_node *cpu_node, - unsigned int cpu) +static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) { int i, ret, count = 0; struct psci_power_state *psci_states; - struct device_node *state_node; + struct device_node *state_node, *cpu_node; + + cpu_node = of_get_cpu_node(cpu, NULL); + if (!cpu_node) + return -ENODEV; /* * If the PSCI cpu_suspend function hook has not been initialized @@ -444,7 +447,7 @@ int __init psci_acpi_init(void) #ifdef CONFIG_SMP -static int __init cpu_psci_cpu_init(struct device_node *dn, unsigned int cpu) +static int __init cpu_psci_cpu_init(unsigned int cpu) { return 0; } diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 2cb008177252f0..98eb68b5660fa9 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -318,6 +318,23 @@ void __init smp_prepare_boot_cpu(void) set_my_cpu_offset(per_cpu_offset(smp_processor_id())); } +/* + * Initialize cpu operations for a logical cpu and + * set it in the possible mask on success + */ +static int __init smp_cpu_setup(int cpu) +{ + if (cpu_read_ops(cpu)) + return -ENODEV; + + if (cpu_ops[cpu]->cpu_init(cpu)) + return -ENODEV; + + set_cpu_possible(cpu, true); + + return 0; +} + /* * Enumerate the possible CPU set from the device tree and build the * cpu logical map array containing MPIDR values related to logical @@ -395,12 +412,6 @@ void __init of_smp_init_cpus(void) if (cpu >= NR_CPUS) goto next; - if (cpu_read_ops(dn, cpu) != 0) - goto next; - - if (cpu_ops[cpu]->cpu_init(dn, cpu)) - goto next; - pr_debug("cpu logical map 0x%llx\n", hwid); cpu_logical_map(cpu) = hwid; next: @@ -418,12 +429,18 @@ void __init of_smp_init_cpus(void) } /* - * All the cpus that made it to the cpu_logical_map have been - * validated so set them as possible cpus. + * We need to set the cpu_logical_map entries before enabling + * the cpus so that cpu processor description entries (DT cpu nodes + * and ACPI MADT entries) can be retrieved by matching the cpu hwid + * with entries in cpu_logical_map while initializing the cpus. + * If the cpu set-up fails, invalidate the cpu_logical_map entry. */ - for (i = 0; i < NR_CPUS; i++) - if (cpu_logical_map(i) != INVALID_HWID) - set_cpu_possible(i, true); + for (i = 1; i < NR_CPUS; i++) { + if (cpu_logical_map(i) != INVALID_HWID) { + if (smp_cpu_setup(i)) + cpu_logical_map(i) = INVALID_HWID; + } + } } void __init smp_prepare_cpus(unsigned int max_cpus) diff --git a/arch/arm64/kernel/smp_spin_table.c b/arch/arm64/kernel/smp_spin_table.c index 14944e5b28dace..aef3605a8c4784 100644 --- a/arch/arm64/kernel/smp_spin_table.c +++ b/arch/arm64/kernel/smp_spin_table.c @@ -49,8 +49,14 @@ static void write_pen_release(u64 val) } -static int smp_spin_table_cpu_init(struct device_node *dn, unsigned int cpu) +static int smp_spin_table_cpu_init(unsigned int cpu) { + struct device_node *dn; + + dn = of_get_cpu_node(cpu, NULL); + if (!dn) + return -ENODEV; + /* * Determine the address from which the CPU is polling. */ From 2ad0a21f93ebb081b0cc24bc623ecba027693738 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 13 May 2015 14:12:47 +0100 Subject: [PATCH 18/41] ARM64: kernel: unify ACPI and DT cpus initialization The code that initializes cpus on arm64 is currently split in two different code paths that carry out DT and ACPI cpus initialization. Most of the code executing SMP initialization is common and should be merged to reduce discrepancies between ACPI and DT initialization and to have code initializing cpus in a single common place in the kernel. This patch refactors arm64 SMP cpus initialization code to merge ACPI and DT boot paths in a common file and to create sanity checks that can be reused by both boot methods. Current code assumes PSCI is the only available boot method when arm64 boots with ACPI; this can be easily extended if/when the ACPI parking protocol is merged into the kernel. Signed-off-by: Sudeep Holla Signed-off-by: Lorenzo Pieralisi Acked-by: Hanjun Guo Acked-by: Mark Rutland Tested-by: Hanjun Guo Tested-by: Mark Rutland [DT] Cc: Will Deacon Cc: Catalin Marinas --- arch/arm64/include/asm/acpi.h | 4 + arch/arm64/include/asm/cpu_ops.h | 1 - arch/arm64/include/asm/smp.h | 2 +- arch/arm64/kernel/acpi.c | 124 -------------------- arch/arm64/kernel/cpu_ops.c | 65 +++++++---- arch/arm64/kernel/setup.c | 7 +- arch/arm64/kernel/smp.c | 191 ++++++++++++++++++++++++------- 7 files changed, 196 insertions(+), 198 deletions(-) diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 59c05d8ea4a000..1240b867d4a2b4 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -93,4 +93,8 @@ static inline bool acpi_psci_use_hvc(void) { return false; } static inline void acpi_init_cpus(void) { } #endif /* CONFIG_ACPI */ +static inline const char *acpi_get_enable_method(int cpu) +{ + return acpi_psci_present() ? "psci" : NULL; +} #endif /*_ASM_ACPI_H*/ diff --git a/arch/arm64/include/asm/cpu_ops.h b/arch/arm64/include/asm/cpu_ops.h index 7d8b999d1a2b65..8f03446cf89f11 100644 --- a/arch/arm64/include/asm/cpu_ops.h +++ b/arch/arm64/include/asm/cpu_ops.h @@ -65,7 +65,6 @@ struct cpu_operations { extern const struct cpu_operations *cpu_ops[NR_CPUS]; int __init cpu_read_ops(int cpu); -const struct cpu_operations *cpu_get_ops(const char *name); static inline void __init cpu_read_bootcpu_ops(void) { diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h index bf22650b1a7827..db02be81b90a1c 100644 --- a/arch/arm64/include/asm/smp.h +++ b/arch/arm64/include/asm/smp.h @@ -42,7 +42,7 @@ extern void handle_IPI(int ipinr, struct pt_regs *regs); * Discover the set of possible CPUs and determine their * SMP operations. */ -extern void of_smp_init_cpus(void); +extern void smp_init_cpus(void); /* * Provide a function to raise an IPI cross call on CPUs in callmap. diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 945cc1fb3c78c8..732f57b8731bd6 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -36,12 +36,6 @@ EXPORT_SYMBOL(acpi_disabled); int acpi_pci_disabled = 1; /* skip ACPI PCI scan and IRQ initialization */ EXPORT_SYMBOL(acpi_pci_disabled); -/* Processors with enabled flag and sane MPIDR */ -static int enabled_cpus; - -/* Boot CPU is valid or not in MADT */ -static bool bootcpu_valid __initdata; - static bool param_acpi_off __initdata; static bool param_acpi_force __initdata; @@ -95,124 +89,6 @@ void __init __acpi_unmap_table(char *map, unsigned long size) early_memunmap(map, size); } -/** - * acpi_map_gic_cpu_interface - generates a logical cpu number - * and map to MPIDR represented by GICC structure - */ -static void __init -acpi_map_gic_cpu_interface(struct acpi_madt_generic_interrupt *processor) -{ - int i; - u64 mpidr = processor->arm_mpidr & MPIDR_HWID_BITMASK; - bool enabled = !!(processor->flags & ACPI_MADT_ENABLED); - - if (mpidr == INVALID_HWID) { - pr_info("Skip MADT cpu entry with invalid MPIDR\n"); - return; - } - - total_cpus++; - if (!enabled) - return; - - if (enabled_cpus >= NR_CPUS) { - pr_warn("NR_CPUS limit of %d reached, Processor %d/0x%llx ignored.\n", - NR_CPUS, total_cpus, mpidr); - return; - } - - /* Check if GICC structure of boot CPU is available in the MADT */ - if (cpu_logical_map(0) == mpidr) { - if (bootcpu_valid) { - pr_err("Firmware bug, duplicate CPU MPIDR: 0x%llx in MADT\n", - mpidr); - return; - } - - bootcpu_valid = true; - } - - /* - * Duplicate MPIDRs are a recipe for disaster. Scan - * all initialized entries and check for - * duplicates. If any is found just ignore the CPU. - */ - for (i = 1; i < enabled_cpus; i++) { - if (cpu_logical_map(i) == mpidr) { - pr_err("Firmware bug, duplicate CPU MPIDR: 0x%llx in MADT\n", - mpidr); - return; - } - } - - if (!acpi_psci_present()) - return; - - cpu_ops[enabled_cpus] = cpu_get_ops("psci"); - /* CPU 0 was already initialized */ - if (enabled_cpus) { - if (!cpu_ops[enabled_cpus]) - return; - - if (cpu_ops[enabled_cpus]->cpu_init(enabled_cpus)) - return; - - /* map the logical cpu id to cpu MPIDR */ - cpu_logical_map(enabled_cpus) = mpidr; - } - - enabled_cpus++; -} - -static int __init -acpi_parse_gic_cpu_interface(struct acpi_subtable_header *header, - const unsigned long end) -{ - struct acpi_madt_generic_interrupt *processor; - - processor = (struct acpi_madt_generic_interrupt *)header; - - if (BAD_MADT_ENTRY(processor, end)) - return -EINVAL; - - acpi_table_print_madt_entry(header); - acpi_map_gic_cpu_interface(processor); - return 0; -} - -/* Parse GIC cpu interface entries in MADT for SMP init */ -void __init acpi_init_cpus(void) -{ - int count, i; - - /* - * do a partial walk of MADT to determine how many CPUs - * we have including disabled CPUs, and get information - * we need for SMP init - */ - count = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT, - acpi_parse_gic_cpu_interface, 0); - - if (!count) { - pr_err("No GIC CPU interface entries present\n"); - return; - } else if (count < 0) { - pr_err("Error parsing GIC CPU interface entry\n"); - return; - } - - if (!bootcpu_valid) { - pr_err("MADT missing boot CPU MPIDR, not enabling secondaries\n"); - return; - } - - for (i = 0; i < enabled_cpus; i++) - set_cpu_possible(i, true); - - /* Make boot-up look pretty */ - pr_info("%d CPUs enabled, %d CPUs total\n", enabled_cpus, total_cpus); -} - /* * acpi_fadt_sanity_check() - Check FADT presence and carry out sanity * checks on it diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c index bbda2d267ea66a..5ea337dd2f15d2 100644 --- a/arch/arm64/kernel/cpu_ops.c +++ b/arch/arm64/kernel/cpu_ops.c @@ -16,11 +16,13 @@ * along with this program. If not, see . */ -#include -#include +#include #include #include #include +#include +#include +#include extern const struct cpu_operations smp_spin_table_ops; extern const struct cpu_operations cpu_psci_ops; @@ -35,7 +37,7 @@ static const struct cpu_operations *supported_cpu_ops[] __initconst = { NULL, }; -const struct cpu_operations * __init cpu_get_ops(const char *name) +static const struct cpu_operations * __init cpu_get_ops(const char *name) { const struct cpu_operations **ops = supported_cpu_ops; @@ -49,36 +51,51 @@ const struct cpu_operations * __init cpu_get_ops(const char *name) return NULL; } +static const char *__init cpu_read_enable_method(int cpu) +{ + const char *enable_method; + + if (acpi_disabled) { + struct device_node *dn = of_get_cpu_node(cpu, NULL); + + if (!dn) { + if (!cpu) + pr_err("Failed to find device node for boot cpu\n"); + return NULL; + } + + enable_method = of_get_property(dn, "enable-method", NULL); + if (!enable_method) { + /* + * The boot CPU may not have an enable method (e.g. + * when spin-table is used for secondaries). + * Don't warn spuriously. + */ + if (cpu != 0) + pr_err("%s: missing enable-method property\n", + dn->full_name); + } + } else { + enable_method = acpi_get_enable_method(cpu); + if (!enable_method) + pr_err("Unsupported ACPI enable-method\n"); + } + + return enable_method; +} /* - * Read a cpu's enable method from the device tree and record it in cpu_ops. + * Read a cpu's enable method and record it in cpu_ops. */ int __init cpu_read_ops(int cpu) { - const char *enable_method; - struct device_node *dn = of_get_cpu_node(cpu, NULL); + const char *enable_method = cpu_read_enable_method(cpu); - if (!dn) { - if (!cpu) - pr_err("Failed to find device node for boot cpu\n"); + if (!enable_method) return -ENODEV; - } - - enable_method = of_get_property(dn, "enable-method", NULL); - if (!enable_method) { - /* - * The boot CPU may not have an enable method (e.g. when - * spin-table is used for secondaries). Don't warn spuriously. - */ - if (cpu != 0) - pr_err("%s: missing enable-method property\n", - dn->full_name); - return -ENOENT; - } cpu_ops[cpu] = cpu_get_ops(enable_method); if (!cpu_ops[cpu]) { - pr_warn("%s: unsupported enable-method property: %s\n", - dn->full_name, enable_method); + pr_warn("Unsupported enable-method: %s\n", enable_method); return -EOPNOTSUPP; } diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 74753132c3ac8f..508cca1f8dce27 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -408,16 +408,13 @@ void __init setup_arch(char **cmdline_p) if (acpi_disabled) { unflatten_device_tree(); psci_dt_init(); - cpu_read_bootcpu_ops(); -#ifdef CONFIG_SMP - of_smp_init_cpus(); -#endif } else { psci_acpi_init(); - acpi_init_cpus(); } + cpu_read_bootcpu_ops(); #ifdef CONFIG_SMP + smp_init_cpus(); smp_build_mpidr_hash(); #endif diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 98eb68b5660fa9..b698208848e9c4 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -17,6 +17,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -318,6 +319,49 @@ void __init smp_prepare_boot_cpu(void) set_my_cpu_offset(per_cpu_offset(smp_processor_id())); } +static u64 __init of_get_cpu_mpidr(struct device_node *dn) +{ + const __be32 *cell; + u64 hwid; + + /* + * A cpu node with missing "reg" property is + * considered invalid to build a cpu_logical_map + * entry. + */ + cell = of_get_property(dn, "reg", NULL); + if (!cell) { + pr_err("%s: missing reg property\n", dn->full_name); + return INVALID_HWID; + } + + hwid = of_read_number(cell, of_n_addr_cells(dn)); + /* + * Non affinity bits must be set to 0 in the DT + */ + if (hwid & ~MPIDR_HWID_BITMASK) { + pr_err("%s: invalid reg property\n", dn->full_name); + return INVALID_HWID; + } + return hwid; +} + +/* + * Duplicate MPIDRs are a recipe for disaster. Scan all initialized + * entries and check for duplicates. If any is found just ignore the + * cpu. cpu_logical_map was initialized to INVALID_HWID to avoid + * matching valid MPIDR values. + */ +static bool __init is_mpidr_duplicate(unsigned int cpu, u64 hwid) +{ + unsigned int i; + + for (i = 1; (i < cpu) && (i < NR_CPUS); i++) + if (cpu_logical_map(i) == hwid) + return true; + return false; +} + /* * Initialize cpu operations for a logical cpu and * set it in the possible mask on success @@ -335,56 +379,97 @@ static int __init smp_cpu_setup(int cpu) return 0; } +static bool bootcpu_valid __initdata; +static unsigned int cpu_count = 1; + +#ifdef CONFIG_ACPI +/* + * acpi_map_gic_cpu_interface - parse processor MADT entry + * + * Carry out sanity checks on MADT processor entry and initialize + * cpu_logical_map on success + */ +static void __init +acpi_map_gic_cpu_interface(struct acpi_madt_generic_interrupt *processor) +{ + u64 hwid = processor->arm_mpidr; + + if (hwid & ~MPIDR_HWID_BITMASK || hwid == INVALID_HWID) { + pr_err("skipping CPU entry with invalid MPIDR 0x%llx\n", hwid); + return; + } + + if (!(processor->flags & ACPI_MADT_ENABLED)) { + pr_err("skipping disabled CPU entry with 0x%llx MPIDR\n", hwid); + return; + } + + if (is_mpidr_duplicate(cpu_count, hwid)) { + pr_err("duplicate CPU MPIDR 0x%llx in MADT\n", hwid); + return; + } + + /* Check if GICC structure of boot CPU is available in the MADT */ + if (cpu_logical_map(0) == hwid) { + if (bootcpu_valid) { + pr_err("duplicate boot CPU MPIDR: 0x%llx in MADT\n", + hwid); + return; + } + bootcpu_valid = true; + return; + } + + if (cpu_count >= NR_CPUS) + return; + + /* map the logical cpu id to cpu MPIDR */ + cpu_logical_map(cpu_count) = hwid; + + cpu_count++; +} + +static int __init +acpi_parse_gic_cpu_interface(struct acpi_subtable_header *header, + const unsigned long end) +{ + struct acpi_madt_generic_interrupt *processor; + + processor = (struct acpi_madt_generic_interrupt *)header; + if (BAD_MADT_ENTRY(processor, end)) + return -EINVAL; + + acpi_table_print_madt_entry(header); + + acpi_map_gic_cpu_interface(processor); + + return 0; +} +#else +#define acpi_table_parse_madt(...) do { } while (0) +#endif + /* * Enumerate the possible CPU set from the device tree and build the * cpu logical map array containing MPIDR values related to logical * cpus. Assumes that cpu_logical_map(0) has already been initialized. */ -void __init of_smp_init_cpus(void) +void __init of_parse_and_init_cpus(void) { struct device_node *dn = NULL; - unsigned int i, cpu = 1; - bool bootcpu_valid = false; while ((dn = of_find_node_by_type(dn, "cpu"))) { - const u32 *cell; - u64 hwid; + u64 hwid = of_get_cpu_mpidr(dn); - /* - * A cpu node with missing "reg" property is - * considered invalid to build a cpu_logical_map - * entry. - */ - cell = of_get_property(dn, "reg", NULL); - if (!cell) { - pr_err("%s: missing reg property\n", dn->full_name); + if (hwid == INVALID_HWID) goto next; - } - hwid = of_read_number(cell, of_n_addr_cells(dn)); - /* - * Non affinity bits must be set to 0 in the DT - */ - if (hwid & ~MPIDR_HWID_BITMASK) { - pr_err("%s: invalid reg property\n", dn->full_name); + if (is_mpidr_duplicate(cpu_count, hwid)) { + pr_err("%s: duplicate cpu reg properties in the DT\n", + dn->full_name); goto next; } - /* - * Duplicate MPIDRs are a recipe for disaster. Scan - * all initialized entries and check for - * duplicates. If any is found just ignore the cpu. - * cpu_logical_map was initialized to INVALID_HWID to - * avoid matching valid MPIDR values. - */ - for (i = 1; (i < cpu) && (i < NR_CPUS); i++) { - if (cpu_logical_map(i) == hwid) { - pr_err("%s: duplicate cpu reg properties in the DT\n", - dn->full_name); - goto next; - } - } - /* * The numbering scheme requires that the boot CPU * must be assigned logical id 0. Record it so that @@ -409,22 +494,42 @@ void __init of_smp_init_cpus(void) continue; } - if (cpu >= NR_CPUS) + if (cpu_count >= NR_CPUS) goto next; pr_debug("cpu logical map 0x%llx\n", hwid); - cpu_logical_map(cpu) = hwid; + cpu_logical_map(cpu_count) = hwid; next: - cpu++; + cpu_count++; } +} + +/* + * Enumerate the possible CPU set from the device tree or ACPI and build the + * cpu logical map array containing MPIDR values related to logical + * cpus. Assumes that cpu_logical_map(0) has already been initialized. + */ +void __init smp_init_cpus(void) +{ + int i; + + if (acpi_disabled) + of_parse_and_init_cpus(); + else + /* + * do a walk of MADT to determine how many CPUs + * we have including disabled CPUs, and get information + * we need for SMP init + */ + acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT, + acpi_parse_gic_cpu_interface, 0); - /* sanity check */ - if (cpu > NR_CPUS) - pr_warning("no. of cores (%d) greater than configured maximum of %d - clipping\n", - cpu, NR_CPUS); + if (cpu_count > NR_CPUS) + pr_warn("no. of cores (%d) greater than configured maximum of %d - clipping\n", + cpu_count, NR_CPUS); if (!bootcpu_valid) { - pr_err("DT missing boot CPU MPIDR, not enabling secondaries\n"); + pr_err("missing boot CPU MPIDR, not enabling secondaries\n"); return; } From b465f2aa8af581e38e582b81c88e8b1a8fe18c9e Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Fri, 1 May 2015 10:53:39 +0100 Subject: [PATCH 19/41] arm/arm64: kvm: add missing PSCI include We make use of the PSCI function IDs, but don't explicitly include the header which defines them. Relying on transitive header includes is fragile and will be broken as headers are refactored. This patch includes the relevant header file directly so as to avoid future breakage. Signed-off-by: Mark Rutland Acked-by: Christoffer Dall Reviewed-by: Hanjun Guo Tested-by: Hanjun Guo Cc: Marc Zyngier --- arch/arm/kvm/psci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/kvm/psci.c b/arch/arm/kvm/psci.c index 02fa8eff6ae1d5..7e9398c6b38780 100644 --- a/arch/arm/kvm/psci.c +++ b/arch/arm/kvm/psci.c @@ -24,6 +24,8 @@ #include #include +#include + /* * This is an implementation of the Power State Coordination Interface * as described in ARM document number ARM DEN 0022A. From d739be82c2a7b72e75279a34fc2ca38b9065d3e2 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Wed, 22 Apr 2015 17:26:18 +0100 Subject: [PATCH 20/41] arm64: smp_plat: add get_logical_index The PSCI MIGRATE_INFO_UP_CPU call returns a physical ID, which we will need to map back to a Linux logical ID. Implement a reusable get_logical_index to map from a physical ID to a logical ID. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Acked-by: Lorenzo Pieralisi Reviewed-by: Hanjun Guo Tested-by: Hanjun Guo Cc: Will Deacon --- arch/arm64/include/asm/smp_plat.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/include/asm/smp_plat.h b/arch/arm64/include/asm/smp_plat.h index 8dcd61e32176b4..7abf7570c00f8e 100644 --- a/arch/arm64/include/asm/smp_plat.h +++ b/arch/arm64/include/asm/smp_plat.h @@ -19,6 +19,8 @@ #ifndef __ASM_SMP_PLAT_H #define __ASM_SMP_PLAT_H +#include + #include struct mpidr_hash { @@ -39,6 +41,20 @@ static inline u32 mpidr_hash_size(void) */ extern u64 __cpu_logical_map[NR_CPUS]; #define cpu_logical_map(cpu) __cpu_logical_map[cpu] +/* + * Retrieve logical cpu index corresponding to a given MPIDR.Aff* + * - mpidr: MPIDR.Aff* bits to be used for the look-up + * + * Returns the cpu logical index or -EINVAL on look-up error + */ +static inline int get_logical_index(u64 mpidr) +{ + int cpu; + for (cpu = 0; cpu < nr_cpu_ids; cpu++) + if (cpu_logical_map(cpu) == mpidr) + return cpu; + return -EINVAL; +} void __init do_post_cpus_up_work(void); From 6a67916caf6df13f86db0e3502d938b906c8d4fc Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 20 Apr 2015 17:55:30 +0100 Subject: [PATCH 21/41] arm64: smp: consistently use error codes cpu_kill currently returns one for success and zero for failure, which is unlike all the other cpu_operations, which return zero for success and an error code upon failure. This difference is unnecessarily confusing. Make cpu_kill consistent with the other cpu_operations. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Acked-by: Lorenzo Pieralisi Reviewed-by: Hanjun Guo Tested-by: Hanjun Guo Cc: Will Deacon --- arch/arm64/kernel/psci.c | 7 +++---- arch/arm64/kernel/smp.c | 10 +++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index efe3480a97a965..17e717c4b1ef3b 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -501,7 +501,7 @@ static int cpu_psci_cpu_kill(unsigned int cpu) int err, i; if (!psci_ops.affinity_info) - return 1; + return 0; /* * cpu_kill could race with cpu_die and we can * potentially end up declaring this cpu undead @@ -512,7 +512,7 @@ static int cpu_psci_cpu_kill(unsigned int cpu) err = psci_ops.affinity_info(cpu_logical_map(cpu), 0); if (err == PSCI_0_2_AFFINITY_LEVEL_OFF) { pr_info("CPU%d killed.\n", cpu); - return 1; + return 0; } msleep(10); @@ -521,8 +521,7 @@ static int cpu_psci_cpu_kill(unsigned int cpu) pr_warn("CPU%d may not have shut down cleanly (AFFINITY_INFO reports %d)\n", cpu, err); - /* Make op_cpu_kill() fail. */ - return 0; + return -ETIMEDOUT; } #endif #endif diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index b698208848e9c4..2b503a3186dd48 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -249,7 +249,7 @@ static int op_cpu_kill(unsigned int cpu) * time and hope that it's dead, so let's skip the wait and just hope. */ if (!cpu_ops[cpu]->cpu_kill) - return 1; + return 0; return cpu_ops[cpu]->cpu_kill(cpu); } @@ -262,6 +262,8 @@ static DECLARE_COMPLETION(cpu_died); */ void __cpu_die(unsigned int cpu) { + int err; + if (!wait_for_completion_timeout(&cpu_died, msecs_to_jiffies(5000))) { pr_crit("CPU%u: cpu didn't die\n", cpu); return; @@ -274,8 +276,10 @@ void __cpu_die(unsigned int cpu) * verify that it has really left the kernel before we consider * clobbering anything it might still be using. */ - if (!op_cpu_kill(cpu)) - pr_warn("CPU%d may not have shut down cleanly\n", cpu); + err = op_cpu_kill(cpu); + if (err) + pr_warn("CPU%d may not have shut down cleanly: %d\n", + cpu, err); } /* From 772b9d790c948990533a0a29c5c25300c563d2ca Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Wed, 22 Apr 2015 16:22:55 +0100 Subject: [PATCH 22/41] arm64: psci: remove unnecessary id indirection PSCI 0.1 did not define canonical IDs for CPU_ON, CPU_OFF, CPU_SUSPEND, or MIGRATE, and so these need to be provided when using firmware compliant to PSCI 0.1. However, functions introduced in 0.2 or later have canonical IDs, and these cannot be provided via DT. There's no need to indirect the IDs via a table; they can be used directly at callsites (and already are for SYSTEM_OFF and SYSTEM_RESET). This patch removes the unnecessary function ID indirection for AFFINITY_INFO and MIGRATE_INFO_TYPE. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Acked-by: Lorenzo Pieralisi Reviewed-by: Hanjun Guo Tested-by: Hanjun Guo Cc: Will Deacon --- arch/arm64/kernel/psci.c | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 17e717c4b1ef3b..0c1efb662a71a7 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -67,8 +67,6 @@ enum psci_function { PSCI_FN_CPU_ON, PSCI_FN_CPU_OFF, PSCI_FN_MIGRATE, - PSCI_FN_AFFINITY_INFO, - PSCI_FN_MIGRATE_INFO_TYPE, PSCI_FN_MAX, }; @@ -168,22 +166,13 @@ static int psci_migrate(unsigned long cpuid) static int psci_affinity_info(unsigned long target_affinity, unsigned long lowest_affinity_level) { - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_AFFINITY_INFO]; - err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0); - return err; + return invoke_psci_fn(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, + lowest_affinity_level, 0); } static int psci_migrate_info_type(void) { - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE]; - err = invoke_psci_fn(fn, 0, 0, 0); - return err; + return invoke_psci_fn(PSCI_0_2_FN_MIGRATE_INFO_TYPE, 0, 0, 0); } static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) @@ -293,11 +282,8 @@ static void __init psci_0_2_set_functions(void) psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN64_MIGRATE; psci_ops.migrate = psci_migrate; - psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN64_AFFINITY_INFO; psci_ops.affinity_info = psci_affinity_info; - psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] = - PSCI_0_2_FN_MIGRATE_INFO_TYPE; psci_ops.migrate_info_type = psci_migrate_info_type; arm_pm_restart = psci_sys_reset; From 5bd1fc2867a0399870f8145c2fdc335058afae76 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 20 Apr 2015 14:51:00 +0100 Subject: [PATCH 23/41] arm64: psci: support unsigned return values PSCI_VERSION and MIGRATE_INFO_TYPE_UP_CPU return unsigned values, with the latter returning a 64-bit value. However, the PSCI invocation functions have prototypes returning int. This patch upgrades the invocation functions to return unsigned long, with a new typedef to keep things legible. As PSCI_VERSION cannot return a negative value, the erroneous check against PSCI_RET_NOT_SUPPORTED is also removed. The unrelated psci_initcall_t typedef is moved closer to its first user, to avoid confusion with the invocation functions. In preparation for sharing the code with ARM, unsigned long is used in preference of u64. In the SMC32 calling convention, the relevant fields will be 32 bits wide. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Reviewed-by: Hanjun Guo Tested-by: Hanjun Guo Cc: Lorenzo Pieralisi Cc: Will Deacon --- arch/arm64/kernel/psci.c | 47 +++++++++++++++------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 0c1efb662a71a7..7d441b3439a703 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -56,11 +56,11 @@ struct psci_operations { static struct psci_operations psci_ops; -static int (*invoke_psci_fn)(u64, u64, u64, u64); -typedef int (*psci_initcall_t)(const struct device_node *); - -asmlinkage int __invoke_psci_fn_hvc(u64, u64, u64, u64); -asmlinkage int __invoke_psci_fn_smc(u64, u64, u64, u64); +typedef unsigned long (psci_fn)(unsigned long, unsigned long, + unsigned long, unsigned long); +asmlinkage psci_fn __invoke_psci_fn_hvc; +asmlinkage psci_fn __invoke_psci_fn_smc; +static psci_fn *invoke_psci_fn; enum psci_function { PSCI_FN_CPU_SUSPEND, @@ -112,12 +112,9 @@ static void psci_power_state_unpack(u32 power_state, PSCI_0_2_POWER_STATE_AFFL_SHIFT; } -static int psci_get_version(void) +static u32 psci_get_version(void) { - int err; - - err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); - return err; + return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); } static int psci_cpu_suspend(struct psci_power_state state, @@ -296,25 +293,15 @@ static void __init psci_0_2_set_functions(void) */ static int __init psci_probe(void) { - int ver = psci_get_version(); - - if (ver == PSCI_RET_NOT_SUPPORTED) { - /* - * PSCI versions >=0.2 mandates implementation of - * PSCI_VERSION. - */ - pr_err("PSCI firmware does not comply with the v0.2 spec.\n"); - return -EOPNOTSUPP; - } else { - pr_info("PSCIv%d.%d detected in firmware.\n", - PSCI_VERSION_MAJOR(ver), - PSCI_VERSION_MINOR(ver)); - - if (PSCI_VERSION_MAJOR(ver) == 0 && - PSCI_VERSION_MINOR(ver) < 2) { - pr_err("Conflicting PSCI version detected.\n"); - return -EINVAL; - } + u32 ver = psci_get_version(); + + pr_info("PSCIv%d.%d detected in firmware.\n", + PSCI_VERSION_MAJOR(ver), + PSCI_VERSION_MINOR(ver)); + + if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) { + pr_err("Conflicting PSCI version detected.\n"); + return -EINVAL; } psci_0_2_set_functions(); @@ -322,6 +309,8 @@ static int __init psci_probe(void) return 0; } +typedef int (*psci_initcall_t)(const struct device_node *); + /* * PSCI init function for PSCI versions >=0.2 * From c797e0e6dea0ecf287076e8aee164c25b6a16ea7 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Wed, 22 Apr 2015 18:10:26 +0100 Subject: [PATCH 24/41] arm64: psci: account for Trusted OS instances Software resident in the secure world (a "Trusted OS") may cause CPU_OFF calls for the CPU it is resident on to be denied. Such a denial would be fatal for the kernel, and so we must detect when this can happen before the point of no return. This patch implements Trusted OS detection for PSCI 0.2+ systems, using MIGRATE_INFO_TYPE and MIGRATE_INFO_UP_CPU. When a trusted OS is detected as resident on a particular CPU, attempts to hot unplug that CPU will be denied early, before they can prove fatal. Trusted OS migration is not implemented by this patch. Implementation of migratable UP trusted OSs seems unlikely, and the right policy for migration is unclear (and will likely differ across implementations). As such, it is likely that migration will require cooperation with Trusted OS drivers. PSCI implementations prior to 0.1 do not provide the facility to detect the presence of a Trusted OS, nor the CPU any such OS is resident on, so without additional information it is not possible to handle Trusted OSs with PSCI 0.1. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Tested-by: Hanjun Guo Cc: Lorenzo Pieralisi Cc: Will Deacon --- arch/arm64/kernel/psci.c | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 7d441b3439a703..ffa17d171be57a 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -43,6 +44,19 @@ struct psci_power_state { u8 affinity_level; }; +/* + * The CPU any Trusted OS is resident on. The trusted OS may reject CPU_OFF + * calls to its resident CPU, so we must avoid issuing those. We never migrate + * a Trusted OS even if it claims to be capable of migration -- doing so will + * require cooperation with a Trusted OS driver. + */ +static int resident_cpu = -1; + +static bool psci_tos_resident_on(int cpu) +{ + return cpu == resident_cpu; +} + struct psci_operations { int (*cpu_suspend)(struct psci_power_state state, unsigned long entry_point); @@ -172,6 +186,11 @@ static int psci_migrate_info_type(void) return invoke_psci_fn(PSCI_0_2_FN_MIGRATE_INFO_TYPE, 0, 0, 0); } +static unsigned long psci_migrate_info_up_cpu(void) +{ + return invoke_psci_fn(PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU, 0, 0, 0); +} + static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) { int i, ret, count = 0; @@ -264,6 +283,46 @@ static void psci_sys_poweroff(void) invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); } +/* + * Detect the presence of a resident Trusted OS which may cause CPU_OFF to + * return DENIED (which would be fatal). + */ +static void __init psci_init_migrate(void) +{ + unsigned long cpuid; + int type, cpu; + + type = psci_ops.migrate_info_type(); + + if (type == PSCI_0_2_TOS_MP) { + pr_info("Trusted OS migration not required\n"); + return; + } + + if (type == PSCI_RET_NOT_SUPPORTED) { + pr_info("MIGRATE_INFO_TYPE not supported.\n"); + return; + } + + if (type != PSCI_0_2_TOS_UP_MIGRATE && + type != PSCI_0_2_TOS_UP_NO_MIGRATE) { + pr_err("MIGRATE_INFO_TYPE returned unknown type (%d)\n", type); + return; + } + + cpuid = psci_migrate_info_up_cpu(); + if (cpuid & ~MPIDR_HWID_BITMASK) { + pr_warn("MIGRATE_INFO_UP_CPU reported invalid physical ID (0x%lx)\n", + cpuid); + return; + } + + cpu = get_logical_index(cpuid); + resident_cpu = cpu >= 0 ? cpu : -1; + + pr_info("Trusted OS resident on physical CPU 0x%lx\n", cpuid); +} + static void __init psci_0_2_set_functions(void) { pr_info("Using standard PSCI v0.2 function IDs\n"); @@ -306,6 +365,8 @@ static int __init psci_probe(void) psci_0_2_set_functions(); + psci_init_migrate(); + return 0; } @@ -452,6 +513,11 @@ static int cpu_psci_cpu_disable(unsigned int cpu) /* Fail early if we don't have CPU_OFF support */ if (!psci_ops.cpu_off) return -EOPNOTSUPP; + + /* Trusted OS will deny CPU_OFF */ + if (psci_tos_resident_on(cpu)) + return -EPERM; + return 0; } From fabebbc78f9286322b737cc659de620ba089aaca Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Thu, 30 Apr 2015 17:59:03 +0100 Subject: [PATCH 25/41] arm64: psci: kill psci_power_state A PSCI 1.0 implementation may choose to use the new extended StateID format, the presence of which may be queried via the PSCI_FEATURES call. The layout of this new StateID format is incompatible with the existing format, and so to handle both we must abstract attempts to parse the fields. In preparation for PSCI 1.0 support, this patch introduces psci_power_state_loses_context and psci_power_state_is_valid functions to query information from a PSCI power state, which is no longer decomposed (and hence the pack/unpack functions are removed). As it is no longer decomposed, it is now passed round as an opaque u32 token. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Acked-by: Lorenzo Pieralisi Tested-by: Hanjun Guo Cc: Will Deacon --- arch/arm64/kernel/psci.c | 89 +++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 52 deletions(-) diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index ffa17d171be57a..0e2a4407b38638 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -38,11 +38,19 @@ #define PSCI_POWER_STATE_TYPE_STANDBY 0 #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 -struct psci_power_state { - u16 id; - u8 type; - u8 affinity_level; -}; +static bool psci_power_state_loses_context(u32 state) +{ + return state & PSCI_0_2_POWER_STATE_TYPE_MASK; +} + +static bool psci_power_state_is_valid(u32 state) +{ + const u32 valid_mask = PSCI_0_2_POWER_STATE_ID_MASK | + PSCI_0_2_POWER_STATE_TYPE_MASK | + PSCI_0_2_POWER_STATE_AFFL_MASK; + + return !(state & ~valid_mask); +} /* * The CPU any Trusted OS is resident on. The trusted OS may reject CPU_OFF @@ -58,9 +66,8 @@ static bool psci_tos_resident_on(int cpu) } struct psci_operations { - int (*cpu_suspend)(struct psci_power_state state, - unsigned long entry_point); - int (*cpu_off)(struct psci_power_state state); + int (*cpu_suspend)(u32 state, unsigned long entry_point); + int (*cpu_off)(u32 state); int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); int (*migrate)(unsigned long cpuid); int (*affinity_info)(unsigned long target_affinity, @@ -84,7 +91,7 @@ enum psci_function { PSCI_FN_MAX, }; -static DEFINE_PER_CPU_READ_MOSTLY(struct psci_power_state *, psci_power_state); +static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state); static u32 psci_function_id[PSCI_FN_MAX]; @@ -104,53 +111,28 @@ static int psci_to_linux_errno(int errno) return -EINVAL; } -static u32 psci_power_state_pack(struct psci_power_state state) -{ - return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT) - & PSCI_0_2_POWER_STATE_ID_MASK) | - ((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT) - & PSCI_0_2_POWER_STATE_TYPE_MASK) | - ((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT) - & PSCI_0_2_POWER_STATE_AFFL_MASK); -} - -static void psci_power_state_unpack(u32 power_state, - struct psci_power_state *state) -{ - state->id = (power_state & PSCI_0_2_POWER_STATE_ID_MASK) >> - PSCI_0_2_POWER_STATE_ID_SHIFT; - state->type = (power_state & PSCI_0_2_POWER_STATE_TYPE_MASK) >> - PSCI_0_2_POWER_STATE_TYPE_SHIFT; - state->affinity_level = - (power_state & PSCI_0_2_POWER_STATE_AFFL_MASK) >> - PSCI_0_2_POWER_STATE_AFFL_SHIFT; -} - static u32 psci_get_version(void) { return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); } -static int psci_cpu_suspend(struct psci_power_state state, - unsigned long entry_point) +static int psci_cpu_suspend(u32 state, unsigned long entry_point) { int err; - u32 fn, power_state; + u32 fn; fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; - power_state = psci_power_state_pack(state); - err = invoke_psci_fn(fn, power_state, entry_point, 0); + err = invoke_psci_fn(fn, state, entry_point, 0); return psci_to_linux_errno(err); } -static int psci_cpu_off(struct psci_power_state state) +static int psci_cpu_off(u32 state) { int err; - u32 fn, power_state; + u32 fn; fn = psci_function_id[PSCI_FN_CPU_OFF]; - power_state = psci_power_state_pack(state); - err = invoke_psci_fn(fn, power_state, 0, 0); + err = invoke_psci_fn(fn, state, 0, 0); return psci_to_linux_errno(err); } @@ -194,7 +176,7 @@ static unsigned long psci_migrate_info_up_cpu(void) static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) { int i, ret, count = 0; - struct psci_power_state *psci_states; + u32 *psci_states; struct device_node *state_node, *cpu_node; cpu_node = of_get_cpu_node(cpu, NULL); @@ -223,13 +205,13 @@ static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) return -ENOMEM; for (i = 0; i < count; i++) { - u32 psci_power_state; + u32 state; state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i); ret = of_property_read_u32(state_node, "arm,psci-suspend-param", - &psci_power_state); + &state); if (ret) { pr_warn(" * %s missing arm,psci-suspend-param property\n", state_node->full_name); @@ -238,9 +220,13 @@ static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) } of_node_put(state_node); - pr_debug("psci-power-state %#x index %d\n", psci_power_state, - i); - psci_power_state_unpack(psci_power_state, &psci_states[i]); + pr_debug("psci-power-state %#x index %d\n", state, i); + if (!psci_power_state_is_valid(state)) { + pr_warn("Invalid PSCI power state %#x\n", state); + ret = -EINVAL; + goto free_mem; + } + psci_states[i] = state; } /* Idle states parsed correctly, initialize per-cpu pointer */ per_cpu(psci_power_state, cpu) = psci_states; @@ -528,9 +514,8 @@ static void cpu_psci_cpu_die(unsigned int cpu) * There are no known implementations of PSCI actually using the * power state field, pass a sensible default for now. */ - struct psci_power_state state = { - .type = PSCI_POWER_STATE_TYPE_POWER_DOWN, - }; + u32 state = PSCI_POWER_STATE_TYPE_POWER_DOWN << + PSCI_0_2_POWER_STATE_TYPE_SHIFT; ret = psci_ops.cpu_off(state); @@ -569,7 +554,7 @@ static int cpu_psci_cpu_kill(unsigned int cpu) static int psci_suspend_finisher(unsigned long index) { - struct psci_power_state *state = __this_cpu_read(psci_power_state); + u32 *state = __this_cpu_read(psci_power_state); return psci_ops.cpu_suspend(state[index - 1], virt_to_phys(cpu_resume)); @@ -578,7 +563,7 @@ static int psci_suspend_finisher(unsigned long index) static int __maybe_unused cpu_psci_cpu_suspend(unsigned long index) { int ret; - struct psci_power_state *state = __this_cpu_read(psci_power_state); + u32 *state = __this_cpu_read(psci_power_state); /* * idle state index 0 corresponds to wfi, should never be called * from the cpu_suspend operations @@ -586,7 +571,7 @@ static int __maybe_unused cpu_psci_cpu_suspend(unsigned long index) if (WARN_ON_ONCE(!index)) return -EINVAL; - if (state[index - 1].type == PSCI_POWER_STATE_TYPE_STANDBY) + if (!psci_power_state_loses_context(state[index - 1])) ret = psci_ops.cpu_suspend(state[index - 1], 0); else ret = __cpu_suspend(index, psci_suspend_finisher); From 1ac592c742facfa0d88d3a7d98ef1de51e4f6745 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Thu, 30 Apr 2015 14:22:04 +0100 Subject: [PATCH 26/41] arm64: psci: remove ACPI coupling The 32-bit ARM port doesn't have ACPI headers, and conditionally including them is going to look horrendous. In preparation for sharing the PSCI invocation code with 32-bit, move the acpi_psci_* function declarations and definitions such that the PSCI client code need not include ACPI headers. While it would seem like we could simply hide the ACPI includes in psci.h, the ACPI headers have hilarious circular dependencies which make this infeasible without reorganising most of ACPICA. So rather than doing that, move the acpi_psci_* prototypes into psci.h. The psci_acpi_init function is made dependent on CONFIG_ACPI (with a stub implementation in asm/psci.h) such that it need not be built for 32-bit ARM or kernels without ACPI support. The currently missing __init annotations are added to the prototypes in the header. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Acked-by: Hanjun Guo Reviewed-by: Al Stone Reviewed-by: Ashwin Chaugule Tested-by: Hanjun Guo Cc: Lorenzo Pieralisi Cc: Will Deacon --- arch/arm64/include/asm/acpi.h | 15 +-------------- arch/arm64/include/asm/psci.h | 12 ++++++++++-- arch/arm64/kernel/acpi.c | 11 +++++++++++ arch/arm64/kernel/psci.c | 4 ++-- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 1240b867d4a2b4..39248d3adf5d8b 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -16,6 +16,7 @@ #include #include +#include #include /* Basic configuration for ACPI */ @@ -39,18 +40,6 @@ extern int acpi_disabled; extern int acpi_noirq; extern int acpi_pci_disabled; -/* 1 to indicate PSCI 0.2+ is implemented */ -static inline bool acpi_psci_present(void) -{ - return acpi_gbl_FADT.arm_boot_flags & ACPI_FADT_PSCI_COMPLIANT; -} - -/* 1 to indicate HVC must be used instead of SMC as the PSCI conduit */ -static inline bool acpi_psci_use_hvc(void) -{ - return acpi_gbl_FADT.arm_boot_flags & ACPI_FADT_PSCI_USE_HVC; -} - static inline void disable_acpi(void) { acpi_disabled = 1; @@ -88,8 +77,6 @@ static inline void arch_fix_phys_package_id(int num, u32 slot) { } void __init acpi_init_cpus(void); #else -static inline bool acpi_psci_present(void) { return false; } -static inline bool acpi_psci_use_hvc(void) { return false; } static inline void acpi_init_cpus(void) { } #endif /* CONFIG_ACPI */ diff --git a/arch/arm64/include/asm/psci.h b/arch/arm64/include/asm/psci.h index 2454bc59c916b2..49d7e1aaebdce6 100644 --- a/arch/arm64/include/asm/psci.h +++ b/arch/arm64/include/asm/psci.h @@ -14,7 +14,15 @@ #ifndef __ASM_PSCI_H #define __ASM_PSCI_H -int psci_dt_init(void); -int psci_acpi_init(void); +int __init psci_dt_init(void); + +#ifdef CONFIG_ACPI +int __init psci_acpi_init(void); +bool __init acpi_psci_present(void); +bool __init acpi_psci_use_hvc(void); +#else +static inline int psci_acpi_init(void) { return 0; } +static inline bool acpi_psci_present(void) { return false; } +#endif #endif /* __ASM_PSCI_H */ diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index 732f57b8731bd6..19de7537e7d32f 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -89,6 +89,17 @@ void __init __acpi_unmap_table(char *map, unsigned long size) early_memunmap(map, size); } +bool __init acpi_psci_present(void) +{ + return acpi_gbl_FADT.arm_boot_flags & ACPI_FADT_PSCI_COMPLIANT; +} + +/* Whether HVC must be used instead of SMC as the PSCI conduit */ +bool __init acpi_psci_use_hvc(void) +{ + return acpi_gbl_FADT.arm_boot_flags & ACPI_FADT_PSCI_USE_HVC; +} + /* * acpi_fadt_sanity_check() - Check FADT presence and carry out sanity * checks on it diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 0e2a4407b38638..cba05d9bcbc67a 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -15,7 +15,6 @@ #define pr_fmt(fmt) "psci: " fmt -#include #include #include #include @@ -25,7 +24,6 @@ #include #include -#include #include #include #include @@ -446,6 +444,7 @@ int __init psci_dt_init(void) return init_fn(np); } +#ifdef CONFIG_ACPI /* * We use PSCI 0.2+ when ACPI is deployed on ARM64 and it's * explicitly clarified in SBBR @@ -466,6 +465,7 @@ int __init psci_acpi_init(void) return psci_probe(); } +#endif #ifdef CONFIG_SMP From a7c8a5efed04f82d3c45030f90c3a8f01b324092 Mon Sep 17 00:00:00 2001 From: Al Stone Date: Mon, 6 Jul 2015 17:16:47 -0600 Subject: [PATCH 27/41] ACPI / ARM64: add BAD_MADT_GICC_ENTRY() macro The BAD_MADT_ENTRY() macro is designed to work for all of the subtables of the MADT. In the ACPI 5.1 version of the spec, the struct for the GICC subtable (struct acpi_madt_generic_interrupt) is 76 bytes long; in ACPI 6.0, the struct is 80 bytes long. But, there is only one definition in ACPICA for this struct -- and that is the 6.0 version. Hence, when BAD_MADT_ENTRY() compares the struct size to the length in the GICC subtable, it fails if 5.1 structs are in use, and there are systems in the wild that have them. This patch adds the BAD_MADT_GICC_ENTRY() that checks the GICC subtable only, accounting for the difference in specification versions that are possible. The BAD_MADT_ENTRY() will continue to work as is for all other MADT subtables. This code is being added to an arm64 header file since that is currently the only architecture using the GICC subtable of the MADT. As a GIC is specific to ARM, it is also unlikely the subtable will be used elsewhere. Fixes: aeb823bbacc2 ("ACPICA: ACPI 6.0: Add changes for FADT table.") Signed-off-by: Al Stone Acked-by: Will Deacon Acked-by: "Rafael J. Wysocki" [catalin.marinas@arm.com: extra brackets around macro arguments] Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/acpi.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 39248d3adf5d8b..406485ed110af9 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -19,6 +19,14 @@ #include #include +/* Macros for consistency checks of the GICC subtable of MADT */ +#define ACPI_MADT_GICC_LENGTH \ + (acpi_gbl_FADT.header.revision < 6 ? 76 : 80) + +#define BAD_MADT_GICC_ENTRY(entry, end) \ + (!(entry) || (unsigned long)(entry) + sizeof(*(entry)) > (end) || \ + (entry)->header.length != ACPI_MADT_GICC_LENGTH) + /* Basic configuration for ACPI */ #ifdef CONFIG_ACPI /* ACPI table mapping after acpi_gbl_permanent_mmap is set */ From 36f38e216d2e8ce710b1b153e264da344185b28b Mon Sep 17 00:00:00 2001 From: Al Stone Date: Mon, 6 Jul 2015 17:16:48 -0600 Subject: [PATCH 28/41] ACPI / ARM64 : use the new BAD_MADT_GICC_ENTRY macro For those parts of the arm64 ACPI code that need to check GICC subtables in the MADT, use the new BAD_MADT_GICC_ENTRY macro instead of the previous BAD_MADT_ENTRY. The new macro takes into account differences in the size of the GICC subtable that the old macro did not; this caused failures even though the subtable entries are valid. Fixes: aeb823bbacc2 ("ACPICA: ACPI 6.0: Add changes for FADT table.") Signed-off-by: Al Stone Reviewed-by: Hanjun Guo Acked-by: Will Deacon Acked-by: "Rafael J. Wysocki" Signed-off-by: Catalin Marinas --- arch/arm64/kernel/smp.c | 2 +- drivers/irqchip/irq-gic.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 2b503a3186dd48..2782c9263bb857 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -440,7 +440,7 @@ acpi_parse_gic_cpu_interface(struct acpi_subtable_header *header, struct acpi_madt_generic_interrupt *processor; processor = (struct acpi_madt_generic_interrupt *)header; - if (BAD_MADT_ENTRY(processor, end)) + if (BAD_MADT_GICC_ENTRY(processor, end)) return -EINVAL; acpi_table_print_madt_entry(header); diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 01999d74bd3af3..fbb6d2529124fb 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1054,7 +1054,7 @@ gic_acpi_parse_madt_cpu(struct acpi_subtable_header *header, processor = (struct acpi_madt_generic_interrupt *)header; - if (BAD_MADT_ENTRY(processor, end)) + if (BAD_MADT_GICC_ENTRY(processor, end)) return -EINVAL; /* From 44a0be1cb43cfa0a5033ff006e43297e61af96a0 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Thu, 11 Jun 2015 18:16:47 +0100 Subject: [PATCH 29/41] arm64: psci: fix !CONFIG_HOTPLUG_CPU build warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When building without CONFIG_HOTPLUG_CPU, GCC complains (rightly) that psci_tos_resident_on is unused: arch/arm64/kernel/psci.c:61:13: warning: ‘psci_tos_resident_on’ defined but not used [-Wunused-function] static bool psci_tos_resident_on(int cpu) As it's only ever used when CONFIG_HOTPLUG_CPU is selected, let's move it into the existing ifdef. Signed-off-by: Will Deacon [Mark: write commit message] Signed-off-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/kernel/psci.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index cba05d9bcbc67a..20521377c4b307 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -58,11 +58,6 @@ static bool psci_power_state_is_valid(u32 state) */ static int resident_cpu = -1; -static bool psci_tos_resident_on(int cpu) -{ - return cpu == resident_cpu; -} - struct psci_operations { int (*cpu_suspend)(u32 state, unsigned long entry_point); int (*cpu_off)(u32 state); @@ -494,6 +489,11 @@ static int cpu_psci_cpu_boot(unsigned int cpu) } #ifdef CONFIG_HOTPLUG_CPU +static bool psci_tos_resident_on(int cpu) +{ + return cpu == resident_cpu; +} + static int cpu_psci_cpu_disable(unsigned int cpu) { /* Fail early if we don't have CPU_OFF support */ From e9ff42beb7b13cfe2bd5dfcf593700e914fac31d Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 18 Jun 2015 15:41:32 +0100 Subject: [PATCH 30/41] arm64: kernel: rename __cpu_suspend to keep it aligned with arm This patch renames __cpu_suspend to cpu_suspend so that it's aligned with ARM32. It also removes the redundant wrapper created. This is in preparation to implement generic PSCI system suspend using the cpu_{suspend,resume} which now has the same interface on both ARM and ARM64. Cc: Mark Rutland Reviewed-by: Lorenzo Pieralisi Reviewed-by: Ashwin Chaugule Signed-off-by: Sudeep Holla Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/cpuidle.h | 8 ++------ arch/arm64/include/asm/suspend.h | 2 +- arch/arm64/kernel/cpuidle.c | 4 ++-- arch/arm64/kernel/psci.c | 2 +- arch/arm64/kernel/suspend.c | 6 +++--- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/arch/arm64/include/asm/cpuidle.h b/arch/arm64/include/asm/cpuidle.h index 141b2fcabaa67e..0f74f05d662a76 100644 --- a/arch/arm64/include/asm/cpuidle.h +++ b/arch/arm64/include/asm/cpuidle.h @@ -5,20 +5,16 @@ #ifdef CONFIG_CPU_IDLE extern int arm_cpuidle_init(unsigned int cpu); -extern int cpu_suspend(unsigned long arg); +extern int arm_cpuidle_suspend(int index); #else static inline int arm_cpuidle_init(unsigned int cpu) { return -EOPNOTSUPP; } -static inline int cpu_suspend(unsigned long arg) +static inline int arm_cpuidle_suspend(int index) { return -EOPNOTSUPP; } #endif -static inline int arm_cpuidle_suspend(int index) -{ - return cpu_suspend(index); -} #endif diff --git a/arch/arm64/include/asm/suspend.h b/arch/arm64/include/asm/suspend.h index 003802f5896336..59a5b0f1e81c32 100644 --- a/arch/arm64/include/asm/suspend.h +++ b/arch/arm64/include/asm/suspend.h @@ -21,6 +21,6 @@ struct sleep_save_sp { phys_addr_t save_ptr_stash_phys; }; -extern int __cpu_suspend(unsigned long arg, int (*fn)(unsigned long)); +extern int cpu_suspend(unsigned long arg, int (*fn)(unsigned long)); extern void cpu_resume(void); #endif diff --git a/arch/arm64/kernel/cpuidle.c b/arch/arm64/kernel/cpuidle.c index f8aa16973318f5..7ce589ca54a4fb 100644 --- a/arch/arm64/kernel/cpuidle.c +++ b/arch/arm64/kernel/cpuidle.c @@ -32,7 +32,7 @@ int arm_cpuidle_init(unsigned int cpu) * Return: 0 on success, -EOPNOTSUPP if CPU suspend hook not initialized, CPU * operations back-end error code otherwise. */ -int cpu_suspend(unsigned long arg) +int arm_cpuidle_suspend(int index) { int cpu = smp_processor_id(); @@ -42,5 +42,5 @@ int cpu_suspend(unsigned long arg) */ if (!cpu_ops[cpu] || !cpu_ops[cpu]->cpu_suspend) return -EOPNOTSUPP; - return cpu_ops[cpu]->cpu_suspend(arg); + return cpu_ops[cpu]->cpu_suspend(index); } diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 20521377c4b307..869f202748e8ea 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -574,7 +574,7 @@ static int __maybe_unused cpu_psci_cpu_suspend(unsigned long index) if (!psci_power_state_loses_context(state[index - 1])) ret = psci_ops.cpu_suspend(state[index - 1], 0); else - ret = __cpu_suspend(index, psci_suspend_finisher); + ret = cpu_suspend(index, psci_suspend_finisher); return ret; } diff --git a/arch/arm64/kernel/suspend.c b/arch/arm64/kernel/suspend.c index d7daf45ae7a253..400e1265e205a7 100644 --- a/arch/arm64/kernel/suspend.c +++ b/arch/arm64/kernel/suspend.c @@ -51,13 +51,13 @@ void __init cpu_suspend_set_dbg_restorer(void (*hw_bp_restore)(void *)) } /* - * __cpu_suspend + * cpu_suspend * * arg: argument to pass to the finisher function * fn: finisher function pointer * */ -int __cpu_suspend(unsigned long arg, int (*fn)(unsigned long)) +int cpu_suspend(unsigned long arg, int (*fn)(unsigned long)) { struct mm_struct *mm = current->active_mm; int ret; @@ -82,7 +82,7 @@ int __cpu_suspend(unsigned long arg, int (*fn)(unsigned long)) * We are resuming from reset with TTBR0_EL1 set to the * idmap to enable the MMU; restore the active_mm mappings in * TTBR0_EL1 unless the active_mm == &init_mm, in which case - * the thread entered __cpu_suspend with TTBR0_EL1 set to + * the thread entered cpu_suspend with TTBR0_EL1 set to * reserved TTBR0 page tables and should be restored as such. */ if (mm == &init_mm) From e07917affafd2dcbaebdfd6153a866716cd9d70c Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Thu, 2 Jul 2015 18:12:34 +0100 Subject: [PATCH 31/41] arm64: psci: factor invocation code to drivers To enable sharing with arm, move the core PSCI framework code to drivers/firmware. This results in a minor gain in lines of code, but this will quickly be amortised by the removal of code currently duplicated in arch/arm. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Reviewed-by: Hanjun Guo Tested-by: Hanjun Guo Cc: Lorenzo Pieralisi Cc: Will Deacon --- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/acpi.h | 4 +- arch/arm64/include/asm/psci.h | 28 --- arch/arm64/kernel/psci.c | 361 +-------------------------------- arch/arm64/kernel/setup.c | 2 +- drivers/firmware/Kconfig | 3 + drivers/firmware/Makefile | 1 + drivers/firmware/psci.c | 369 ++++++++++++++++++++++++++++++++++ include/linux/psci.h | 52 +++++ 9 files changed, 431 insertions(+), 390 deletions(-) delete mode 100644 arch/arm64/include/asm/psci.h create mode 100644 drivers/firmware/psci.c create mode 100644 include/linux/psci.h diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 7682e1ff62f289..f0e224e7f35f87 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -19,6 +19,7 @@ config ARM64 select ARM_GIC_V2M if PCI_MSI select ARM_GIC_V3 select ARM_GIC_V3_ITS if PCI_MSI + select ARM_PSCI_FW select BUILDTIME_EXTABLE_SORT select CLONE_BACKWARDS select COMMON_CLK diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 406485ed110af9..208cec08a74fa0 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -12,11 +12,11 @@ #ifndef _ASM_ACPI_H #define _ASM_ACPI_H -#include #include +#include +#include #include -#include #include /* Macros for consistency checks of the GICC subtable of MADT */ diff --git a/arch/arm64/include/asm/psci.h b/arch/arm64/include/asm/psci.h deleted file mode 100644 index 49d7e1aaebdce6..00000000000000 --- a/arch/arm64/include/asm/psci.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * Copyright (C) 2013 ARM Limited - */ - -#ifndef __ASM_PSCI_H -#define __ASM_PSCI_H - -int __init psci_dt_init(void); - -#ifdef CONFIG_ACPI -int __init psci_acpi_init(void); -bool __init acpi_psci_present(void); -bool __init acpi_psci_use_hvc(void); -#else -static inline int psci_acpi_init(void) { return 0; } -static inline bool acpi_psci_present(void) { return false; } -#endif - -#endif /* __ASM_PSCI_H */ diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 869f202748e8ea..51fd15a16461a9 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -18,23 +18,17 @@ #include #include #include -#include -#include #include +#include #include + #include #include -#include #include #include -#include #include #include -#include - -#define PSCI_POWER_STATE_TYPE_STANDBY 0 -#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 static bool psci_power_state_loses_context(u32 state) { @@ -50,122 +44,8 @@ static bool psci_power_state_is_valid(u32 state) return !(state & ~valid_mask); } -/* - * The CPU any Trusted OS is resident on. The trusted OS may reject CPU_OFF - * calls to its resident CPU, so we must avoid issuing those. We never migrate - * a Trusted OS even if it claims to be capable of migration -- doing so will - * require cooperation with a Trusted OS driver. - */ -static int resident_cpu = -1; - -struct psci_operations { - int (*cpu_suspend)(u32 state, unsigned long entry_point); - int (*cpu_off)(u32 state); - int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); - int (*migrate)(unsigned long cpuid); - int (*affinity_info)(unsigned long target_affinity, - unsigned long lowest_affinity_level); - int (*migrate_info_type)(void); -}; - -static struct psci_operations psci_ops; - -typedef unsigned long (psci_fn)(unsigned long, unsigned long, - unsigned long, unsigned long); -asmlinkage psci_fn __invoke_psci_fn_hvc; -asmlinkage psci_fn __invoke_psci_fn_smc; -static psci_fn *invoke_psci_fn; - -enum psci_function { - PSCI_FN_CPU_SUSPEND, - PSCI_FN_CPU_ON, - PSCI_FN_CPU_OFF, - PSCI_FN_MIGRATE, - PSCI_FN_MAX, -}; - static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state); -static u32 psci_function_id[PSCI_FN_MAX]; - -static int psci_to_linux_errno(int errno) -{ - switch (errno) { - case PSCI_RET_SUCCESS: - return 0; - case PSCI_RET_NOT_SUPPORTED: - return -EOPNOTSUPP; - case PSCI_RET_INVALID_PARAMS: - return -EINVAL; - case PSCI_RET_DENIED: - return -EPERM; - }; - - return -EINVAL; -} - -static u32 psci_get_version(void) -{ - return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); -} - -static int psci_cpu_suspend(u32 state, unsigned long entry_point) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; - err = invoke_psci_fn(fn, state, entry_point, 0); - return psci_to_linux_errno(err); -} - -static int psci_cpu_off(u32 state) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_CPU_OFF]; - err = invoke_psci_fn(fn, state, 0, 0); - return psci_to_linux_errno(err); -} - -static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_CPU_ON]; - err = invoke_psci_fn(fn, cpuid, entry_point, 0); - return psci_to_linux_errno(err); -} - -static int psci_migrate(unsigned long cpuid) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_MIGRATE]; - err = invoke_psci_fn(fn, cpuid, 0, 0); - return psci_to_linux_errno(err); -} - -static int psci_affinity_info(unsigned long target_affinity, - unsigned long lowest_affinity_level) -{ - return invoke_psci_fn(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, - lowest_affinity_level, 0); -} - -static int psci_migrate_info_type(void) -{ - return invoke_psci_fn(PSCI_0_2_FN_MIGRATE_INFO_TYPE, 0, 0, 0); -} - -static unsigned long psci_migrate_info_up_cpu(void) -{ - return invoke_psci_fn(PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU, 0, 0, 0); -} - static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) { int i, ret, count = 0; @@ -230,238 +110,6 @@ static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) return ret; } -static int get_set_conduit_method(struct device_node *np) -{ - const char *method; - - pr_info("probing for conduit method from DT.\n"); - - if (of_property_read_string(np, "method", &method)) { - pr_warn("missing \"method\" property\n"); - return -ENXIO; - } - - if (!strcmp("hvc", method)) { - invoke_psci_fn = __invoke_psci_fn_hvc; - } else if (!strcmp("smc", method)) { - invoke_psci_fn = __invoke_psci_fn_smc; - } else { - pr_warn("invalid \"method\" property: %s\n", method); - return -EINVAL; - } - return 0; -} - -static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) -{ - invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); -} - -static void psci_sys_poweroff(void) -{ - invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); -} - -/* - * Detect the presence of a resident Trusted OS which may cause CPU_OFF to - * return DENIED (which would be fatal). - */ -static void __init psci_init_migrate(void) -{ - unsigned long cpuid; - int type, cpu; - - type = psci_ops.migrate_info_type(); - - if (type == PSCI_0_2_TOS_MP) { - pr_info("Trusted OS migration not required\n"); - return; - } - - if (type == PSCI_RET_NOT_SUPPORTED) { - pr_info("MIGRATE_INFO_TYPE not supported.\n"); - return; - } - - if (type != PSCI_0_2_TOS_UP_MIGRATE && - type != PSCI_0_2_TOS_UP_NO_MIGRATE) { - pr_err("MIGRATE_INFO_TYPE returned unknown type (%d)\n", type); - return; - } - - cpuid = psci_migrate_info_up_cpu(); - if (cpuid & ~MPIDR_HWID_BITMASK) { - pr_warn("MIGRATE_INFO_UP_CPU reported invalid physical ID (0x%lx)\n", - cpuid); - return; - } - - cpu = get_logical_index(cpuid); - resident_cpu = cpu >= 0 ? cpu : -1; - - pr_info("Trusted OS resident on physical CPU 0x%lx\n", cpuid); -} - -static void __init psci_0_2_set_functions(void) -{ - pr_info("Using standard PSCI v0.2 function IDs\n"); - psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN64_CPU_SUSPEND; - psci_ops.cpu_suspend = psci_cpu_suspend; - - psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; - psci_ops.cpu_off = psci_cpu_off; - - psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN64_CPU_ON; - psci_ops.cpu_on = psci_cpu_on; - - psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN64_MIGRATE; - psci_ops.migrate = psci_migrate; - - psci_ops.affinity_info = psci_affinity_info; - - psci_ops.migrate_info_type = psci_migrate_info_type; - - arm_pm_restart = psci_sys_reset; - - pm_power_off = psci_sys_poweroff; -} - -/* - * Probe function for PSCI firmware versions >= 0.2 - */ -static int __init psci_probe(void) -{ - u32 ver = psci_get_version(); - - pr_info("PSCIv%d.%d detected in firmware.\n", - PSCI_VERSION_MAJOR(ver), - PSCI_VERSION_MINOR(ver)); - - if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) { - pr_err("Conflicting PSCI version detected.\n"); - return -EINVAL; - } - - psci_0_2_set_functions(); - - psci_init_migrate(); - - return 0; -} - -typedef int (*psci_initcall_t)(const struct device_node *); - -/* - * PSCI init function for PSCI versions >=0.2 - * - * Probe based on PSCI PSCI_VERSION function - */ -static int __init psci_0_2_init(struct device_node *np) -{ - int err; - - err = get_set_conduit_method(np); - - if (err) - goto out_put_node; - /* - * Starting with v0.2, the PSCI specification introduced a call - * (PSCI_VERSION) that allows probing the firmware version, so - * that PSCI function IDs and version specific initialization - * can be carried out according to the specific version reported - * by firmware - */ - err = psci_probe(); - -out_put_node: - of_node_put(np); - return err; -} - -/* - * PSCI < v0.2 get PSCI Function IDs via DT. - */ -static int __init psci_0_1_init(struct device_node *np) -{ - u32 id; - int err; - - err = get_set_conduit_method(np); - - if (err) - goto out_put_node; - - pr_info("Using PSCI v0.1 Function IDs from DT\n"); - - if (!of_property_read_u32(np, "cpu_suspend", &id)) { - psci_function_id[PSCI_FN_CPU_SUSPEND] = id; - psci_ops.cpu_suspend = psci_cpu_suspend; - } - - if (!of_property_read_u32(np, "cpu_off", &id)) { - psci_function_id[PSCI_FN_CPU_OFF] = id; - psci_ops.cpu_off = psci_cpu_off; - } - - if (!of_property_read_u32(np, "cpu_on", &id)) { - psci_function_id[PSCI_FN_CPU_ON] = id; - psci_ops.cpu_on = psci_cpu_on; - } - - if (!of_property_read_u32(np, "migrate", &id)) { - psci_function_id[PSCI_FN_MIGRATE] = id; - psci_ops.migrate = psci_migrate; - } - -out_put_node: - of_node_put(np); - return err; -} - -static const struct of_device_id psci_of_match[] __initconst = { - { .compatible = "arm,psci", .data = psci_0_1_init}, - { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, - {}, -}; - -int __init psci_dt_init(void) -{ - struct device_node *np; - const struct of_device_id *matched_np; - psci_initcall_t init_fn; - - np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); - - if (!np) - return -ENODEV; - - init_fn = (psci_initcall_t)matched_np->data; - return init_fn(np); -} - -#ifdef CONFIG_ACPI -/* - * We use PSCI 0.2+ when ACPI is deployed on ARM64 and it's - * explicitly clarified in SBBR - */ -int __init psci_acpi_init(void) -{ - if (!acpi_psci_present()) { - pr_info("is not implemented in ACPI.\n"); - return -EOPNOTSUPP; - } - - pr_info("probing for conduit method from ACPI.\n"); - - if (acpi_psci_use_hvc()) - invoke_psci_fn = __invoke_psci_fn_hvc; - else - invoke_psci_fn = __invoke_psci_fn_smc; - - return psci_probe(); -} -#endif - #ifdef CONFIG_SMP static int __init cpu_psci_cpu_init(unsigned int cpu) @@ -489,11 +137,6 @@ static int cpu_psci_cpu_boot(unsigned int cpu) } #ifdef CONFIG_HOTPLUG_CPU -static bool psci_tos_resident_on(int cpu) -{ - return cpu == resident_cpu; -} - static int cpu_psci_cpu_disable(unsigned int cpu) { /* Fail early if we don't have CPU_OFF support */ diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 508cca1f8dce27..d6a59228816287 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include @@ -61,7 +62,6 @@ #include #include #include -#include #include #include diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 6517132e5d8b59..24be7a34b84a1c 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -5,6 +5,9 @@ menu "Firmware Drivers" +config ARM_PSCI_FW + bool + config EDD tristate "BIOS Enhanced Disk Drive calls determine boot disk" depends on X86 diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 3fdd3912709af5..6b051293e68939 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -1,6 +1,7 @@ # # Makefile for the linux kernel. # +obj-$(CONFIG_ARM_PSCI_FW) += psci.o obj-$(CONFIG_DMI) += dmi_scan.o obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o obj-$(CONFIG_EDD) += edd.o diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c new file mode 100644 index 00000000000000..36e2cea3809bea --- /dev/null +++ b/drivers/firmware/psci.c @@ -0,0 +1,369 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2015 ARM Limited + */ + +#define pr_fmt(fmt) "psci: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +/* + * The CPU any Trusted OS is resident on. The trusted OS may reject CPU_OFF + * calls to its resident CPU, so we must avoid issuing those. We never migrate + * a Trusted OS even if it claims to be capable of migration -- doing so will + * require cooperation with a Trusted OS driver. + */ +static int resident_cpu = -1; + +bool psci_tos_resident_on(int cpu) +{ + return cpu == resident_cpu; +} + +struct psci_operations psci_ops; + +typedef unsigned long (psci_fn)(unsigned long, unsigned long, + unsigned long, unsigned long); +asmlinkage psci_fn __invoke_psci_fn_hvc; +asmlinkage psci_fn __invoke_psci_fn_smc; +static psci_fn *invoke_psci_fn; + +enum psci_function { + PSCI_FN_CPU_SUSPEND, + PSCI_FN_CPU_ON, + PSCI_FN_CPU_OFF, + PSCI_FN_MIGRATE, + PSCI_FN_MAX, +}; + +static u32 psci_function_id[PSCI_FN_MAX]; + +static int psci_to_linux_errno(int errno) +{ + switch (errno) { + case PSCI_RET_SUCCESS: + return 0; + case PSCI_RET_NOT_SUPPORTED: + return -EOPNOTSUPP; + case PSCI_RET_INVALID_PARAMS: + return -EINVAL; + case PSCI_RET_DENIED: + return -EPERM; + }; + + return -EINVAL; +} + +static u32 psci_get_version(void) +{ + return invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); +} + +static int psci_cpu_suspend(u32 state, unsigned long entry_point) +{ + int err; + u32 fn; + + fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; + err = invoke_psci_fn(fn, state, entry_point, 0); + return psci_to_linux_errno(err); +} + +static int psci_cpu_off(u32 state) +{ + int err; + u32 fn; + + fn = psci_function_id[PSCI_FN_CPU_OFF]; + err = invoke_psci_fn(fn, state, 0, 0); + return psci_to_linux_errno(err); +} + +static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point) +{ + int err; + u32 fn; + + fn = psci_function_id[PSCI_FN_CPU_ON]; + err = invoke_psci_fn(fn, cpuid, entry_point, 0); + return psci_to_linux_errno(err); +} + +static int psci_migrate(unsigned long cpuid) +{ + int err; + u32 fn; + + fn = psci_function_id[PSCI_FN_MIGRATE]; + err = invoke_psci_fn(fn, cpuid, 0, 0); + return psci_to_linux_errno(err); +} + +static int psci_affinity_info(unsigned long target_affinity, + unsigned long lowest_affinity_level) +{ + return invoke_psci_fn(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, + lowest_affinity_level, 0); +} + +static int psci_migrate_info_type(void) +{ + return invoke_psci_fn(PSCI_0_2_FN_MIGRATE_INFO_TYPE, 0, 0, 0); +} + +static unsigned long psci_migrate_info_up_cpu(void) +{ + return invoke_psci_fn(PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU, 0, 0, 0); +} + +static int get_set_conduit_method(struct device_node *np) +{ + const char *method; + + pr_info("probing for conduit method from DT.\n"); + + if (of_property_read_string(np, "method", &method)) { + pr_warn("missing \"method\" property\n"); + return -ENXIO; + } + + if (!strcmp("hvc", method)) { + invoke_psci_fn = __invoke_psci_fn_hvc; + } else if (!strcmp("smc", method)) { + invoke_psci_fn = __invoke_psci_fn_smc; + } else { + pr_warn("invalid \"method\" property: %s\n", method); + return -EINVAL; + } + return 0; +} + +static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) +{ + invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); +} + +static void psci_sys_poweroff(void) +{ + invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); +} + +/* + * Detect the presence of a resident Trusted OS which may cause CPU_OFF to + * return DENIED (which would be fatal). + */ +static void __init psci_init_migrate(void) +{ + unsigned long cpuid; + int type, cpu = -1; + + type = psci_ops.migrate_info_type(); + + if (type == PSCI_0_2_TOS_MP) { + pr_info("Trusted OS migration not required\n"); + return; + } + + if (type == PSCI_RET_NOT_SUPPORTED) { + pr_info("MIGRATE_INFO_TYPE not supported.\n"); + return; + } + + if (type != PSCI_0_2_TOS_UP_MIGRATE && + type != PSCI_0_2_TOS_UP_NO_MIGRATE) { + pr_err("MIGRATE_INFO_TYPE returned unknown type (%d)\n", type); + return; + } + + cpuid = psci_migrate_info_up_cpu(); + if (cpuid & ~MPIDR_HWID_BITMASK) { + pr_warn("MIGRATE_INFO_UP_CPU reported invalid physical ID (0x%lx)\n", + cpuid); + return; + } + + cpu = get_logical_index(cpuid); + resident_cpu = cpu >= 0 ? cpu : -1; + + pr_info("Trusted OS resident on physical CPU 0x%lx\n", cpuid); +} + +static void __init psci_0_2_set_functions(void) +{ + pr_info("Using standard PSCI v0.2 function IDs\n"); + psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN64_CPU_SUSPEND; + psci_ops.cpu_suspend = psci_cpu_suspend; + + psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; + psci_ops.cpu_off = psci_cpu_off; + + psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN64_CPU_ON; + psci_ops.cpu_on = psci_cpu_on; + + psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN64_MIGRATE; + psci_ops.migrate = psci_migrate; + + psci_ops.affinity_info = psci_affinity_info; + + psci_ops.migrate_info_type = psci_migrate_info_type; + + arm_pm_restart = psci_sys_reset; + + pm_power_off = psci_sys_poweroff; +} + +/* + * Probe function for PSCI firmware versions >= 0.2 + */ +static int __init psci_probe(void) +{ + u32 ver = psci_get_version(); + + pr_info("PSCIv%d.%d detected in firmware.\n", + PSCI_VERSION_MAJOR(ver), + PSCI_VERSION_MINOR(ver)); + + if (PSCI_VERSION_MAJOR(ver) == 0 && PSCI_VERSION_MINOR(ver) < 2) { + pr_err("Conflicting PSCI version detected.\n"); + return -EINVAL; + } + + psci_0_2_set_functions(); + + psci_init_migrate(); + + return 0; +} + +typedef int (*psci_initcall_t)(const struct device_node *); + +/* + * PSCI init function for PSCI versions >=0.2 + * + * Probe based on PSCI PSCI_VERSION function + */ +static int __init psci_0_2_init(struct device_node *np) +{ + int err; + + err = get_set_conduit_method(np); + + if (err) + goto out_put_node; + /* + * Starting with v0.2, the PSCI specification introduced a call + * (PSCI_VERSION) that allows probing the firmware version, so + * that PSCI function IDs and version specific initialization + * can be carried out according to the specific version reported + * by firmware + */ + err = psci_probe(); + +out_put_node: + of_node_put(np); + return err; +} + +/* + * PSCI < v0.2 get PSCI Function IDs via DT. + */ +static int __init psci_0_1_init(struct device_node *np) +{ + u32 id; + int err; + + err = get_set_conduit_method(np); + + if (err) + goto out_put_node; + + pr_info("Using PSCI v0.1 Function IDs from DT\n"); + + if (!of_property_read_u32(np, "cpu_suspend", &id)) { + psci_function_id[PSCI_FN_CPU_SUSPEND] = id; + psci_ops.cpu_suspend = psci_cpu_suspend; + } + + if (!of_property_read_u32(np, "cpu_off", &id)) { + psci_function_id[PSCI_FN_CPU_OFF] = id; + psci_ops.cpu_off = psci_cpu_off; + } + + if (!of_property_read_u32(np, "cpu_on", &id)) { + psci_function_id[PSCI_FN_CPU_ON] = id; + psci_ops.cpu_on = psci_cpu_on; + } + + if (!of_property_read_u32(np, "migrate", &id)) { + psci_function_id[PSCI_FN_MIGRATE] = id; + psci_ops.migrate = psci_migrate; + } + +out_put_node: + of_node_put(np); + return err; +} + +static const struct of_device_id psci_of_match[] __initconst = { + { .compatible = "arm,psci", .data = psci_0_1_init}, + { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, + {}, +}; + +int __init psci_dt_init(void) +{ + struct device_node *np; + const struct of_device_id *matched_np; + psci_initcall_t init_fn; + + np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); + + if (!np) + return -ENODEV; + + init_fn = (psci_initcall_t)matched_np->data; + return init_fn(np); +} + +#ifdef CONFIG_ACPI +/* + * We use PSCI 0.2+ when ACPI is deployed on ARM64 and it's + * explicitly clarified in SBBR + */ +int __init psci_acpi_init(void) +{ + if (!acpi_psci_present()) { + pr_info("is not implemented in ACPI.\n"); + return -EOPNOTSUPP; + } + + pr_info("probing for conduit method from ACPI.\n"); + + if (acpi_psci_use_hvc()) + invoke_psci_fn = __invoke_psci_fn_hvc; + else + invoke_psci_fn = __invoke_psci_fn_smc; + + return psci_probe(); +} +#endif diff --git a/include/linux/psci.h b/include/linux/psci.h new file mode 100644 index 00000000000000..a682fcc91c3321 --- /dev/null +++ b/include/linux/psci.h @@ -0,0 +1,52 @@ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Copyright (C) 2015 ARM Limited + */ + +#ifndef __LINUX_PSCI_H +#define __LINUX_PSCI_H + +#include +#include + +#define PSCI_POWER_STATE_TYPE_STANDBY 0 +#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 + +bool psci_tos_resident_on(int cpu); + +struct psci_operations { + int (*cpu_suspend)(u32 state, unsigned long entry_point); + int (*cpu_off)(u32 state); + int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); + int (*migrate)(unsigned long cpuid); + int (*affinity_info)(unsigned long target_affinity, + unsigned long lowest_affinity_level); + int (*migrate_info_type)(void); +}; + +extern struct psci_operations psci_ops; + +#if defined(CONFIG_ARM_PSCI_FW) +int __init psci_dt_init(void); +#else +static inline int psci_dt_init(void) { return 0; } +#endif + +#if defined(CONFIG_ARM_PSCI_FW) && defined(CONFIG_ACPI) +int __init psci_acpi_init(void); +bool __init acpi_psci_present(void); +bool __init acpi_psci_use_hvc(void); +#else +static inline int psci_acpi_init(void) { return 0; } +static inline bool acpi_psci_present(void) { return false; } +#endif + +#endif /* __LINUX_PSCI_H */ From 675703318aefa37c550d4a17915c079696340d78 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Thu, 2 Jul 2015 18:12:35 +0100 Subject: [PATCH 32/41] drivers: psci: support native SMC{32,64} calls A 32-bit OS cannot make calls with SMC64 IDs, while a 64-bit OS must invoke some PSCI functions with SMC64 IDs. This patch introduces and makes use of a new macro to choose the appropriate IDs based on the register width of the OS, which will allow 32-bit callers to use the PSCI client code. Signed-off-by: Mark Rutland Tested-by: Hanjun Guo Cc: Lorenzo Pieralisi --- drivers/firmware/psci.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index 36e2cea3809bea..a6956007dd3837 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -27,6 +27,18 @@ #include #include +/* + * While a 64-bit OS can make calls with SMC32 calling conventions, for some + * calls it is necessary to use SMC64 to pass or return 64-bit values. For such + * calls PSCI_0_2_FN_NATIVE(x) will choose the appropriate (native-width) + * function ID. + */ +#ifdef CONFIG_64BIT +#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN64_##name +#else +#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN_##name +#endif + /* * The CPU any Trusted OS is resident on. The trusted OS may reject CPU_OFF * calls to its resident CPU, so we must avoid issuing those. We never migrate @@ -122,8 +134,8 @@ static int psci_migrate(unsigned long cpuid) static int psci_affinity_info(unsigned long target_affinity, unsigned long lowest_affinity_level) { - return invoke_psci_fn(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, - lowest_affinity_level, 0); + return invoke_psci_fn(PSCI_0_2_FN_NATIVE(AFFINITY_INFO), + target_affinity, lowest_affinity_level, 0); } static int psci_migrate_info_type(void) @@ -133,7 +145,8 @@ static int psci_migrate_info_type(void) static unsigned long psci_migrate_info_up_cpu(void) { - return invoke_psci_fn(PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU, 0, 0, 0); + return invoke_psci_fn(PSCI_0_2_FN_NATIVE(MIGRATE_INFO_UP_CPU), + 0, 0, 0); } static int get_set_conduit_method(struct device_node *np) @@ -211,16 +224,16 @@ static void __init psci_init_migrate(void) static void __init psci_0_2_set_functions(void) { pr_info("Using standard PSCI v0.2 function IDs\n"); - psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN64_CPU_SUSPEND; + psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_NATIVE(CPU_SUSPEND); psci_ops.cpu_suspend = psci_cpu_suspend; psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; psci_ops.cpu_off = psci_cpu_off; - psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN64_CPU_ON; + psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_NATIVE(CPU_ON); psci_ops.cpu_on = psci_cpu_on; - psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN64_MIGRATE; + psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_NATIVE(MIGRATE); psci_ops.migrate = psci_migrate; psci_ops.affinity_info = psci_affinity_info; From 470ce3b46187e34185960be90999f5f22bd78b70 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Thu, 2 Jul 2015 18:12:36 +0100 Subject: [PATCH 33/41] ARM: migrate to common PSCI client code Now that the common PSCI client code has been factored out to drivers/firmware, and made safe for 32-bit use, move the 32-bit ARM code over to it. This results in a moderate reduction of duplicated lines, and will prevent further duplication as the PSCI client code is updated for PSCI 1.0 and beyond. The two legacy platform users of the PSCI invocation code are updated to account for interface changes. In both cases the power state parameter (which is constant) is now generated using macros, so that the pack/unpack logic can be killed in preparation for PSCI 1.0 power state changes. Signed-off-by: Mark Rutland Cc: Catalin Marinas Cc: Ashwin Chaugule Cc: Lorenzo Pieralisi Cc: Rob Herring Cc: Russell King Cc: Will Deacon --- arch/arm/Kconfig | 1 + arch/arm/include/asm/psci.h | 23 --- arch/arm/kernel/Makefile | 2 +- arch/arm/kernel/psci.c | 299 ------------------------------ arch/arm/kernel/psci_smp.c | 29 ++- arch/arm/kernel/setup.c | 3 +- arch/arm/mach-highbank/highbank.c | 2 +- arch/arm/mach-highbank/pm.c | 16 +- drivers/cpuidle/cpuidle-calxeda.c | 15 +- 9 files changed, 46 insertions(+), 344 deletions(-) delete mode 100644 arch/arm/kernel/psci.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 3a1eb1bdac03ff..75a62df0cb82ea 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1460,6 +1460,7 @@ config HOTPLUG_CPU config ARM_PSCI bool "Support for the ARM Power State Coordination Interface (PSCI)" depends on CPU_V7 + select ARM_PSCI_FW help Say Y here if you want Linux to communicate with system firmware implementing the PSCI specification for CPU-centric power diff --git a/arch/arm/include/asm/psci.h b/arch/arm/include/asm/psci.h index c25ef3ec6d1f85..68ee3ce17b820e 100644 --- a/arch/arm/include/asm/psci.h +++ b/arch/arm/include/asm/psci.h @@ -14,34 +14,11 @@ #ifndef __ASM_ARM_PSCI_H #define __ASM_ARM_PSCI_H -#define PSCI_POWER_STATE_TYPE_STANDBY 0 -#define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 - -struct psci_power_state { - u16 id; - u8 type; - u8 affinity_level; -}; - -struct psci_operations { - int (*cpu_suspend)(struct psci_power_state state, - unsigned long entry_point); - int (*cpu_off)(struct psci_power_state state); - int (*cpu_on)(unsigned long cpuid, unsigned long entry_point); - int (*migrate)(unsigned long cpuid); - int (*affinity_info)(unsigned long target_affinity, - unsigned long lowest_affinity_level); - int (*migrate_info_type)(void); -}; - -extern struct psci_operations psci_ops; extern struct smp_operations psci_smp_ops; #ifdef CONFIG_ARM_PSCI -int psci_init(void); bool psci_smp_available(void); #else -static inline int psci_init(void) { return 0; } static inline bool psci_smp_available(void) { return false; } #endif diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 752725dcbf4291..2c06383f4d4741 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -86,7 +86,7 @@ obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-$(CONFIG_ARM_VIRT_EXT) += hyp-stub.o ifeq ($(CONFIG_ARM_PSCI),y) -obj-y += psci.o psci-call.o +obj-y += psci-call.o obj-$(CONFIG_SMP) += psci_smp.o endif diff --git a/arch/arm/kernel/psci.c b/arch/arm/kernel/psci.c deleted file mode 100644 index f90fdf4ce7c723..00000000000000 --- a/arch/arm/kernel/psci.c +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * Copyright (C) 2012 ARM Limited - * - * Author: Will Deacon - */ - -#define pr_fmt(fmt) "psci: " fmt - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -struct psci_operations psci_ops; - -static int (*invoke_psci_fn)(u32, u32, u32, u32); -typedef int (*psci_initcall_t)(const struct device_node *); - -asmlinkage int __invoke_psci_fn_hvc(u32, u32, u32, u32); -asmlinkage int __invoke_psci_fn_smc(u32, u32, u32, u32); - -enum psci_function { - PSCI_FN_CPU_SUSPEND, - PSCI_FN_CPU_ON, - PSCI_FN_CPU_OFF, - PSCI_FN_MIGRATE, - PSCI_FN_AFFINITY_INFO, - PSCI_FN_MIGRATE_INFO_TYPE, - PSCI_FN_MAX, -}; - -static u32 psci_function_id[PSCI_FN_MAX]; - -static int psci_to_linux_errno(int errno) -{ - switch (errno) { - case PSCI_RET_SUCCESS: - return 0; - case PSCI_RET_NOT_SUPPORTED: - return -EOPNOTSUPP; - case PSCI_RET_INVALID_PARAMS: - return -EINVAL; - case PSCI_RET_DENIED: - return -EPERM; - }; - - return -EINVAL; -} - -static u32 psci_power_state_pack(struct psci_power_state state) -{ - return ((state.id << PSCI_0_2_POWER_STATE_ID_SHIFT) - & PSCI_0_2_POWER_STATE_ID_MASK) | - ((state.type << PSCI_0_2_POWER_STATE_TYPE_SHIFT) - & PSCI_0_2_POWER_STATE_TYPE_MASK) | - ((state.affinity_level << PSCI_0_2_POWER_STATE_AFFL_SHIFT) - & PSCI_0_2_POWER_STATE_AFFL_MASK); -} - -static int psci_get_version(void) -{ - int err; - - err = invoke_psci_fn(PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0); - return err; -} - -static int psci_cpu_suspend(struct psci_power_state state, - unsigned long entry_point) -{ - int err; - u32 fn, power_state; - - fn = psci_function_id[PSCI_FN_CPU_SUSPEND]; - power_state = psci_power_state_pack(state); - err = invoke_psci_fn(fn, power_state, entry_point, 0); - return psci_to_linux_errno(err); -} - -static int psci_cpu_off(struct psci_power_state state) -{ - int err; - u32 fn, power_state; - - fn = psci_function_id[PSCI_FN_CPU_OFF]; - power_state = psci_power_state_pack(state); - err = invoke_psci_fn(fn, power_state, 0, 0); - return psci_to_linux_errno(err); -} - -static int psci_cpu_on(unsigned long cpuid, unsigned long entry_point) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_CPU_ON]; - err = invoke_psci_fn(fn, cpuid, entry_point, 0); - return psci_to_linux_errno(err); -} - -static int psci_migrate(unsigned long cpuid) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_MIGRATE]; - err = invoke_psci_fn(fn, cpuid, 0, 0); - return psci_to_linux_errno(err); -} - -static int psci_affinity_info(unsigned long target_affinity, - unsigned long lowest_affinity_level) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_AFFINITY_INFO]; - err = invoke_psci_fn(fn, target_affinity, lowest_affinity_level, 0); - return err; -} - -static int psci_migrate_info_type(void) -{ - int err; - u32 fn; - - fn = psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE]; - err = invoke_psci_fn(fn, 0, 0, 0); - return err; -} - -static int get_set_conduit_method(struct device_node *np) -{ - const char *method; - - pr_info("probing for conduit method from DT.\n"); - - if (of_property_read_string(np, "method", &method)) { - pr_warn("missing \"method\" property\n"); - return -ENXIO; - } - - if (!strcmp("hvc", method)) { - invoke_psci_fn = __invoke_psci_fn_hvc; - } else if (!strcmp("smc", method)) { - invoke_psci_fn = __invoke_psci_fn_smc; - } else { - pr_warn("invalid \"method\" property: %s\n", method); - return -EINVAL; - } - return 0; -} - -static void psci_sys_reset(enum reboot_mode reboot_mode, const char *cmd) -{ - invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0); -} - -static void psci_sys_poweroff(void) -{ - invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); -} - -/* - * PSCI Function IDs for v0.2+ are well defined so use - * standard values. - */ -static int psci_0_2_init(struct device_node *np) -{ - int err, ver; - - err = get_set_conduit_method(np); - - if (err) - goto out_put_node; - - ver = psci_get_version(); - - if (ver == PSCI_RET_NOT_SUPPORTED) { - /* PSCI v0.2 mandates implementation of PSCI_ID_VERSION. */ - pr_err("PSCI firmware does not comply with the v0.2 spec.\n"); - err = -EOPNOTSUPP; - goto out_put_node; - } else { - pr_info("PSCIv%d.%d detected in firmware.\n", - PSCI_VERSION_MAJOR(ver), - PSCI_VERSION_MINOR(ver)); - - if (PSCI_VERSION_MAJOR(ver) == 0 && - PSCI_VERSION_MINOR(ver) < 2) { - err = -EINVAL; - pr_err("Conflicting PSCI version detected.\n"); - goto out_put_node; - } - } - - pr_info("Using standard PSCI v0.2 function IDs\n"); - psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_CPU_SUSPEND; - psci_ops.cpu_suspend = psci_cpu_suspend; - - psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; - psci_ops.cpu_off = psci_cpu_off; - - psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_CPU_ON; - psci_ops.cpu_on = psci_cpu_on; - - psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_MIGRATE; - psci_ops.migrate = psci_migrate; - - psci_function_id[PSCI_FN_AFFINITY_INFO] = PSCI_0_2_FN_AFFINITY_INFO; - psci_ops.affinity_info = psci_affinity_info; - - psci_function_id[PSCI_FN_MIGRATE_INFO_TYPE] = - PSCI_0_2_FN_MIGRATE_INFO_TYPE; - psci_ops.migrate_info_type = psci_migrate_info_type; - - arm_pm_restart = psci_sys_reset; - - pm_power_off = psci_sys_poweroff; - -out_put_node: - of_node_put(np); - return err; -} - -/* - * PSCI < v0.2 get PSCI Function IDs via DT. - */ -static int psci_0_1_init(struct device_node *np) -{ - u32 id; - int err; - - err = get_set_conduit_method(np); - - if (err) - goto out_put_node; - - pr_info("Using PSCI v0.1 Function IDs from DT\n"); - - if (!of_property_read_u32(np, "cpu_suspend", &id)) { - psci_function_id[PSCI_FN_CPU_SUSPEND] = id; - psci_ops.cpu_suspend = psci_cpu_suspend; - } - - if (!of_property_read_u32(np, "cpu_off", &id)) { - psci_function_id[PSCI_FN_CPU_OFF] = id; - psci_ops.cpu_off = psci_cpu_off; - } - - if (!of_property_read_u32(np, "cpu_on", &id)) { - psci_function_id[PSCI_FN_CPU_ON] = id; - psci_ops.cpu_on = psci_cpu_on; - } - - if (!of_property_read_u32(np, "migrate", &id)) { - psci_function_id[PSCI_FN_MIGRATE] = id; - psci_ops.migrate = psci_migrate; - } - -out_put_node: - of_node_put(np); - return err; -} - -static const struct of_device_id psci_of_match[] __initconst = { - { .compatible = "arm,psci", .data = psci_0_1_init}, - { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, - {}, -}; - -int __init psci_init(void) -{ - struct device_node *np; - const struct of_device_id *matched_np; - psci_initcall_t init_fn; - - np = of_find_matching_node_and_match(NULL, psci_of_match, &matched_np); - if (!np) - return -ENODEV; - - init_fn = (psci_initcall_t)matched_np->data; - return init_fn(np); -} diff --git a/arch/arm/kernel/psci_smp.c b/arch/arm/kernel/psci_smp.c index 244aaddfbfda00..61c04b02faebb0 100644 --- a/arch/arm/kernel/psci_smp.c +++ b/arch/arm/kernel/psci_smp.c @@ -17,6 +17,8 @@ #include #include #include +#include + #include #include @@ -56,17 +58,29 @@ static int psci_boot_secondary(unsigned int cpu, struct task_struct *idle) } #ifdef CONFIG_HOTPLUG_CPU +int psci_cpu_disable(unsigned int cpu) +{ + /* Fail early if we don't have CPU_OFF support */ + if (!psci_ops.cpu_off) + return -EOPNOTSUPP; + + /* Trusted OS will deny CPU_OFF */ + if (psci_tos_resident_on(cpu)) + return -EPERM; + + return 0; +} + void __ref psci_cpu_die(unsigned int cpu) { - const struct psci_power_state ps = { - .type = PSCI_POWER_STATE_TYPE_POWER_DOWN, - }; + u32 state = PSCI_POWER_STATE_TYPE_POWER_DOWN << + PSCI_0_2_POWER_STATE_TYPE_SHIFT; - if (psci_ops.cpu_off) - psci_ops.cpu_off(ps); + if (psci_ops.cpu_off) + psci_ops.cpu_off(state); - /* We should never return */ - panic("psci: cpu %d failed to shutdown\n", cpu); + /* We should never return */ + panic("psci: cpu %d failed to shutdown\n", cpu); } int __ref psci_cpu_kill(unsigned int cpu) @@ -109,6 +123,7 @@ bool __init psci_smp_available(void) struct smp_operations __initdata psci_smp_ops = { .smp_boot_secondary = psci_boot_secondary, #ifdef CONFIG_HOTPLUG_CPU + .cpu_disable = psci_cpu_disable, .cpu_die = psci_cpu_die, .cpu_kill = psci_cpu_kill, #endif diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 6c777e908a2446..3224680e44f498 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -950,7 +951,7 @@ void __init setup_arch(char **cmdline_p) unflatten_device_tree(); arm_dt_init_cpu_maps(); - psci_init(); + psci_dt_init(); #ifdef CONFIG_SMP if (is_smp()) { if (!mdesc->smp_init || !mdesc->smp_init()) { diff --git a/arch/arm/mach-highbank/highbank.c b/arch/arm/mach-highbank/highbank.c index 231fba0d03e513..6050a14faee6f3 100644 --- a/arch/arm/mach-highbank/highbank.c +++ b/arch/arm/mach-highbank/highbank.c @@ -28,8 +28,8 @@ #include #include #include +#include -#include #include #include #include diff --git a/arch/arm/mach-highbank/pm.c b/arch/arm/mach-highbank/pm.c index 7f2bd85eb9350d..40031169554813 100644 --- a/arch/arm/mach-highbank/pm.c +++ b/arch/arm/mach-highbank/pm.c @@ -16,19 +16,21 @@ #include #include +#include #include #include -#include + +#include + +#define HIGHBANK_SUSPEND_PARAM \ + ((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (1 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) static int highbank_suspend_finish(unsigned long val) { - const struct psci_power_state ps = { - .type = PSCI_POWER_STATE_TYPE_POWER_DOWN, - .affinity_level = 1, - }; - - return psci_ops.cpu_suspend(ps, __pa(cpu_resume)); + return psci_ops.cpu_suspend(HIGHBANK_SUSPEND_PARAM, __pa(cpu_resume)); } static int highbank_pm_enter(suspend_state_t state) diff --git a/drivers/cpuidle/cpuidle-calxeda.c b/drivers/cpuidle/cpuidle-calxeda.c index 9445e6cc02be43..1c3242c3e3a76c 100644 --- a/drivers/cpuidle/cpuidle-calxeda.c +++ b/drivers/cpuidle/cpuidle-calxeda.c @@ -25,16 +25,21 @@ #include #include #include +#include + #include #include -#include + +#include + +#define CALXEDA_IDLE_PARAM \ + ((0 << PSCI_0_2_POWER_STATE_ID_SHIFT) | \ + (0 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) | \ + (PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT)) static int calxeda_idle_finish(unsigned long val) { - const struct psci_power_state ps = { - .type = PSCI_POWER_STATE_TYPE_POWER_DOWN, - }; - return psci_ops.cpu_suspend(ps, __pa(cpu_resume)); + return psci_ops.cpu_suspend(CALXEDA_IDLE_PARAM, __pa(cpu_resume)); } static int calxeda_pwrdown_idle(struct cpuidle_device *dev, From 5503995d80343040c7a0103db37940fa6bb407df Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Thu, 2 Jul 2015 18:12:37 +0100 Subject: [PATCH 34/41] MAINTAINERS: add PSCI entry Add myself and Lorenzo as maintainers of the PSCI client code. Signed-off-by: Mark Rutland Acked-by: Catalin Marinas Cc: Lorenzo Pieralisi Cc: Will Deacon --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 60d24f22427113..734a0d48a96b5c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7789,6 +7789,15 @@ S: Maintained F: include/linux/power_supply.h F: drivers/power/ +POWER STATE COORDINATION INTERFACE (PSCI) +M: Mark Rutland +M: Lorenzo Pieralisi +L: linux-arm-kernel@lists.infradead.org +S: Maintained +F: drivers/firmware/psci.c +F: include/linux/psci.h +F: include/uapi/linux/psci.h + PNP SUPPORT M: Rafael J. Wysocki S: Maintained From 6669b36973cac559d0f6676bf1c5531732cd4625 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 8 Jul 2015 18:16:47 +0100 Subject: [PATCH 35/41] drivers: firmware: psci: add INVALID_ADDRESS return value PSCI 1.0 introduces the INVALID_ADDRESS return value for functions that take an address as input parameter (eg CPU_SUSPEND). This patch adds INVALID_ADDRESS return value to kernel code and updates the PSCI to linux error conversion to take it into account. The kernel error value associated to INVALID_ADDRESS is set to the error returned when the PSCI error code is INVALID_PARAMETERS to comply with current call sites expected return value, given that the kernel at present has no use for the additional error information reported. Signed-off-by: Lorenzo Pieralisi Acked-by: Sudeep Holla Acked-by: Mark Rutland --- drivers/firmware/psci.c | 1 + include/uapi/linux/psci.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index a6956007dd3837..3c86418964c761 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -78,6 +78,7 @@ static int psci_to_linux_errno(int errno) case PSCI_RET_NOT_SUPPORTED: return -EOPNOTSUPP; case PSCI_RET_INVALID_PARAMS: + case PSCI_RET_INVALID_ADDRESS: return -EINVAL; case PSCI_RET_DENIED: return -EPERM; diff --git a/include/uapi/linux/psci.h b/include/uapi/linux/psci.h index 310d83e0a91b6b..64469e64688f3f 100644 --- a/include/uapi/linux/psci.h +++ b/include/uapi/linux/psci.h @@ -86,5 +86,6 @@ #define PSCI_RET_INTERNAL_FAILURE -6 #define PSCI_RET_NOT_PRESENT -7 #define PSCI_RET_DISABLED -8 +#define PSCI_RET_INVALID_ADDRESS -9 #endif /* _UAPI_LINUX_PSCI_H */ From 344b231ddbed3231e8a8bbb30a0ce6331bb07d39 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 8 Jul 2015 18:16:48 +0100 Subject: [PATCH 36/41] drivers: firmware: psci: move power_state handling to generic code Functions implemented on arm64 to check if a power_state parameter is valid and if the power_state implies context loss are not arm64 specific and should be moved to generic code so that they can be reused on arm systems too. This patch moves the functions handling the power_state parameter to generic PSCI firmware layer code. Signed-off-by: Lorenzo Pieralisi Acked-by: Will Deacon Acked-by: Sudeep Holla Cc: Catalin Marinas Cc: Mark Rutland Acked-by: Catalin Marinas --- arch/arm64/kernel/psci.c | 14 -------------- drivers/firmware/psci.c | 15 +++++++++++++++ include/linux/psci.h | 2 ++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 51fd15a16461a9..4be597228b145b 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -30,20 +30,6 @@ #include #include -static bool psci_power_state_loses_context(u32 state) -{ - return state & PSCI_0_2_POWER_STATE_TYPE_MASK; -} - -static bool psci_power_state_is_valid(u32 state) -{ - const u32 valid_mask = PSCI_0_2_POWER_STATE_ID_MASK | - PSCI_0_2_POWER_STATE_TYPE_MASK | - PSCI_0_2_POWER_STATE_AFFL_MASK; - - return !(state & ~valid_mask); -} - static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state); static int __maybe_unused cpu_psci_cpu_init_idle(unsigned int cpu) diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index 3c86418964c761..462b0f95bae295 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -70,6 +70,21 @@ enum psci_function { static u32 psci_function_id[PSCI_FN_MAX]; +#define PSCI_0_2_POWER_STATE_MASK \ + (PSCI_0_2_POWER_STATE_ID_MASK | \ + PSCI_0_2_POWER_STATE_TYPE_MASK | \ + PSCI_0_2_POWER_STATE_AFFL_MASK) + +bool psci_power_state_loses_context(u32 state) +{ + return state & PSCI_0_2_POWER_STATE_TYPE_MASK; +} + +bool psci_power_state_is_valid(u32 state) +{ + return !(state & ~PSCI_0_2_POWER_STATE_MASK); +} + static int psci_to_linux_errno(int errno) { switch (errno) { diff --git a/include/linux/psci.h b/include/linux/psci.h index a682fcc91c3321..12c4865457adc3 100644 --- a/include/linux/psci.h +++ b/include/linux/psci.h @@ -21,6 +21,8 @@ #define PSCI_POWER_STATE_TYPE_POWER_DOWN 1 bool psci_tos_resident_on(int cpu); +bool psci_power_state_loses_context(u32 state); +bool psci_power_state_is_valid(u32 state); struct psci_operations { int (*cpu_suspend)(u32 state, unsigned long entry_point); From fc421f16e7d0e97706c264e8395e9b332289a7f4 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 8 Jul 2015 18:16:49 +0100 Subject: [PATCH 37/41] drivers: firmware: psci: add PSCI_FEATURES call PSCI v1.0 introduces a PSCI_FEATURES call that allows to probe for features related to a specific function identifier. This patch adds PSCI_FEATURES support to the PSCI firmware layer. Signed-off-by: Lorenzo Pieralisi Cc: Mark Rutland --- drivers/firmware/psci.c | 6 ++++++ include/uapi/linux/psci.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index 462b0f95bae295..48ae95182b2a50 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -197,6 +197,12 @@ static void psci_sys_poweroff(void) invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); } +static int __init psci_features(u32 psci_func_id) +{ + return invoke_psci_fn(PSCI_1_0_FN_PSCI_FEATURES, + psci_func_id, 0, 0); +} + /* * Detect the presence of a resident Trusted OS which may cause CPU_OFF to * return DENIED (which would be fatal). diff --git a/include/uapi/linux/psci.h b/include/uapi/linux/psci.h index 64469e64688f3f..187b828d77b394 100644 --- a/include/uapi/linux/psci.h +++ b/include/uapi/linux/psci.h @@ -46,6 +46,8 @@ #define PSCI_0_2_FN64_MIGRATE PSCI_0_2_FN64(5) #define PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU PSCI_0_2_FN64(7) +#define PSCI_1_0_FN_PSCI_FEATURES PSCI_0_2_FN(10) + /* PSCI v0.2 power state encoding for CPU_SUSPEND function */ #define PSCI_0_2_POWER_STATE_ID_MASK 0xffff #define PSCI_0_2_POWER_STATE_ID_SHIFT 0 From ba0b70ec1ca73ff49c48069cca8968a31c8dc6bb Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 8 Jul 2015 18:16:50 +0100 Subject: [PATCH 38/41] drivers: firmware: psci: add extended stateid power_state support PSCI v1.0 augmented the power_state parameter format specification (extended stateid) and introduced a way to probe it through the PSCI_FEATURES interface. This patch implements code that detects the power_state format at run-time through the PSCI_FEATURES interface, so that the power_state argument can be properly detected and validated in the kernel according to the information provided through firmware. Signed-off-by: Lorenzo Pieralisi Cc: Mark Rutland --- drivers/firmware/psci.c | 34 ++++++++++++++++++++++++++++++++-- include/uapi/linux/psci.h | 12 ++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index 48ae95182b2a50..9f274eb30a63ec 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -75,14 +75,34 @@ static u32 psci_function_id[PSCI_FN_MAX]; PSCI_0_2_POWER_STATE_TYPE_MASK | \ PSCI_0_2_POWER_STATE_AFFL_MASK) +#define PSCI_1_0_EXT_POWER_STATE_MASK \ + (PSCI_1_0_EXT_POWER_STATE_ID_MASK | \ + PSCI_1_0_EXT_POWER_STATE_TYPE_MASK) + +static u32 psci_cpu_suspend_feature; + +static inline bool psci_has_ext_power_state(void) +{ + return psci_cpu_suspend_feature & + PSCI_1_0_FEATURES_CPU_SUSPEND_PF_MASK; +} + bool psci_power_state_loses_context(u32 state) { - return state & PSCI_0_2_POWER_STATE_TYPE_MASK; + const u32 mask = psci_has_ext_power_state() ? + PSCI_1_0_EXT_POWER_STATE_TYPE_MASK : + PSCI_0_2_POWER_STATE_TYPE_MASK; + + return state & mask; } bool psci_power_state_is_valid(u32 state) { - return !(state & ~PSCI_0_2_POWER_STATE_MASK); + const u32 valid_mask = psci_has_ext_power_state() ? + PSCI_1_0_EXT_POWER_STATE_MASK : + PSCI_0_2_POWER_STATE_MASK; + + return !(state & ~valid_mask); } static int psci_to_linux_errno(int errno) @@ -203,6 +223,14 @@ static int __init psci_features(u32 psci_func_id) psci_func_id, 0, 0); } +static void __init psci_init_cpu_suspend(void) +{ + int feature = psci_features(psci_function_id[PSCI_FN_CPU_SUSPEND]); + + if (feature != PSCI_RET_NOT_SUPPORTED) + psci_cpu_suspend_feature = feature; +} + /* * Detect the presence of a resident Trusted OS which may cause CPU_OFF to * return DENIED (which would be fatal). @@ -287,6 +315,8 @@ static int __init psci_probe(void) psci_init_migrate(); + psci_init_cpu_suspend(); + return 0; } diff --git a/include/uapi/linux/psci.h b/include/uapi/linux/psci.h index 187b828d77b394..0a9485f3c6c370 100644 --- a/include/uapi/linux/psci.h +++ b/include/uapi/linux/psci.h @@ -58,6 +58,13 @@ #define PSCI_0_2_POWER_STATE_AFFL_MASK \ (0x3 << PSCI_0_2_POWER_STATE_AFFL_SHIFT) +/* PSCI extended power state encoding for CPU_SUSPEND function */ +#define PSCI_1_0_EXT_POWER_STATE_ID_MASK 0xfffffff +#define PSCI_1_0_EXT_POWER_STATE_ID_SHIFT 0 +#define PSCI_1_0_EXT_POWER_STATE_TYPE_SHIFT 30 +#define PSCI_1_0_EXT_POWER_STATE_TYPE_MASK \ + (0x1 << PSCI_1_0_EXT_POWER_STATE_TYPE_SHIFT) + /* PSCI v0.2 affinity level state returned by AFFINITY_INFO */ #define PSCI_0_2_AFFINITY_LEVEL_ON 0 #define PSCI_0_2_AFFINITY_LEVEL_OFF 1 @@ -78,6 +85,11 @@ #define PSCI_VERSION_MINOR(ver) \ ((ver) & PSCI_VERSION_MINOR_MASK) +/* PSCI features decoding (>=1.0) */ +#define PSCI_1_0_FEATURES_CPU_SUSPEND_PF_SHIFT 1 +#define PSCI_1_0_FEATURES_CPU_SUSPEND_PF_MASK \ + (0x1 << PSCI_1_0_FEATURES_CPU_SUSPEND_PF_SHIFT) + /* PSCI return values (inclusive of all PSCI versions) */ #define PSCI_RET_SUCCESS 0 #define PSCI_RET_NOT_SUPPORTED -1 From f37c4364ea5ac3e4cad082286e98f78d7e891f54 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Wed, 8 Jul 2015 18:16:51 +0100 Subject: [PATCH 39/41] drivers: firmware: psci: add PSCI v1.0 DT bindings PSCI 1.0 is designed to be fully compliant to the PSCI 0.2 specification, with minor differences that are described in the PSCI specification. In particular, PSCI v1.0 augments the specification with a new power_state format (extended stateid - probeable through the PSCI_FEATURES call), changes some function return codes and functions usage requirements wrt PSCI 0.2. These changes mean that 1.0 vs 0.2 compliancy should be enforced through a DT compatible string that allows firmware to specify 1.0 only compliancy so that older kernels are prevented from using PSCI 1.0 FW implementations in a non-compatible way (eg by calling a 1.0 FW implementation and expecting 0.2 behaviour). This patch adds PSCI 1.0 DT bindings and related compatible string. Signed-off-by: Lorenzo Pieralisi Acked-by: Sudeep Holla Cc: Mark Rutland --- Documentation/devicetree/bindings/arm/psci.txt | 6 ++++++ drivers/firmware/psci.c | 1 + 2 files changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/psci.txt b/Documentation/devicetree/bindings/arm/psci.txt index 5aa40ede0e9933..a9adab84e2feb7 100644 --- a/Documentation/devicetree/bindings/arm/psci.txt +++ b/Documentation/devicetree/bindings/arm/psci.txt @@ -31,6 +31,10 @@ Main node required properties: support, but are permitted to be present for compatibility with existing software when "arm,psci" is later in the compatible list. + * "arm,psci-1.0" : for implementations complying to PSCI 1.0. PSCI 1.0 is + backward compatible with PSCI 0.2 with minor specification updates, + as defined in the PSCI specification[2]. + - method : The method of calling the PSCI firmware. Permitted values are: @@ -100,3 +104,5 @@ Case 3: PSCI v0.2 and PSCI v0.1. [1] Kernel documentation - ARM idle states bindings Documentation/devicetree/bindings/arm/idle-states.txt +[2] Power State Coordination Interface (PSCI) specification + http://infocenter.arm.com/help/topic/com.arm.doc.den0022c/DEN0022C_Power_State_Coordination_Interface.pdf diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index 9f274eb30a63ec..c006ca92cb8bb3 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -392,6 +392,7 @@ static int __init psci_0_1_init(struct device_node *np) static const struct of_device_id psci_of_match[] __initconst = { { .compatible = "arm,psci", .data = psci_0_1_init}, { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, + { .compatible = "arm,psci-1.0", .data = psci_0_2_init}, {}, }; From cbaf4001f6c4cd0289e1331769a86b188e704098 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 18 Jun 2015 15:41:33 +0100 Subject: [PATCH 40/41] drivers: firmware: psci: define more generic PSCI_FN_NATIVE macro This patch replaces the definition and usage of PSCI_0_2_FN_NATIVE with the new and more generic macro PSCI_FN_NATIVE that can be used with any version. This will be useful for the new features introduced in PSCIv1.0 and for any future revisions. Cc: Mark Rutland Cc: Lorenzo Pieralisi Signed-off-by: Sudeep Holla --- drivers/firmware/psci.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index c006ca92cb8bb3..e1818529bd0926 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -30,13 +30,13 @@ /* * While a 64-bit OS can make calls with SMC32 calling conventions, for some * calls it is necessary to use SMC64 to pass or return 64-bit values. For such - * calls PSCI_0_2_FN_NATIVE(x) will choose the appropriate (native-width) + * calls PSCI_FN_NATIVE(x) will choose the appropriate (native-width) * function ID. */ #ifdef CONFIG_64BIT -#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN64_##name +#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name #else -#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN_##name +#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN_##name #endif /* @@ -170,7 +170,7 @@ static int psci_migrate(unsigned long cpuid) static int psci_affinity_info(unsigned long target_affinity, unsigned long lowest_affinity_level) { - return invoke_psci_fn(PSCI_0_2_FN_NATIVE(AFFINITY_INFO), + return invoke_psci_fn(PSCI_FN_NATIVE(0_2, AFFINITY_INFO), target_affinity, lowest_affinity_level, 0); } @@ -181,7 +181,7 @@ static int psci_migrate_info_type(void) static unsigned long psci_migrate_info_up_cpu(void) { - return invoke_psci_fn(PSCI_0_2_FN_NATIVE(MIGRATE_INFO_UP_CPU), + return invoke_psci_fn(PSCI_FN_NATIVE(0_2, MIGRATE_INFO_UP_CPU), 0, 0, 0); } @@ -274,16 +274,17 @@ static void __init psci_init_migrate(void) static void __init psci_0_2_set_functions(void) { pr_info("Using standard PSCI v0.2 function IDs\n"); - psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_NATIVE(CPU_SUSPEND); + psci_function_id[PSCI_FN_CPU_SUSPEND] = + PSCI_FN_NATIVE(0_2, CPU_SUSPEND); psci_ops.cpu_suspend = psci_cpu_suspend; psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; psci_ops.cpu_off = psci_cpu_off; - psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_NATIVE(CPU_ON); + psci_function_id[PSCI_FN_CPU_ON] = PSCI_FN_NATIVE(0_2, CPU_ON); psci_ops.cpu_on = psci_cpu_on; - psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_NATIVE(MIGRATE); + psci_function_id[PSCI_FN_MIGRATE] = PSCI_FN_NATIVE(0_2, MIGRATE); psci_ops.migrate = psci_migrate; psci_ops.affinity_info = psci_affinity_info; From 0c2d92e1c6c67ca088e95122a8f9f1a3264720f0 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 18 Jun 2015 15:41:34 +0100 Subject: [PATCH 41/41] drivers: firmware: psci: add system suspend support PSCI v1.0 introduces a new API called PSCI_SYSTEM_SUSPEND. This API provides the mechanism by which the calling OS can request entry into the deepest possible system sleep state. It meets all the necessary preconditions for entering suspend to RAM state in Linux. This patch adds support for PSCI_SYSTEM_SUSPEND in psci firmware and registers a psci system suspend operation to implement the suspend-to-RAM(s2r) in a generic way on all the platforms implementing PSCI. Cc: Mark Rutland Cc: Lorenzo Pieralisi Signed-off-by: Sudeep Holla --- drivers/firmware/psci.c | 31 +++++++++++++++++++++++++++++++ include/uapi/linux/psci.h | 3 +++ 2 files changed, 34 insertions(+) diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index e1818529bd0926..736370d2e1f7cc 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -20,12 +20,14 @@ #include #include #include +#include #include #include #include #include +#include /* * While a 64-bit OS can make calls with SMC32 calling conventions, for some @@ -223,6 +225,33 @@ static int __init psci_features(u32 psci_func_id) psci_func_id, 0, 0); } +static int psci_system_suspend(unsigned long unused) +{ + return invoke_psci_fn(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), + virt_to_phys(cpu_resume), 0, 0); +} + +static int psci_system_suspend_enter(suspend_state_t state) +{ + return cpu_suspend(0, psci_system_suspend); +} + +static const struct platform_suspend_ops psci_suspend_ops = { + .valid = suspend_valid_only_mem, + .enter = psci_system_suspend_enter, +}; + +static void __init psci_init_system_suspend(void) +{ + if (!IS_ENABLED(CONFIG_SUSPEND)) + return; + + if (psci_features(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND))) + return; + + suspend_set_ops(&psci_suspend_ops); +} + static void __init psci_init_cpu_suspend(void) { int feature = psci_features(psci_function_id[PSCI_FN_CPU_SUSPEND]); @@ -318,6 +347,8 @@ static int __init psci_probe(void) psci_init_cpu_suspend(); + psci_init_system_suspend(); + return 0; } diff --git a/include/uapi/linux/psci.h b/include/uapi/linux/psci.h index 0a9485f3c6c370..3d7a0fc021a75a 100644 --- a/include/uapi/linux/psci.h +++ b/include/uapi/linux/psci.h @@ -47,6 +47,9 @@ #define PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU PSCI_0_2_FN64(7) #define PSCI_1_0_FN_PSCI_FEATURES PSCI_0_2_FN(10) +#define PSCI_1_0_FN_SYSTEM_SUSPEND PSCI_0_2_FN(14) + +#define PSCI_1_0_FN64_SYSTEM_SUSPEND PSCI_0_2_FN64(14) /* PSCI v0.2 power state encoding for CPU_SUSPEND function */ #define PSCI_0_2_POWER_STATE_ID_MASK 0xffff