aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/nouveau/nvkm/falcon
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/falcon')
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/falcon/Kbuild3
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/falcon/base.c40
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.c575
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.h213
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0137c63d.c436
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0148cdec.c264
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/falcon/v1.c124
7 files changed, 1633 insertions, 22 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/falcon/Kbuild b/drivers/gpu/drm/nouveau/nvkm/falcon/Kbuild
index 584863db9bfc..2aa040ba39e5 100644
--- a/drivers/gpu/drm/nouveau/nvkm/falcon/Kbuild
+++ b/drivers/gpu/drm/nouveau/nvkm/falcon/Kbuild
@@ -1,2 +1,5 @@
nvkm-y += nvkm/falcon/base.o
nvkm-y += nvkm/falcon/v1.o
+nvkm-y += nvkm/falcon/msgqueue.o
+nvkm-y += nvkm/falcon/msgqueue_0137c63d.o
+nvkm-y += nvkm/falcon/msgqueue_0148cdec.o
diff --git a/drivers/gpu/drm/nouveau/nvkm/falcon/base.c b/drivers/gpu/drm/nouveau/nvkm/falcon/base.c
index 4852f313762f..1b7f48efd8b1 100644
--- a/drivers/gpu/drm/nouveau/nvkm/falcon/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/falcon/base.c
@@ -41,14 +41,22 @@ void
nvkm_falcon_load_dmem(struct nvkm_falcon *falcon, void *data, u32 start,
u32 size, u8 port)
{
+ mutex_lock(&falcon->dmem_mutex);
+
falcon->func->load_dmem(falcon, data, start, size, port);
+
+ mutex_unlock(&falcon->dmem_mutex);
}
void
nvkm_falcon_read_dmem(struct nvkm_falcon *falcon, u32 start, u32 size, u8 port,
void *data)
{
+ mutex_lock(&falcon->dmem_mutex);
+
falcon->func->read_dmem(falcon, start, size, port, data);
+
+ mutex_unlock(&falcon->dmem_mutex);
}
void
@@ -129,6 +137,9 @@ nvkm_falcon_clear_interrupt(struct nvkm_falcon *falcon, u32 mask)
void
nvkm_falcon_put(struct nvkm_falcon *falcon, const struct nvkm_subdev *user)
{
+ if (unlikely(!falcon))
+ return;
+
mutex_lock(&falcon->mutex);
if (falcon->user == user) {
nvkm_debug(falcon->user, "released %s falcon\n", falcon->name);
@@ -159,6 +170,7 @@ nvkm_falcon_ctor(const struct nvkm_falcon_func *func,
struct nvkm_subdev *subdev, const char *name, u32 addr,
struct nvkm_falcon *falcon)
{
+ u32 debug_reg;
u32 reg;
falcon->func = func;
@@ -166,6 +178,7 @@ nvkm_falcon_ctor(const struct nvkm_falcon_func *func,
falcon->name = name;
falcon->addr = addr;
mutex_init(&falcon->mutex);
+ mutex_init(&falcon->dmem_mutex);
reg = nvkm_falcon_rd32(falcon, 0x12c);
falcon->version = reg & 0xf;
@@ -177,8 +190,31 @@ nvkm_falcon_ctor(const struct nvkm_falcon_func *func,
falcon->code.limit = (reg & 0x1ff) << 8;
falcon->data.limit = (reg & 0x3fe00) >> 1;
- reg = nvkm_falcon_rd32(falcon, 0xc08);
- falcon->debug = (reg >> 20) & 0x1;
+ switch (subdev->index) {
+ case NVKM_ENGINE_GR:
+ debug_reg = 0x0;
+ break;
+ case NVKM_SUBDEV_PMU:
+ debug_reg = 0xc08;
+ break;
+ case NVKM_ENGINE_NVDEC:
+ debug_reg = 0xd00;
+ break;
+ case NVKM_ENGINE_SEC2:
+ debug_reg = 0x408;
+ falcon->has_emem = true;
+ break;
+ default:
+ nvkm_warn(subdev, "unsupported falcon %s!\n",
+ nvkm_subdev_name[subdev->index]);
+ debug_reg = 0;
+ break;
+ }
+
+ if (debug_reg) {
+ u32 val = nvkm_falcon_rd32(falcon, debug_reg);
+ falcon->debug = (val >> 20) & 0x1;
+ }
}
void
diff --git a/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.c b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.c
new file mode 100644
index 000000000000..d45d7947a964
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.c
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include "msgqueue.h"
+#include <engine/falcon.h>
+
+#include <subdev/secboot.h>
+
+
+#define HDR_SIZE sizeof(struct nvkm_msgqueue_hdr)
+#define QUEUE_ALIGNMENT 4
+/* max size of the messages we can receive */
+#define MSG_BUF_SIZE 128
+
+static int
+msg_queue_open(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+
+ mutex_lock(&queue->mutex);
+
+ queue->position = nvkm_falcon_rd32(falcon, queue->tail_reg);
+
+ return 0;
+}
+
+static void
+msg_queue_close(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue,
+ bool commit)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+
+ if (commit)
+ nvkm_falcon_wr32(falcon, queue->tail_reg, queue->position);
+
+ mutex_unlock(&queue->mutex);
+}
+
+static bool
+msg_queue_empty(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+ u32 head, tail;
+
+ head = nvkm_falcon_rd32(falcon, queue->head_reg);
+ tail = nvkm_falcon_rd32(falcon, queue->tail_reg);
+
+ return head == tail;
+}
+
+static int
+msg_queue_pop(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue,
+ void *data, u32 size)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ u32 head, tail, available;
+
+ head = nvkm_falcon_rd32(falcon, queue->head_reg);
+ /* has the buffer looped? */
+ if (head < queue->position)
+ queue->position = queue->offset;
+
+ tail = queue->position;
+
+ available = head - tail;
+
+ if (available == 0) {
+ nvkm_warn(subdev, "no message data available\n");
+ return 0;
+ }
+
+ if (size > available) {
+ nvkm_warn(subdev, "message data smaller than read request\n");
+ size = available;
+ }
+
+ nvkm_falcon_read_dmem(priv->falcon, tail, size, 0, data);
+ queue->position += ALIGN(size, QUEUE_ALIGNMENT);
+
+ return size;
+}
+
+static int
+msg_queue_read(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue,
+ struct nvkm_msgqueue_hdr *hdr)
+{
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ int err;
+
+ err = msg_queue_open(priv, queue);
+ if (err) {
+ nvkm_error(subdev, "fail to open queue %d\n", queue->index);
+ return err;
+ }
+
+ if (msg_queue_empty(priv, queue)) {
+ err = 0;
+ goto close;
+ }
+
+ err = msg_queue_pop(priv, queue, hdr, HDR_SIZE);
+ if (err >= 0 && err != HDR_SIZE)
+ err = -EINVAL;
+ if (err < 0) {
+ nvkm_error(subdev, "failed to read message header: %d\n", err);
+ goto close;
+ }
+
+ if (hdr->size > MSG_BUF_SIZE) {
+ nvkm_error(subdev, "message too big (%d bytes)\n", hdr->size);
+ err = -ENOSPC;
+ goto close;
+ }
+
+ if (hdr->size > HDR_SIZE) {
+ u32 read_size = hdr->size - HDR_SIZE;
+
+ err = msg_queue_pop(priv, queue, (hdr + 1), read_size);
+ if (err >= 0 && err != read_size)
+ err = -EINVAL;
+ if (err < 0) {
+ nvkm_error(subdev, "failed to read message: %d\n", err);
+ goto close;
+ }
+ }
+
+close:
+ msg_queue_close(priv, queue, (err >= 0));
+
+ return err;
+}
+
+static bool
+cmd_queue_has_room(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue,
+ u32 size, bool *rewind)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+ u32 head, tail, free;
+
+ size = ALIGN(size, QUEUE_ALIGNMENT);
+
+ head = nvkm_falcon_rd32(falcon, queue->head_reg);
+ tail = nvkm_falcon_rd32(falcon, queue->tail_reg);
+
+ if (head >= tail) {
+ free = queue->offset + queue->size - head;
+ free -= HDR_SIZE;
+
+ if (size > free) {
+ *rewind = true;
+ head = queue->offset;
+ }
+ }
+
+ if (head < tail)
+ free = tail - head - 1;
+
+ return size <= free;
+}
+
+static int
+cmd_queue_push(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue,
+ void *data, u32 size)
+{
+ nvkm_falcon_load_dmem(priv->falcon, data, queue->position, size, 0);
+ queue->position += ALIGN(size, QUEUE_ALIGNMENT);
+
+ return 0;
+}
+
+/* REWIND unit is always 0x00 */
+#define MSGQUEUE_UNIT_REWIND 0x00
+
+static void
+cmd_queue_rewind(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue)
+{
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ struct nvkm_msgqueue_hdr cmd;
+ int err;
+
+ cmd.unit_id = MSGQUEUE_UNIT_REWIND;
+ cmd.size = sizeof(cmd);
+ err = cmd_queue_push(priv, queue, &cmd, cmd.size);
+ if (err)
+ nvkm_error(subdev, "queue %d rewind failed\n", queue->index);
+ else
+ nvkm_error(subdev, "queue %d rewinded\n", queue->index);
+
+ queue->position = queue->offset;
+}
+
+static int
+cmd_queue_open(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue,
+ u32 size)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ bool rewind = false;
+
+ mutex_lock(&queue->mutex);
+
+ if (!cmd_queue_has_room(priv, queue, size, &rewind)) {
+ nvkm_error(subdev, "queue full\n");
+ mutex_unlock(&queue->mutex);
+ return -EAGAIN;
+ }
+
+ queue->position = nvkm_falcon_rd32(falcon, queue->head_reg);
+
+ if (rewind)
+ cmd_queue_rewind(priv, queue);
+
+ return 0;
+}
+
+static void
+cmd_queue_close(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_queue *queue,
+ bool commit)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+
+ if (commit)
+ nvkm_falcon_wr32(falcon, queue->head_reg, queue->position);
+
+ mutex_unlock(&queue->mutex);
+}
+
+static int
+cmd_write(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_hdr *cmd,
+ struct nvkm_msgqueue_queue *queue)
+{
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ static unsigned long timeout = ~0;
+ unsigned long end_jiffies = jiffies + msecs_to_jiffies(timeout);
+ int ret = -EAGAIN;
+ bool commit = true;
+
+ while (ret == -EAGAIN && time_before(jiffies, end_jiffies))
+ ret = cmd_queue_open(priv, queue, cmd->size);
+ if (ret) {
+ nvkm_error(subdev, "pmu_queue_open_write failed\n");
+ return ret;
+ }
+
+ ret = cmd_queue_push(priv, queue, cmd, cmd->size);
+ if (ret) {
+ nvkm_error(subdev, "pmu_queue_push failed\n");
+ commit = false;
+ }
+
+ cmd_queue_close(priv, queue, commit);
+
+ return ret;
+}
+
+static struct nvkm_msgqueue_seq *
+msgqueue_seq_acquire(struct nvkm_msgqueue *priv)
+{
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ struct nvkm_msgqueue_seq *seq;
+ u32 index;
+
+ mutex_lock(&priv->seq_lock);
+
+ index = find_first_zero_bit(priv->seq_tbl, NVKM_MSGQUEUE_NUM_SEQUENCES);
+
+ if (index >= NVKM_MSGQUEUE_NUM_SEQUENCES) {
+ nvkm_error(subdev, "no free sequence available\n");
+ mutex_unlock(&priv->seq_lock);
+ return ERR_PTR(-EAGAIN);
+ }
+
+ set_bit(index, priv->seq_tbl);
+
+ mutex_unlock(&priv->seq_lock);
+
+ seq = &priv->seq[index];
+ seq->state = SEQ_STATE_PENDING;
+
+ return seq;
+}
+
+static void
+msgqueue_seq_release(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_seq *seq)
+{
+ /* no need to acquire seq_lock since clear_bit is atomic */
+ seq->state = SEQ_STATE_FREE;
+ seq->callback = NULL;
+ seq->completion = NULL;
+ clear_bit(seq->id, priv->seq_tbl);
+}
+
+/* specifies that we want to know the command status in the answer message */
+#define CMD_FLAGS_STATUS BIT(0)
+/* specifies that we want an interrupt when the answer message is queued */
+#define CMD_FLAGS_INTR BIT(1)
+
+int
+nvkm_msgqueue_post(struct nvkm_msgqueue *priv, enum msgqueue_msg_priority prio,
+ struct nvkm_msgqueue_hdr *cmd, nvkm_msgqueue_callback cb,
+ struct completion *completion, bool wait_init)
+{
+ struct nvkm_msgqueue_seq *seq;
+ struct nvkm_msgqueue_queue *queue;
+ int ret;
+
+ if (wait_init && !wait_for_completion_timeout(&priv->init_done,
+ msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ queue = priv->func->cmd_queue(priv, prio);
+ if (IS_ERR(queue))
+ return PTR_ERR(queue);
+
+ seq = msgqueue_seq_acquire(priv);
+ if (IS_ERR(seq))
+ return PTR_ERR(seq);
+
+ cmd->seq_id = seq->id;
+ cmd->ctrl_flags = CMD_FLAGS_STATUS | CMD_FLAGS_INTR;
+
+ seq->callback = cb;
+ seq->state = SEQ_STATE_USED;
+ seq->completion = completion;
+
+ ret = cmd_write(priv, cmd, queue);
+ if (ret) {
+ seq->state = SEQ_STATE_PENDING;
+ msgqueue_seq_release(priv, seq);
+ }
+
+ return ret;
+}
+
+static int
+msgqueue_msg_handle(struct nvkm_msgqueue *priv, struct nvkm_msgqueue_hdr *hdr)
+{
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ struct nvkm_msgqueue_seq *seq;
+
+ seq = &priv->seq[hdr->seq_id];
+ if (seq->state != SEQ_STATE_USED && seq->state != SEQ_STATE_CANCELLED) {
+ nvkm_error(subdev, "msg for unknown sequence %d", seq->id);
+ return -EINVAL;
+ }
+
+ if (seq->state == SEQ_STATE_USED) {
+ if (seq->callback)
+ seq->callback(priv, hdr);
+ }
+
+ if (seq->completion)
+ complete(seq->completion);
+
+ msgqueue_seq_release(priv, seq);
+
+ return 0;
+}
+
+static int
+msgqueue_handle_init_msg(struct nvkm_msgqueue *priv,
+ struct nvkm_msgqueue_hdr *hdr)
+{
+ struct nvkm_falcon *falcon = priv->falcon;
+ const struct nvkm_subdev *subdev = falcon->owner;
+ u32 tail;
+ u32 tail_reg;
+ int ret;
+
+ /*
+ * Of course the message queue registers vary depending on the falcon
+ * used...
+ */
+ switch (falcon->owner->index) {
+ case NVKM_SUBDEV_PMU:
+ tail_reg = 0x4cc;
+ break;
+ case NVKM_ENGINE_SEC2:
+ tail_reg = 0xa34;
+ break;
+ default:
+ nvkm_error(subdev, "falcon %s unsupported for msgqueue!\n",
+ nvkm_subdev_name[falcon->owner->index]);
+ return -EINVAL;
+ }
+
+ /*
+ * Read the message - queues are not initialized yet so we cannot rely
+ * on msg_queue_read()
+ */
+ tail = nvkm_falcon_rd32(falcon, tail_reg);
+ nvkm_falcon_read_dmem(falcon, tail, HDR_SIZE, 0, hdr);
+
+ if (hdr->size > MSG_BUF_SIZE) {
+ nvkm_error(subdev, "message too big (%d bytes)\n", hdr->size);
+ return -ENOSPC;
+ }
+
+ nvkm_falcon_read_dmem(falcon, tail + HDR_SIZE, hdr->size - HDR_SIZE, 0,
+ (hdr + 1));
+
+ tail += ALIGN(hdr->size, QUEUE_ALIGNMENT);
+ nvkm_falcon_wr32(falcon, tail_reg, tail);
+
+ ret = priv->func->init_func->init_callback(priv, hdr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+void
+nvkm_msgqueue_process_msgs(struct nvkm_msgqueue *priv,
+ struct nvkm_msgqueue_queue *queue)
+{
+ /*
+ * We are invoked from a worker thread, so normally we have plenty of
+ * stack space to work with.
+ */
+ u8 msg_buffer[MSG_BUF_SIZE];
+ struct nvkm_msgqueue_hdr *hdr = (void *)msg_buffer;
+ int ret;
+
+ /* the first message we receive must be the init message */
+ if ((!priv->init_msg_received)) {
+ ret = msgqueue_handle_init_msg(priv, hdr);
+ if (!ret)
+ priv->init_msg_received = true;
+ } else {
+ while (msg_queue_read(priv, queue, hdr) > 0)
+ msgqueue_msg_handle(priv, hdr);
+ }
+}
+
+void
+nvkm_msgqueue_write_cmdline(struct nvkm_msgqueue *queue, void *buf)
+{
+ if (!queue || !queue->func || !queue->func->init_func)
+ return;
+
+ queue->func->init_func->gen_cmdline(queue, buf);
+}
+
+int
+nvkm_msgqueue_acr_boot_falcons(struct nvkm_msgqueue *queue,
+ unsigned long falcon_mask)
+{
+ unsigned long falcon;
+
+ if (!queue || !queue->func->acr_func)
+ return -ENODEV;
+
+ /* Does the firmware support booting multiple falcons? */
+ if (queue->func->acr_func->boot_multiple_falcons)
+ return queue->func->acr_func->boot_multiple_falcons(queue,
+ falcon_mask);
+
+ /* Else boot all requested falcons individually */
+ if (!queue->func->acr_func->boot_falcon)
+ return -ENODEV;
+
+ for_each_set_bit(falcon, &falcon_mask, NVKM_SECBOOT_FALCON_END) {
+ int ret = queue->func->acr_func->boot_falcon(queue, falcon);
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int
+nvkm_msgqueue_new(u32 version, struct nvkm_falcon *falcon,
+ const struct nvkm_secboot *sb, struct nvkm_msgqueue **queue)
+{
+ const struct nvkm_subdev *subdev = falcon->owner;
+ int ret = -EINVAL;
+
+ switch (version) {
+ case 0x0137c63d:
+ ret = msgqueue_0137c63d_new(falcon, sb, queue);
+ break;
+ case 0x0137bca5:
+ ret = msgqueue_0137bca5_new(falcon, sb, queue);
+ break;
+ case 0x0148cdec:
+ ret = msgqueue_0148cdec_new(falcon, sb, queue);
+ break;
+ default:
+ nvkm_error(subdev, "unhandled firmware version 0x%08x\n",
+ version);
+ break;
+ }
+
+ if (ret == 0) {
+ nvkm_debug(subdev, "firmware version: 0x%08x\n", version);
+ (*queue)->fw_version = version;
+ }
+
+ return ret;
+}
+
+void
+nvkm_msgqueue_del(struct nvkm_msgqueue **queue)
+{
+ if (*queue) {
+ (*queue)->func->dtor(*queue);
+ *queue = NULL;
+ }
+}
+
+void
+nvkm_msgqueue_recv(struct nvkm_msgqueue *queue)
+{
+ if (!queue->func || !queue->func->recv) {
+ const struct nvkm_subdev *subdev = queue->falcon->owner;
+
+ nvkm_warn(subdev, "missing msgqueue recv function\n");
+ return;
+ }
+
+ queue->func->recv(queue);
+}
+
+int
+nvkm_msgqueue_reinit(struct nvkm_msgqueue *queue)
+{
+ /* firmware not set yet... */
+ if (!queue)
+ return 0;
+
+ queue->init_msg_received = false;
+ reinit_completion(&queue->init_done);
+
+ return 0;
+}
+
+void
+nvkm_msgqueue_ctor(const struct nvkm_msgqueue_func *func,
+ struct nvkm_falcon *falcon,
+ struct nvkm_msgqueue *queue)
+{
+ int i;
+
+ queue->func = func;
+ queue->falcon = falcon;
+ mutex_init(&queue->seq_lock);
+ for (i = 0; i < NVKM_MSGQUEUE_NUM_SEQUENCES; i++)
+ queue->seq[i].id = i;
+
+ init_completion(&queue->init_done);
+
+
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.h b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.h
new file mode 100644
index 000000000000..13b54f8d8e04
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef __NVKM_CORE_FALCON_MSGQUEUE_H
+#define __NVKM_CORE_FALCON_MSGQUEUE_H
+
+#include <core/msgqueue.h>
+
+/*
+ * The struct nvkm_msgqueue (named so for lack of better candidate) manages
+ * a firmware (typically, NVIDIA signed firmware) running under a given falcon.
+ *
+ * Such firmwares expect to receive commands (through one or several command
+ * queues) and will reply to such command by sending messages (using one
+ * message queue).
+ *
+ * Each firmware can support one or several units - ACR for managing secure
+ * falcons, PMU for power management, etc. A unit can be seen as a class to
+ * which command can be sent.
+ *
+ * One usage example would be to send a command to the SEC falcon to ask it to
+ * reset a secure falcon. The SEC falcon will receive the command, process it,
+ * and send a message to signal success or failure. Only when the corresponding
+ * message is received can the requester assume the request has been processed.
+ *
+ * Since we expect many variations between the firmwares NVIDIA will release
+ * across GPU generations, this library is built in a very modular way. Message
+ * formats and queues details (such as number of usage) are left to
+ * specializations of struct nvkm_msgqueue, while the functions in msgqueue.c
+ * take care of posting commands and processing messages in a fashion that is
+ * universal.
+ *
+ */
+
+enum msgqueue_msg_priority {
+ MSGQUEUE_MSG_PRIORITY_HIGH,
+ MSGQUEUE_MSG_PRIORITY_LOW,
+};
+
+/**
+ * struct nvkm_msgqueue_hdr - header for all commands/messages
+ * @unit_id: id of firmware using receiving the command/sending the message
+ * @size: total size of command/message
+ * @ctrl_flags: type of command/message
+ * @seq_id: used to match a message from its corresponding command
+ */
+struct nvkm_msgqueue_hdr {
+ u8 unit_id;
+ u8 size;
+ u8 ctrl_flags;
+ u8 seq_id;
+};
+
+/**
+ * struct nvkm_msgqueue_msg - base message.
+ *
+ * This is just a header and a message (or command) type. Useful when
+ * building command-specific structures.
+ */
+struct nvkm_msgqueue_msg {
+ struct nvkm_msgqueue_hdr hdr;
+ u8 msg_type;
+};
+
+struct nvkm_msgqueue;
+typedef void
+(*nvkm_msgqueue_callback)(struct nvkm_msgqueue *, struct nvkm_msgqueue_hdr *);
+
+/**
+ * struct nvkm_msgqueue_init_func - msgqueue functions related to initialization
+ *
+ * @gen_cmdline: build the commandline into a pre-allocated buffer
+ * @init_callback: called to process the init message
+ */
+struct nvkm_msgqueue_init_func {
+ void (*gen_cmdline)(struct nvkm_msgqueue *, void *);
+ int (*init_callback)(struct nvkm_msgqueue *, struct nvkm_msgqueue_hdr *);
+};
+
+/**
+ * struct nvkm_msgqueue_acr_func - msgqueue functions related to ACR
+ *
+ * @boot_falcon: build and send the command to reset a given falcon
+ * @boot_multiple_falcons: build and send the command to reset several falcons
+ */
+struct nvkm_msgqueue_acr_func {
+ int (*boot_falcon)(struct nvkm_msgqueue *, enum nvkm_secboot_falcon);
+ int (*boot_multiple_falcons)(struct nvkm_msgqueue *, unsigned long);
+};
+
+struct nvkm_msgqueue_func {
+ const struct nvkm_msgqueue_init_func *init_func;
+ const struct nvkm_msgqueue_acr_func *acr_func;
+ void (*dtor)(struct nvkm_msgqueue *);
+ struct nvkm_msgqueue_queue *(*cmd_queue)(struct nvkm_msgqueue *,
+ enum msgqueue_msg_priority);
+ void (*recv)(struct nvkm_msgqueue *queue);
+};
+
+/**
+ * struct nvkm_msgqueue_queue - information about a command or message queue
+ *
+ * The number of queues is firmware-dependent. All queues must have their
+ * information filled by the init message handler.
+ *
+ * @mutex_lock: to be acquired when the queue is being used
+ * @index: physical queue index
+ * @offset: DMEM offset where this queue begins
+ * @size: size allocated to this queue in DMEM (in bytes)
+ * @position: current write position
+ * @head_reg: address of the HEAD register for this queue
+ * @tail_reg: address of the TAIL register for this queue
+ */
+struct nvkm_msgqueue_queue {
+ struct mutex mutex;
+ u32 index;
+ u32 offset;
+ u32 size;
+ u32 position;
+
+ u32 head_reg;
+ u32 tail_reg;
+};
+
+/**
+ * struct nvkm_msgqueue_seq - keep track of ongoing commands
+ *
+ * Every time a command is sent, a sequence is assigned to it so the
+ * corresponding message can be matched. Upon receiving the message, a callback
+ * can be called and/or a completion signaled.
+ *
+ * @id: sequence ID
+ * @state: current state
+ * @callback: callback to call upon receiving matching message
+ * @completion: completion to signal after callback is called
+ */
+struct nvkm_msgqueue_seq {
+ u16 id;
+ enum {
+ SEQ_STATE_FREE = 0,
+ SEQ_STATE_PENDING,
+ SEQ_STATE_USED,
+ SEQ_STATE_CANCELLED
+ } state;
+ nvkm_msgqueue_callback callback;
+ struct completion *completion;
+};
+
+/*
+ * We can have an arbitrary number of sequences, but realistically we will
+ * probably not use that much simultaneously.
+ */
+#define NVKM_MSGQUEUE_NUM_SEQUENCES 16
+
+/**
+ * struct nvkm_msgqueue - manage a command/message based FW on a falcon
+ *
+ * @falcon: falcon to be managed
+ * @func: implementation of the firmware to use
+ * @init_msg_received: whether the init message has already been received
+ * @init_done: whether all init is complete and commands can be processed
+ * @seq_lock: protects seq and seq_tbl
+ * @seq: sequences to match commands and messages
+ * @seq_tbl: bitmap of sequences currently in use
+ */
+struct nvkm_msgqueue {
+ struct nvkm_falcon *falcon;
+ const struct nvkm_msgqueue_func *func;
+ u32 fw_version;
+ bool init_msg_received;
+ struct completion init_done;
+
+ struct mutex seq_lock;
+ struct nvkm_msgqueue_seq seq[NVKM_MSGQUEUE_NUM_SEQUENCES];
+ unsigned long seq_tbl[BITS_TO_LONGS(NVKM_MSGQUEUE_NUM_SEQUENCES)];
+};
+
+void nvkm_msgqueue_ctor(const struct nvkm_msgqueue_func *, struct nvkm_falcon *,
+ struct nvkm_msgqueue *);
+int nvkm_msgqueue_post(struct nvkm_msgqueue *, enum msgqueue_msg_priority,
+ struct nvkm_msgqueue_hdr *, nvkm_msgqueue_callback,
+ struct completion *, bool);
+void nvkm_msgqueue_process_msgs(struct nvkm_msgqueue *,
+ struct nvkm_msgqueue_queue *);
+
+int msgqueue_0137c63d_new(struct nvkm_falcon *, const struct nvkm_secboot *,
+ struct nvkm_msgqueue **);
+int msgqueue_0137bca5_new(struct nvkm_falcon *, const struct nvkm_secboot *,
+ struct nvkm_msgqueue **);
+int msgqueue_0148cdec_new(struct nvkm_falcon *, const struct nvkm_secboot *,
+ struct nvkm_msgqueue **);
+
+#endif
diff --git a/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0137c63d.c b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0137c63d.c
new file mode 100644
index 000000000000..fec0273158f6
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0137c63d.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+#include "msgqueue.h"
+#include <engine/falcon.h>
+#include <subdev/secboot.h>
+
+/* Queues identifiers */
+enum {
+ /* High Priority Command Queue for Host -> PMU communication */
+ MSGQUEUE_0137C63D_COMMAND_QUEUE_HPQ = 0,
+ /* Low Priority Command Queue for Host -> PMU communication */
+ MSGQUEUE_0137C63D_COMMAND_QUEUE_LPQ = 1,
+ /* Message queue for PMU -> Host communication */
+ MSGQUEUE_0137C63D_MESSAGE_QUEUE = 4,
+ MSGQUEUE_0137C63D_NUM_QUEUES = 5,
+};
+
+struct msgqueue_0137c63d {
+ struct nvkm_msgqueue base;
+
+ struct nvkm_msgqueue_queue queue[MSGQUEUE_0137C63D_NUM_QUEUES];
+};
+#define msgqueue_0137c63d(q) \
+ container_of(q, struct msgqueue_0137c63d, base)
+
+struct msgqueue_0137bca5 {
+ struct msgqueue_0137c63d base;
+
+ u64 wpr_addr;
+};
+#define msgqueue_0137bca5(q) \
+ container_of(container_of(q, struct msgqueue_0137c63d, base), \
+ struct msgqueue_0137bca5, base);
+
+static struct nvkm_msgqueue_queue *
+msgqueue_0137c63d_cmd_queue(struct nvkm_msgqueue *queue,
+ enum msgqueue_msg_priority priority)
+{
+ struct msgqueue_0137c63d *priv = msgqueue_0137c63d(queue);
+ const struct nvkm_subdev *subdev = priv->base.falcon->owner;
+
+ switch (priority) {
+ case MSGQUEUE_MSG_PRIORITY_HIGH:
+ return &priv->queue[MSGQUEUE_0137C63D_COMMAND_QUEUE_HPQ];
+ case MSGQUEUE_MSG_PRIORITY_LOW:
+ return &priv->queue[MSGQUEUE_0137C63D_COMMAND_QUEUE_LPQ];
+ default:
+ nvkm_error(subdev, "invalid command queue!\n");
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static void
+msgqueue_0137c63d_process_msgs(struct nvkm_msgqueue *queue)
+{
+ struct msgqueue_0137c63d *priv = msgqueue_0137c63d(queue);
+ struct nvkm_msgqueue_queue *q_queue =
+ &priv->queue[MSGQUEUE_0137C63D_MESSAGE_QUEUE];
+
+ nvkm_msgqueue_process_msgs(&priv->base, q_queue);
+}
+
+/* Init unit */
+#define MSGQUEUE_0137C63D_UNIT_INIT 0x07
+
+enum {
+ INIT_MSG_INIT = 0x0,
+};
+
+static void
+init_gen_cmdline(struct nvkm_msgqueue *queue, void *buf)
+{
+ struct {
+ u32 reserved;
+ u32 freq_hz;
+ u32 trace_size;
+ u32 trace_dma_base;
+ u16 trace_dma_base1;
+ u8 trace_dma_offset;
+ u32 trace_dma_idx;
+ bool secure_mode;
+ bool raise_priv_sec;
+ struct {
+ u32 dma_base;
+ u16 dma_base1;
+ u8 dma_offset;
+ u16 fb_size;
+ u8 dma_idx;
+ } gc6_ctx;
+ u8 pad;
+ } *args = buf;
+
+ args->secure_mode = 1;
+}
+
+/* forward declaration */
+static int acr_init_wpr(struct nvkm_msgqueue *queue);
+
+static int
+init_callback(struct nvkm_msgqueue *_queue, struct nvkm_msgqueue_hdr *hdr)
+{
+ struct msgqueue_0137c63d *priv = msgqueue_0137c63d(_queue);
+ struct {
+ struct nvkm_msgqueue_msg base;
+
+ u8 pad;
+ u16 os_debug_entry_point;
+
+ struct {
+ u16 size;
+ u16 offset;
+ u8 index;
+ u8 pad;
+ } queue_info[MSGQUEUE_0137C63D_NUM_QUEUES];
+
+ u16 sw_managed_area_offset;
+ u16 sw_managed_area_size;
+ } *init = (void *)hdr;
+ const struct nvkm_subdev *subdev = _queue->falcon->owner;
+ int i;
+
+ if (init->base.hdr.unit_id != MSGQUEUE_0137C63D_UNIT_INIT) {
+ nvkm_error(subdev, "expected message from init unit\n");
+ return -EINVAL;
+ }
+
+ if (init->base.msg_type != INIT_MSG_INIT) {
+ nvkm_error(subdev, "expected PMU init msg\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < MSGQUEUE_0137C63D_NUM_QUEUES; i++) {
+ struct nvkm_msgqueue_queue *queue = &priv->queue[i];
+
+ mutex_init(&queue->mutex);
+
+ queue->index = init->queue_info[i].index;
+ queue->offset = init->queue_info[i].offset;
+ queue->size = init->queue_info[i].size;
+
+ if (i != MSGQUEUE_0137C63D_MESSAGE_QUEUE) {
+ queue->head_reg = 0x4a0 + (queue->index * 4);
+ queue->tail_reg = 0x4b0 + (queue->index * 4);
+ } else {
+ queue->head_reg = 0x4c8;
+ queue->tail_reg = 0x4cc;
+ }
+
+ nvkm_debug(subdev,
+ "queue %d: index %d, offset 0x%08x, size 0x%08x\n",
+ i, queue->index, queue->offset, queue->size);
+ }
+
+ /* Complete initialization by initializing WPR region */
+ return acr_init_wpr(&priv->base);
+}
+
+static const struct nvkm_msgqueue_init_func
+msgqueue_0137c63d_init_func = {
+ .gen_cmdline = init_gen_cmdline,
+ .init_callback = init_callback,
+};
+
+
+
+/* ACR unit */
+#define MSGQUEUE_0137C63D_UNIT_ACR 0x0a
+
+enum {
+ ACR_CMD_INIT_WPR_REGION = 0x00,
+ ACR_CMD_BOOTSTRAP_FALCON = 0x01,
+ ACR_CMD_BOOTSTRAP_MULTIPLE_FALCONS = 0x03,
+};
+
+static void
+acr_init_wpr_callback(struct nvkm_msgqueue *queue,
+ struct nvkm_msgqueue_hdr *hdr)
+{
+ struct {
+ struct nvkm_msgqueue_msg base;
+ u32 error_code;
+ } *msg = (void *)hdr;
+ const struct nvkm_subdev *subdev = queue->falcon->owner;
+
+ if (msg->error_code) {
+ nvkm_error(subdev, "ACR WPR init failure: %d\n",
+ msg->error_code);
+ return;
+ }
+
+ nvkm_debug(subdev, "ACR WPR init complete\n");
+ complete_all(&queue->init_done);
+}
+
+static int
+acr_init_wpr(struct nvkm_msgqueue *queue)
+{
+ /*
+ * region_id: region ID in WPR region
+ * wpr_offset: offset in WPR region
+ */
+ struct {
+ struct nvkm_msgqueue_hdr hdr;
+ u8 cmd_type;
+ u32 region_id;
+ u32 wpr_offset;
+ } cmd;
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.hdr.unit_id = MSGQUEUE_0137C63D_UNIT_ACR;
+ cmd.hdr.size = sizeof(cmd);
+ cmd.cmd_type = ACR_CMD_INIT_WPR_REGION;
+ cmd.region_id = 0x01;
+ cmd.wpr_offset = 0x00;
+
+ nvkm_msgqueue_post(queue, MSGQUEUE_MSG_PRIORITY_HIGH, &cmd.hdr,
+ acr_init_wpr_callback, NULL, false);
+
+ return 0;
+}
+
+
+static void
+acr_boot_falcon_callback(struct nvkm_msgqueue *priv,
+ struct nvkm_msgqueue_hdr *hdr)
+{
+ struct acr_bootstrap_falcon_msg {
+ struct nvkm_msgqueue_msg base;
+
+ u32 falcon_id;
+ } *msg = (void *)hdr;
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ u32 falcon_id = msg->falcon_id;
+
+ if (falcon_id >= NVKM_SECBOOT_FALCON_END) {
+ nvkm_error(subdev, "in bootstrap falcon callback:\n");
+ nvkm_error(subdev, "invalid falcon ID 0x%x\n", falcon_id);
+ return;
+ }
+ nvkm_debug(subdev, "%s booted\n", nvkm_secboot_falcon_name[falcon_id]);
+}
+
+enum {
+ ACR_CMD_BOOTSTRAP_FALCON_FLAGS_RESET_YES = 0,
+ ACR_CMD_BOOTSTRAP_FALCON_FLAGS_RESET_NO = 1,
+};
+
+static int
+acr_boot_falcon(struct nvkm_msgqueue *priv, enum nvkm_secboot_falcon falcon)
+{
+ DECLARE_COMPLETION_ONSTACK(completed);
+ /*
+ * flags - Flag specifying RESET or no RESET.
+ * falcon id - Falcon id specifying falcon to bootstrap.
+ */
+ struct {
+ struct nvkm_msgqueue_hdr hdr;
+ u8 cmd_type;
+ u32 flags;
+ u32 falcon_id;
+ } cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.hdr.unit_id = MSGQUEUE_0137C63D_UNIT_ACR;
+ cmd.hdr.size = sizeof(cmd);
+ cmd.cmd_type = ACR_CMD_BOOTSTRAP_FALCON;
+ cmd.flags = ACR_CMD_BOOTSTRAP_FALCON_FLAGS_RESET_YES;
+ cmd.falcon_id = falcon;
+ nvkm_msgqueue_post(priv, MSGQUEUE_MSG_PRIORITY_HIGH, &cmd.hdr,
+ acr_boot_falcon_callback, &completed, true);
+
+ if (!wait_for_completion_timeout(&completed, msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static void
+acr_boot_multiple_falcons_callback(struct nvkm_msgqueue *priv,
+ struct nvkm_msgqueue_hdr *hdr)
+{
+ struct acr_bootstrap_falcon_msg {
+ struct nvkm_msgqueue_msg base;
+
+ u32 falcon_mask;
+ } *msg = (void *)hdr;
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ unsigned long falcon_mask = msg->falcon_mask;
+ u32 falcon_id, falcon_treated = 0;
+
+ for_each_set_bit(falcon_id, &falcon_mask, NVKM_SECBOOT_FALCON_END) {
+ nvkm_debug(subdev, "%s booted\n",
+ nvkm_secboot_falcon_name[falcon_id]);
+ falcon_treated |= BIT(falcon_id);
+ }
+
+ if (falcon_treated != msg->falcon_mask) {
+ nvkm_error(subdev, "in bootstrap falcon callback:\n");
+ nvkm_error(subdev, "invalid falcon mask 0x%x\n",
+ msg->falcon_mask);
+ return;
+ }
+}
+
+static int
+acr_boot_multiple_falcons(struct nvkm_msgqueue *priv, unsigned long falcon_mask)
+{
+ DECLARE_COMPLETION_ONSTACK(completed);
+ /*
+ * flags - Flag specifying RESET or no RESET.
+ * falcon id - Falcon id specifying falcon to bootstrap.
+ */
+ struct {
+ struct nvkm_msgqueue_hdr hdr;
+ u8 cmd_type;
+ u32 flags;
+ u32 falcon_mask;
+ u32 use_va_mask;
+ u32 wpr_lo;
+ u32 wpr_hi;
+ } cmd;
+ struct msgqueue_0137bca5 *queue = msgqueue_0137bca5(priv);
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.hdr.unit_id = MSGQUEUE_0137C63D_UNIT_ACR;
+ cmd.hdr.size = sizeof(cmd);
+ cmd.cmd_type = ACR_CMD_BOOTSTRAP_MULTIPLE_FALCONS;
+ cmd.flags = ACR_CMD_BOOTSTRAP_FALCON_FLAGS_RESET_YES;
+ cmd.falcon_mask = falcon_mask;
+ cmd.wpr_lo = lower_32_bits(queue->wpr_addr);
+ cmd.wpr_hi = upper_32_bits(queue->wpr_addr);
+ nvkm_msgqueue_post(priv, MSGQUEUE_MSG_PRIORITY_HIGH, &cmd.hdr,
+ acr_boot_multiple_falcons_callback, &completed, true);
+
+ if (!wait_for_completion_timeout(&completed, msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static const struct nvkm_msgqueue_acr_func
+msgqueue_0137c63d_acr_func = {
+ .boot_falcon = acr_boot_falcon,
+};
+
+static const struct nvkm_msgqueue_acr_func
+msgqueue_0137bca5_acr_func = {
+ .boot_falcon = acr_boot_falcon,
+ .boot_multiple_falcons = acr_boot_multiple_falcons,
+};
+
+static void
+msgqueue_0137c63d_dtor(struct nvkm_msgqueue *queue)
+{
+ kfree(msgqueue_0137c63d(queue));
+}
+
+static const struct nvkm_msgqueue_func
+msgqueue_0137c63d_func = {
+ .init_func = &msgqueue_0137c63d_init_func,
+ .acr_func = &msgqueue_0137c63d_acr_func,
+ .cmd_queue = msgqueue_0137c63d_cmd_queue,
+ .recv = msgqueue_0137c63d_process_msgs,
+ .dtor = msgqueue_0137c63d_dtor,
+};
+
+int
+msgqueue_0137c63d_new(struct nvkm_falcon *falcon, const struct nvkm_secboot *sb,
+ struct nvkm_msgqueue **queue)
+{
+ struct msgqueue_0137c63d *ret;
+
+ ret = kzalloc(sizeof(*ret), GFP_KERNEL);
+ if (!ret)
+ return -ENOMEM;
+
+ *queue = &ret->base;
+
+ nvkm_msgqueue_ctor(&msgqueue_0137c63d_func, falcon, &ret->base);
+
+ return 0;
+}
+
+static const struct nvkm_msgqueue_func
+msgqueue_0137bca5_func = {
+ .init_func = &msgqueue_0137c63d_init_func,
+ .acr_func = &msgqueue_0137bca5_acr_func,
+ .cmd_queue = msgqueue_0137c63d_cmd_queue,
+ .recv = msgqueue_0137c63d_process_msgs,
+ .dtor = msgqueue_0137c63d_dtor,
+};
+
+int
+msgqueue_0137bca5_new(struct nvkm_falcon *falcon, const struct nvkm_secboot *sb,
+ struct nvkm_msgqueue **queue)
+{
+ struct msgqueue_0137bca5 *ret;
+
+ ret = kzalloc(sizeof(*ret), GFP_KERNEL);
+ if (!ret)
+ return -ENOMEM;
+
+ *queue = &ret->base.base;
+
+ /*
+ * FIXME this must be set to the address of a *GPU* mapping within the
+ * ACR address space!
+ */
+ /* ret->wpr_addr = sb->wpr_addr; */
+
+ nvkm_msgqueue_ctor(&msgqueue_0137bca5_func, falcon, &ret->base.base);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0148cdec.c b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0148cdec.c
new file mode 100644
index 000000000000..9424803b9ef4
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue_0148cdec.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include "msgqueue.h"
+#include <engine/falcon.h>
+#include <subdev/secboot.h>
+
+/*
+ * This firmware runs on the SEC falcon. It only has one command and one
+ * message queue, and uses a different command line and init message.
+ */
+
+enum {
+ MSGQUEUE_0148CDEC_COMMAND_QUEUE = 0,
+ MSGQUEUE_0148CDEC_MESSAGE_QUEUE = 1,
+ MSGQUEUE_0148CDEC_NUM_QUEUES,
+};
+
+struct msgqueue_0148cdec {
+ struct nvkm_msgqueue base;
+
+ struct nvkm_msgqueue_queue queue[MSGQUEUE_0148CDEC_NUM_QUEUES];
+};
+#define msgqueue_0148cdec(q) \
+ container_of(q, struct msgqueue_0148cdec, base)
+
+static struct nvkm_msgqueue_queue *
+msgqueue_0148cdec_cmd_queue(struct nvkm_msgqueue *queue,
+ enum msgqueue_msg_priority priority)
+{
+ struct msgqueue_0148cdec *priv = msgqueue_0148cdec(queue);
+
+ return &priv->queue[MSGQUEUE_0148CDEC_COMMAND_QUEUE];
+}
+
+static void
+msgqueue_0148cdec_process_msgs(struct nvkm_msgqueue *queue)
+{
+ struct msgqueue_0148cdec *priv = msgqueue_0148cdec(queue);
+ struct nvkm_msgqueue_queue *q_queue =
+ &priv->queue[MSGQUEUE_0148CDEC_MESSAGE_QUEUE];
+
+ nvkm_msgqueue_process_msgs(&priv->base, q_queue);
+}
+
+
+/* Init unit */
+#define MSGQUEUE_0148CDEC_UNIT_INIT 0x01
+
+enum {
+ INIT_MSG_INIT = 0x0,
+};
+
+static void
+init_gen_cmdline(struct nvkm_msgqueue *queue, void *buf)
+{
+ struct {
+ u32 freq_hz;
+ u32 falc_trace_size;
+ u32 falc_trace_dma_base;
+ u32 falc_trace_dma_idx;
+ bool secure_mode;
+ } *args = buf;
+
+ args->secure_mode = false;
+}
+
+static int
+init_callback(struct nvkm_msgqueue *_queue, struct nvkm_msgqueue_hdr *hdr)
+{
+ struct msgqueue_0148cdec *priv = msgqueue_0148cdec(_queue);
+ struct {
+ struct nvkm_msgqueue_msg base;
+
+ u8 num_queues;
+ u16 os_debug_entry_point;
+
+ struct {
+ u32 offset;
+ u16 size;
+ u8 index;
+ u8 id;
+ } queue_info[MSGQUEUE_0148CDEC_NUM_QUEUES];
+
+ u16 sw_managed_area_offset;
+ u16 sw_managed_area_size;
+ } *init = (void *)hdr;
+ const struct nvkm_subdev *subdev = _queue->falcon->owner;
+ int i;
+
+ if (init->base.hdr.unit_id != MSGQUEUE_0148CDEC_UNIT_INIT) {
+ nvkm_error(subdev, "expected message from init unit\n");
+ return -EINVAL;
+ }
+
+ if (init->base.msg_type != INIT_MSG_INIT) {
+ nvkm_error(subdev, "expected SEC init msg\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < MSGQUEUE_0148CDEC_NUM_QUEUES; i++) {
+ u8 id = init->queue_info[i].id;
+ struct nvkm_msgqueue_queue *queue = &priv->queue[id];
+
+ mutex_init(&queue->mutex);
+
+ queue->index = init->queue_info[i].index;
+ queue->offset = init->queue_info[i].offset;
+ queue->size = init->queue_info[i].size;
+
+ if (id == MSGQUEUE_0148CDEC_MESSAGE_QUEUE) {
+ queue->head_reg = 0xa30 + (queue->index * 8);
+ queue->tail_reg = 0xa34 + (queue->index * 8);
+ } else {
+ queue->head_reg = 0xa00 + (queue->index * 8);
+ queue->tail_reg = 0xa04 + (queue->index * 8);
+ }
+
+ nvkm_debug(subdev,
+ "queue %d: index %d, offset 0x%08x, size 0x%08x\n",
+ id, queue->index, queue->offset, queue->size);
+ }
+
+ complete_all(&_queue->init_done);
+
+ return 0;
+}
+
+static const struct nvkm_msgqueue_init_func
+msgqueue_0148cdec_init_func = {
+ .gen_cmdline = init_gen_cmdline,
+ .init_callback = init_callback,
+};
+
+
+
+/* ACR unit */
+#define MSGQUEUE_0148CDEC_UNIT_ACR 0x08
+
+enum {
+ ACR_CMD_BOOTSTRAP_FALCON = 0x00,
+};
+
+static void
+acr_boot_falcon_callback(struct nvkm_msgqueue *priv,
+ struct nvkm_msgqueue_hdr *hdr)
+{
+ struct acr_bootstrap_falcon_msg {
+ struct nvkm_msgqueue_msg base;
+
+ u32 error_code;
+ u32 falcon_id;
+ } *msg = (void *)hdr;
+ const struct nvkm_subdev *subdev = priv->falcon->owner;
+ u32 falcon_id = msg->falcon_id;
+
+ if (msg->error_code) {
+ nvkm_error(subdev, "in bootstrap falcon callback:\n");
+ nvkm_error(subdev, "expected error code 0x%x\n",
+ msg->error_code);
+ return;
+ }
+
+ if (falcon_id >= NVKM_SECBOOT_FALCON_END) {
+ nvkm_error(subdev, "in bootstrap falcon callback:\n");
+ nvkm_error(subdev, "invalid falcon ID 0x%x\n", falcon_id);
+ return;
+ }
+
+ nvkm_debug(subdev, "%s booted\n", nvkm_secboot_falcon_name[falcon_id]);
+}
+
+enum {
+ ACR_CMD_BOOTSTRAP_FALCON_FLAGS_RESET_YES = 0,
+ ACR_CMD_BOOTSTRAP_FALCON_FLAGS_RESET_NO = 1,
+};
+
+static int
+acr_boot_falcon(struct nvkm_msgqueue *priv, enum nvkm_secboot_falcon falcon)
+{
+ DECLARE_COMPLETION_ONSTACK(completed);
+ /*
+ * flags - Flag specifying RESET or no RESET.
+ * falcon id - Falcon id specifying falcon to bootstrap.
+ */
+ struct {
+ struct nvkm_msgqueue_hdr hdr;
+ u8 cmd_type;
+ u32 flags;
+ u32 falcon_id;
+ } cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ cmd.hdr.unit_id = MSGQUEUE_0148CDEC_UNIT_ACR;
+ cmd.hdr.size = sizeof(cmd);
+ cmd.cmd_type = ACR_CMD_BOOTSTRAP_FALCON;
+ cmd.flags = ACR_CMD_BOOTSTRAP_FALCON_FLAGS_RESET_YES;
+ cmd.falcon_id = falcon;
+ nvkm_msgqueue_post(priv, MSGQUEUE_MSG_PRIORITY_HIGH, &cmd.hdr,
+ acr_boot_falcon_callback, &completed, true);
+
+ if (!wait_for_completion_timeout(&completed, msecs_to_jiffies(1000)))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+const struct nvkm_msgqueue_acr_func
+msgqueue_0148cdec_acr_func = {
+ .boot_falcon = acr_boot_falcon,
+};
+
+static void
+msgqueue_0148cdec_dtor(struct nvkm_msgqueue *queue)
+{
+ kfree(msgqueue_0148cdec(queue));
+}
+
+const struct nvkm_msgqueue_func
+msgqueue_0148cdec_func = {
+ .init_func = &msgqueue_0148cdec_init_func,
+ .acr_func = &msgqueue_0148cdec_acr_func,
+ .cmd_queue = msgqueue_0148cdec_cmd_queue,
+ .recv = msgqueue_0148cdec_process_msgs,
+ .dtor = msgqueue_0148cdec_dtor,
+};
+
+int
+msgqueue_0148cdec_new(struct nvkm_falcon *falcon, const struct nvkm_secboot *sb,
+ struct nvkm_msgqueue **queue)
+{
+ struct msgqueue_0148cdec *ret;
+
+ ret = kzalloc(sizeof(*ret), GFP_KERNEL);
+ if (!ret)
+ return -ENOMEM;
+
+ *queue = &ret->base;
+
+ nvkm_msgqueue_ctor(&msgqueue_0148cdec_func, falcon, &ret->base);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/nouveau/nvkm/falcon/v1.c b/drivers/gpu/drm/nouveau/nvkm/falcon/v1.c
index b537f111f39c..669c24028470 100644
--- a/drivers/gpu/drm/nouveau/nvkm/falcon/v1.c
+++ b/drivers/gpu/drm/nouveau/nvkm/falcon/v1.c
@@ -40,8 +40,8 @@ nvkm_falcon_v1_load_imem(struct nvkm_falcon *falcon, void *data, u32 start,
for (i = 0; i < size / 4; i++) {
/* write new tag every 256B */
if ((i & 0x3f) == 0)
- nvkm_falcon_wr32(falcon, 0x188, tag++);
- nvkm_falcon_wr32(falcon, 0x184, ((u32 *)data)[i]);
+ nvkm_falcon_wr32(falcon, 0x188 + (port * 16), tag++);
+ nvkm_falcon_wr32(falcon, 0x184 + (port * 16), ((u32 *)data)[i]);
}
/*
@@ -53,37 +53,98 @@ nvkm_falcon_v1_load_imem(struct nvkm_falcon *falcon, void *data, u32 start,
/* write new tag every 256B */
if ((i & 0x3f) == 0)
- nvkm_falcon_wr32(falcon, 0x188, tag++);
- nvkm_falcon_wr32(falcon, 0x184, extra & (BIT(rem * 8) - 1));
+ nvkm_falcon_wr32(falcon, 0x188 + (port * 16), tag++);
+ nvkm_falcon_wr32(falcon, 0x184 + (port * 16),
+ extra & (BIT(rem * 8) - 1));
++i;
}
/* code must be padded to 0x40 words */
for (; i & 0x3f; i++)
- nvkm_falcon_wr32(falcon, 0x184, 0);
+ nvkm_falcon_wr32(falcon, 0x184 + (port * 16), 0);
}
static void
+nvkm_falcon_v1_load_emem(struct nvkm_falcon *falcon, void *data, u32 start,
+ u32 size, u8 port)
+{
+ u8 rem = size % 4;
+ int i;
+
+ size -= rem;
+
+ nvkm_falcon_wr32(falcon, 0xac0 + (port * 8), start | (0x1 << 24));
+ for (i = 0; i < size / 4; i++)
+ nvkm_falcon_wr32(falcon, 0xac4 + (port * 8), ((u32 *)data)[i]);
+
+ /*
+ * If size is not a multiple of 4, mask the last word to ensure garbage
+ * does not get written
+ */
+ if (rem) {
+ u32 extra = ((u32 *)data)[i];
+
+ nvkm_falcon_wr32(falcon, 0xac4 + (port * 8),
+ extra & (BIT(rem * 8) - 1));
+ }
+}
+
+static const u32 EMEM_START_ADDR = 0x1000000;
+
+static void
nvkm_falcon_v1_load_dmem(struct nvkm_falcon *falcon, void *data, u32 start,
u32 size, u8 port)
{
u8 rem = size % 4;
int i;
+ if (start >= EMEM_START_ADDR && falcon->has_emem)
+ return nvkm_falcon_v1_load_emem(falcon, data,
+ start - EMEM_START_ADDR, size,
+ port);
+
size -= rem;
- nvkm_falcon_wr32(falcon, 0x1c0 + (port * 16), start | (0x1 << 24));
+ nvkm_falcon_wr32(falcon, 0x1c0 + (port * 8), start | (0x1 << 24));
for (i = 0; i < size / 4; i++)
- nvkm_falcon_wr32(falcon, 0x1c4, ((u32 *)data)[i]);
+ nvkm_falcon_wr32(falcon, 0x1c4 + (port * 8), ((u32 *)data)[i]);
/*
- * If size is not a multiple of 4, mask the last work to ensure garbage
- * does not get read
+ * If size is not a multiple of 4, mask the last word to ensure garbage
+ * does not get written
*/
if (rem) {
u32 extra = ((u32 *)data)[i];
- nvkm_falcon_wr32(falcon, 0x1c4, extra & (BIT(rem * 8) - 1));
+ nvkm_falcon_wr32(falcon, 0x1c4 + (port * 8),
+ extra & (BIT(rem * 8) - 1));
+ }
+}
+
+static void
+nvkm_falcon_v1_read_emem(struct nvkm_falcon *falcon, u32 start, u32 size,
+ u8 port, void *data)
+{
+ u8 rem = size % 4;
+ int i;
+
+ size -= rem;
+
+ nvkm_falcon_wr32(falcon, 0xac0 + (port * 8), start | (0x1 << 25));
+ for (i = 0; i < size / 4; i++)
+ ((u32 *)data)[i] = nvkm_falcon_rd32(falcon, 0xac4 + (port * 8));
+
+ /*
+ * If size is not a multiple of 4, mask the last word to ensure garbage
+ * does not get read
+ */
+ if (rem) {
+ u32 extra = nvkm_falcon_rd32(falcon, 0xac4 + (port * 8));
+
+ for (i = size; i < size + rem; i++) {
+ ((u8 *)data)[i] = (u8)(extra & 0xff);
+ extra >>= 8;
+ }
}
}
@@ -94,18 +155,22 @@ nvkm_falcon_v1_read_dmem(struct nvkm_falcon *falcon, u32 start, u32 size,
u8 rem = size % 4;
int i;
+ if (start >= EMEM_START_ADDR && falcon->has_emem)
+ return nvkm_falcon_v1_read_emem(falcon, start - EMEM_START_ADDR,
+ size, port, data);
+
size -= rem;
- nvkm_falcon_wr32(falcon, 0x1c0 + (port * 16), start | (0x1 << 25));
+ nvkm_falcon_wr32(falcon, 0x1c0 + (port * 8), start | (0x1 << 25));
for (i = 0; i < size / 4; i++)
- ((u32 *)data)[i] = nvkm_falcon_rd32(falcon, 0x1c4);
+ ((u32 *)data)[i] = nvkm_falcon_rd32(falcon, 0x1c4 + (port * 8));
/*
- * If size is not a multiple of 4, mask the last work to ensure garbage
+ * If size is not a multiple of 4, mask the last word to ensure garbage
* does not get read
*/
if (rem) {
- u32 extra = nvkm_falcon_rd32(falcon, 0x1c4);
+ u32 extra = nvkm_falcon_rd32(falcon, 0x1c4 + (port * 8));
for (i = size; i < size + rem; i++) {
((u8 *)data)[i] = (u8)(extra & 0xff);
@@ -118,6 +183,7 @@ static void
nvkm_falcon_v1_bind_context(struct nvkm_falcon *falcon, struct nvkm_gpuobj *ctx)
{
u32 inst_loc;
+ u32 fbif;
/* disable instance block binding */
if (ctx == NULL) {
@@ -125,19 +191,34 @@ nvkm_falcon_v1_bind_context(struct nvkm_falcon *falcon, struct nvkm_gpuobj *ctx)
return;
}
+ switch (falcon->owner->index) {
+ case NVKM_ENGINE_NVENC0:
+ case NVKM_ENGINE_NVENC1:
+ case NVKM_ENGINE_NVENC2:
+ fbif = 0x800;
+ break;
+ case NVKM_SUBDEV_PMU:
+ fbif = 0xe00;
+ break;
+ default:
+ fbif = 0x600;
+ break;
+ }
+
nvkm_falcon_wr32(falcon, 0x10c, 0x1);
/* setup apertures - virtual */
- nvkm_falcon_wr32(falcon, 0xe00 + 4 * FALCON_DMAIDX_UCODE, 0x4);
- nvkm_falcon_wr32(falcon, 0xe00 + 4 * FALCON_DMAIDX_VIRT, 0x0);
+ nvkm_falcon_wr32(falcon, fbif + 4 * FALCON_DMAIDX_UCODE, 0x4);
+ nvkm_falcon_wr32(falcon, fbif + 4 * FALCON_DMAIDX_VIRT, 0x0);
/* setup apertures - physical */
- nvkm_falcon_wr32(falcon, 0xe00 + 4 * FALCON_DMAIDX_PHYS_VID, 0x4);
- nvkm_falcon_wr32(falcon, 0xe00 + 4 * FALCON_DMAIDX_PHYS_SYS_COH, 0x5);
- nvkm_falcon_wr32(falcon, 0xe00 + 4 * FALCON_DMAIDX_PHYS_SYS_NCOH, 0x6);
+ nvkm_falcon_wr32(falcon, fbif + 4 * FALCON_DMAIDX_PHYS_VID, 0x4);
+ nvkm_falcon_wr32(falcon, fbif + 4 * FALCON_DMAIDX_PHYS_SYS_COH, 0x5);
+ nvkm_falcon_wr32(falcon, fbif + 4 * FALCON_DMAIDX_PHYS_SYS_NCOH, 0x6);
/* Set context */
switch (nvkm_memory_target(ctx->memory)) {
case NVKM_MEM_TARGET_VRAM: inst_loc = 0; break;
+ case NVKM_MEM_TARGET_HOST: inst_loc = 2; break;
case NVKM_MEM_TARGET_NCOH: inst_loc = 3; break;
default:
WARN_ON(1);
@@ -146,9 +227,12 @@ nvkm_falcon_v1_bind_context(struct nvkm_falcon *falcon, struct nvkm_gpuobj *ctx)
/* Enable context */
nvkm_falcon_mask(falcon, 0x048, 0x1, 0x1);
- nvkm_falcon_wr32(falcon, 0x480,
+ nvkm_falcon_wr32(falcon, 0x054,
((ctx->addr >> 12) & 0xfffffff) |
(inst_loc << 28) | (1 << 30));
+
+ nvkm_falcon_mask(falcon, 0x090, 0x10000, 0x10000);
+ nvkm_falcon_mask(falcon, 0x0a4, 0x8, 0x8);
}
static void