diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-03 11:44:24 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-05-03 11:44:24 -0700 |
commit | 2f34c1231bfc9f2550f934acb268ac7315fb3837 (patch) | |
tree | ff8114b3b4ec4723a11b041c6b74c389e9f0eeb9 /drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.c | |
parent | a3719f34fdb664ffcfaec2160ef20fca7becf2ee (diff) | |
parent | 8b03d1ed2c43a2ba5ef3381322ee4515b97381bf (diff) |
Merge tag 'drm-for-v4.12' of git://people.freedesktop.org/~airlied/linux
Pull drm u pdates from Dave Airlie:
"This is the main drm pull request for v4.12. Apart from two fixes
pulls, everything should have been in drm-next for at least 2 weeks.
The biggest thing in here is AMD released the public headers for their
upcoming VEGA GPUs. These as always are quite a sizeable chunk of
header files. They've also added initial non-display support for those
GPUs, though they aren't available in production yet.
Otherwise it's pretty much normal.
New bridge drivers:
- megachips-stdpxxxx-ge-b850v3-fw LVDS->DP++
- generic LVDS bridge support.
Core:
- Displayport link train failure reporting to userspace
- debugfs interface cleaned up
- subsystem TODO in kerneldoc now
- Extended fbdev support (flipping and vblank wait)
- drm_platform removed
- EDP CRC support in helper
- HF-VSDB SCDC support in EDID parser
- Lots of code cleanups and header extraction
- Thunderbolt external GPU awareness
- Atomic helper improvements
- Documentation improvements
panel:
- Sitronix and Samsung new panel support
amdgpu:
- Preliminary vega10 support
- Multi-level page table support
- GPU sensor support for userspace
- PRT support for sparse buffers
- SR-IOV improvements
- Non-contig VRAM CPU mapping
i915:
- Atomic modesetting enabled by default on Gen5+
- LSPCON improvements
- Atomic state handling for cdclk
- GPU reset improvements
- In-kernel unit tests
- Geminilake improvements and color manager support
- Designware i2c fixes
- vblank evasion improvements
- Hotplug safe connector iterators
- GVT scheduler QoS support
- GVT Kabylake support
nouveau:
- Acceleration support for Pascal (GP10x).
- Rearchitecture of code handling proprietary signed firmware
- Fix GTX 970 with odd MMU configuration
- GP10B support
- GP107 acceleration support
vmwgfx:
- Atomic modesetting support for vmwgfx
omapdrm:
- Support for render nodes
- Refactor omapdss code
- Fix some probe ordering issues
- Fix too dark RGB565 rendering
sunxi:
- prelim rework for multiple pipes.
mali-dp:
- Color management support
- Plane scaling
- Power management improvements
imx-drm:
- Prefetch Resolve Engine/Gasket on i.MX6QP
- Deferred plane disabling
- Separate alpha support
mediatek:
- Mediatek SoC MT2701 support
rcar-du:
- Gen3 HDMI support
msm:
- 4k support for newer chips
- OPP bindings for gpu
- prep work for per-process pagetables
vc4:
- HDMI audio support
- fixes
qxl:
- minor fixes.
dw-hdmi:
- PHY improvements
- CSC fixes
- Amlogic GX SoC support"
* tag 'drm-for-v4.12' of git://people.freedesktop.org/~airlied/linux: (1778 commits)
drm/nouveau/fb/gf100-: Fix 32 bit wraparound in new ram detection
drm/nouveau/secboot/gm20b: fix the error return code in gm20b_secboot_tegra_read_wpr()
drm/nouveau/kms: Increase max retries in scanout position queries.
drm/nouveau/bios/bitP: check that table is long enough for optional pointers
drm/nouveau/fifo/nv40: no ctxsw for pre-nv44 mpeg engine
drm: mali-dp: use div_u64 for expensive 64-bit divisions
drm/i915: Confirm the request is still active before adding it to the await
drm/i915: Avoid busy-spinning on VLV_GLTC_PW_STATUS mmio
drm/i915/selftests: Allocate inode/file dynamically
drm/i915: Fix system hang with EI UP masked on Haswell
drm/i915: checking for NULL instead of IS_ERR() in mock selftests
drm/i915: Perform link quality check unconditionally during long pulse
drm/i915: Fix use after free in lpe_audio_platdev_destroy()
drm/i915: Use the right mapping_gfp_mask for final shmem allocation
drm/i915: Make legacy cursor updates more unsynced
drm/i915: Apply a cond_resched() to the saturated signaler
drm/i915: Park the signaler before sleeping
drm: mali-dp: Check the mclk rate and allow up/down scaling
drm: mali-dp: Enable image enhancement when scaling
drm: mali-dp: Add plane upscaling support
...
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nvkm/falcon/msgqueue.c | 575 |
1 files changed, 575 insertions, 0 deletions
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); + + +} |