/*
* GK20A Cycle stats snapshots support (subsystem for gr_gk20a).
*
* Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/bitops.h>
#include <linux/dma-mapping.h>
#include <linux/dma-buf.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
#include "gk20a.h"
#include "hw_perf_gk20a.h"
#include "hw_mc_gk20a.h"
/* cycle stats fifo header (must match NvSnapshotBufferFifo) */
struct gk20a_cs_snapshot_fifo {
/* layout description of the buffer */
u32 start;
u32 end;
/* snafu bits */
u32 hw_overflow_events_occured;
u32 sw_overflow_events_occured;
/* the kernel copies new entries to put and
* increment the put++. if put == get then
* overflowEventsOccured++
*/
u32 put;
u32 _reserved10;
u32 _reserved11;
u32 _reserved12;
/* the driver/client reads from get until
* put==get, get++ */
u32 get;
u32 _reserved20;
u32 _reserved21;
u32 _reserved22;
/* unused */
u32 _reserved30;
u32 _reserved31;
u32 _reserved32;
u32 _reserved33;
};
/* cycle stats fifo entry (must match NvSnapshotBufferFifoEntry) */
struct gk20a_cs_snapshot_fifo_entry {
/* global 48 timestamp */
u32 timestamp31_00:32;
u32 timestamp39_32:8;
/* id of perfmon, should correlate with CSS_MAX_PERFMON_IDS */
u32 perfmon_id:8;
/* typically samples_counter is wired to #pmtrigger count */
u32 samples_counter:12;
/* DS=Delay Sample, SZ=Size (0=32B, 1=16B) */
u32 ds:1;
u32 sz:1;
u32 zero0:1;
u32 zero1:1;
/* counter results */
u32 event_cnt:32;
u32 trigger0_cnt:32;
u32 trigger1_cnt:32;
u32 sample_cnt:32;
/* Local PmTrigger results for Maxwell+ or padding otherwise */
u16 local_trigger_b_count:16;
u16 book_mark_b:16;
u16 local_trigger_a_count:16;
u16 book_mark_a:16;
};
/* cycle stats snapshot client data (e.g. associated with channel) */
struct gk20a_cs_snapshot_client {
struct list_head list;
u32 dmabuf_fd;
struct dma_buf *dma_handler;
struct gk20a_cs_snapshot_fifo *snapshot;
u32 snapshot_size;
u32 perfmon_start;
u32 perfmon_count;
};
/* check client for pointed perfmon ownership */
#define CONTAINS_PERFMON(cl, pm) \
((cl)->perfmon_start <= (pm) && \
((pm) - (cl)->perfmon_start) < (cl)->perfmon_count)
/* the minimal size of HW buffer - should be enough to avoid HW overflows */
#define CSS_MIN_HW_SNAPSHOT_SIZE (8 * 1024 * 1024)
/* the minimal size of client buffer */
#define CSS_MIN_CLIENT_SNAPSHOT_SIZE \
(sizeof(struct gk20a_cs_snapshot_fifo) + \
sizeof(struct gk20a_cs_snapshot_fifo_entry) * 256)
/* address of fifo entry by offset */
#define CSS_FIFO_ENTRY(fifo, offs) \
((struct gk20a_cs_snapshot_fifo_entry *)(((char *)(fifo)) + (offs)))
/* calculate area capacity in number of fifo entries */
#define CSS_FIFO_ENTRY_CAPACITY(s) \
(((s) - sizeof(struct gk20a_cs_snapshot_fifo)) \
/ sizeof(struct gk20a_cs_snapshot_fifo_entry))
/* reserved to indicate failures with data */
#define CSS_FIRST_PERFMON_ID 32
/* should correlate with size of gk20a_cs_snapshot_fifo_entry::perfmon_id */
#define CSS_MAX_PERFMON_IDS 256
/* this type is used for storing bits in perfmon mask */
typedef u32 css_perfmon_t;
/* local definitions to avoid hardcodes sizes and shifts */
#define PM_BITS (sizeof(css_perfmon_t) * BITS_PER_BYTE)
#define PM_BITS_MASK (PM_BITS - 1)
#define PM_BITMAP_SIZE ((CSS_MAX_PERFMON_IDS + PM_BITS - 1) / PM_BITS)
#define PM_SLOT(i) ((i) / PM_BITS)
#define PM_SHIFT(i) ((i) & PM_BITS_MASK)
#define PM_BIT(i) (1u << PM_SHIFT(i))
#define CSS_PERFMON_GET(p, i) (1 == ((p[PM_SLOT(i)] >> PM_SHIFT(i)) & 1))
#define CSS_PERFMON_USE(p, i) (p[PM_SLOT(i)] |= PM_BIT(i))
#define CSS_PERFMON_REL(p, i) (p[PM_SLOT(i)] &= ~PM_BIT(i))
/* cycle stats snapshot control structure for one HW entry and many clients */
struct gk20a_cs_snapshot {
css_perfmon_t perfmon_ids[PM_BITMAP_SIZE];
struct list_head clients;
struct mem_desc hw_memdesc;
/* pointer to allocated cpu_va memory where GPU place data */
struct gk20a_cs_snapshot_fifo_entry *hw_snapshot;
struct gk20a_cs_snapshot_fifo_entry *hw_end;
struct gk20a_cs_snapshot_fifo_entry *hw_get;
};
/* reports whether the hw queue overflowed */
static inline bool css_hw_get_overflow_status(struct gk20a *g)
{
const u32 st = perf_pmasys_control_membuf_status_overflowed_f();
return st == (gk20a_readl(g, perf_pmasys_control_r()) & st);
}
/* returns how many pending snapshot entries are pending */
static inline u32 css_hw_get_pending_snapshots(struct gk20a *g)
{
return gk20a_readl(g, perf_pmasys_mem_bytes_r()) /
sizeof(struct gk20a_cs_snapshot_fifo_entry);
}
/* informs hw how many snapshots have been processed (frees up fifo space) */
static inline void css_hw_set_handled_snapshots(struct gk20a *g, u32 done)
{
if (done > 0) {
gk20a_writel(g, perf_pmasys_mem_bump_r(),
done * sizeof(struct gk20a_cs_snapshot_fifo_entry));
}
}
/* disable streaming to memory */
static void css_hw_reset_streaming(struct gk20a *g)
{
u32 engine_status;
u32 old_pmc = gk20a_readl(g, mc_enable_r());
/* reset the perfmon */
gk20a_writel(g, mc_enable_r(),
old_pmc & ~mc_enable_perfmon_enabled_f());
gk20a_writel(g, mc_enable_r(), old_pmc);
/* RBUFEMPTY must be set -- otherwise we'll pick up */
/* snapshot that have been queued up from earlier */
engine_status = gk20a_readl(g, perf_pmasys_enginestatus_r());
WARN_ON(0 == (engine_status
& perf_pmasys_enginestatus_rbufempty_empty_f()));
/* turn off writes */
gk20a_writel(g, perf_pmasys_control_r(),
perf_pmasys_control_membuf_clear_status_doit_f());
/* pointing all pending snapshots as handled */
css_hw_set_handled_snapshots(g, css_hw_get_pending_snapshots(g));
}
/*
* WARNING: all css_gr_XXX functions are local and expected to be called
* from locked context (protected by cs_lock)
*/
static int css_gr_create_shared_data(struct gr_gk20a *gr)
{
struct gk20a_cs_snapshot *data;
if (gr->cs_data)
return 0;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
INIT_LIST_HEAD(&data->clients);
gr->cs_data = data;
return 0;
}
static int css_hw_enable_snapshot(struct gr_gk20a *gr, u32 snapshot_size)
{
struct gk20a *g = gr->g;
struct gk20a_cs_snapshot *data = gr->cs_data;
int ret;
u32 virt_addr_lo;
u32 virt_addr_hi;
u32 inst_pa_page;
if (data->hw_snapshot)
return 0;
if (snapshot_size < CSS_MIN_HW_SNAPSHOT_SIZE)
snapshot_size = CSS_MIN_HW_SNAPSHOT_SIZE;
ret = gk20a_gmmu_alloc_map(&g->mm.pmu.vm, snapshot_size,
&data->hw_memdesc);
if (ret)
return ret;
/* perf output buffer may not cross a 4GB boundary - with a separate */
/* va smaller than that, it won't but check anyway */
if (!data->hw_memdesc.cpu_va ||
data->hw_memdesc.size < snapshot_size ||
data->hw_memdesc.gpu_va + u64_lo32(snapshot_size) > SZ_4G) {
ret = -EFAULT;
goto failed_allocation;
}
data->hw_snapshot =
(struct gk20a_cs_snapshot_fifo_entry *)data->hw_memdesc.cpu_va;
data->hw_end = data->hw_snapshot +
snapshot_size / sizeof(struct gk20a_cs_snapshot_fifo_entry);
data->hw_get = data->hw_snapshot;
memset(data->hw_snapshot, 0xff, snapshot_size);
/* address and size are aligned to 32 bytes, the lowest bits read back
* as zeros */
virt_addr_lo = u64_lo32(data->hw_memdesc.gpu_va);
virt_addr_hi = u64_hi32(data->hw_memdesc.gpu_va);
css_hw_reset_streaming(g);
gk20a_writel(g, perf_pmasys_outbase_r(), virt_addr_lo);
gk20a_writel(g, perf_pmasys_outbaseupper_r(),
perf_pmasys_outbaseupper_ptr_f(virt_addr_hi));
gk20a_writel(g, perf_pmasys_outsize_r(), snapshot_size);
/* this field is aligned to 4K */
inst_pa_page = gk20a_mm_inst_block_addr(g, &g->mm.hwpm.inst_block) >> 12;
/* A write to MEM_BLOCK triggers the block bind operation. MEM_BLOCK
* should be written last */
gk20a_writel(g, perf_pmasys_mem_block_r(),
perf_pmasys_mem_block_base_f(inst_pa_page) |
perf_pmasys_mem_block_valid_true_f() |
perf_pmasys_mem_block_target_lfb_f());
gk20a_dbg_info("cyclestats: buffer for hardware snapshots enabled\n");
return 0;
failed_allocation:
if (data->hw_memdesc.size) {
gk20a_gmmu_unmap_free(&g->mm.pmu.vm, &data->hw_memdesc);
memset(&data->hw_memdesc, 0, sizeof(data->hw_memdesc));
}
data->hw_snapshot = NULL;
return ret;
}
static void css_hw_disable_snapshot(struct gr_gk20a *gr)
{
struct gk20a *g = gr->g;
struct gk20a_cs_snapshot *data = gr->cs_data;
if (!data->hw_snapshot)
return;
css_hw_reset_streaming(g);
gk20a_writel(g, perf_pmasys_outbase_r(), 0);
gk20a_writel(g, perf_pmasys_outbaseupper_r(),
perf_pmasys_outbaseupper_ptr_f(0));
gk20a_writel(g, perf_pmasys_outsize_r(), 0);
gk20a_writel(g, perf_pmasys_mem_block_r(),
perf_pmasys_mem_block_base_f(0) |
perf_pmasys_mem_block_valid_false_f() |
perf_pmasys_mem_block_target_f(0));
gk20a_gmmu_unmap_free(&g->mm.pmu.vm, &data->hw_memdesc);
memset(&data->hw_memdesc, 0, sizeof(data->hw_memdesc));
data->hw_snapshot = NULL;
gk20a_dbg_info("cyclestats: buffer for hardware snapshots disabled\n");
}
static void css_gr_free_shared_data(struct gr_gk20a *gr)
{
if (gr->cs_data) {
/* the clients list is expected to be empty */
css_hw_disable_snapshot(gr);
/* release the objects */
kfree(gr->cs_data);
gr->cs_data = NULL;
}
}
static struct gk20a_cs_snapshot_client*
css_gr_search_client(struct list_head *clients, u32 perfmon)
{
struct list_head *pos;
list_for_each(pos, clients) {
struct gk20a_cs_snapshot_client *client =
container_of(pos,
struct gk20a_cs_snapshot_client, list);
if (CONTAINS_PERFMON(client, perfmon))
return client;
}
return NULL;
}
static int css_gr_flush_snapshots(struct gr_gk20a *gr)
{
struct gk20a *g = gr->g;
struct gk20a_cs_snapshot *css = gr->cs_data;
struct gk20a_cs_snapshot_client *cur;
u32 pending;
/* variables for iterating over HW entries */
u32 sid;
struct gk20a_cs_snapshot_fifo_entry *src;
/* due to data sharing with userspace we allowed update only */
/* overflows and put field in the fifo header */
struct gk20a_cs_snapshot_fifo *dst;
struct gk20a_cs_snapshot_fifo_entry *dst_get;
struct gk20a_cs_snapshot_fifo_entry *dst_put;
struct gk20a_cs_snapshot_fifo_entry *dst_head;
struct gk20a_cs_snapshot_fifo_entry *dst_tail;
if (!css)
return -EINVAL;
if (!css->hw_snapshot)
return -EINVAL;
if (list_empty(&css->clients))
return -EBADF;
/* check data available */
pending = css_hw_get_pending_snapshots(g);
if (!pending)
return 0;
if (css_hw_get_overflow_status(g)) {
struct list_head *pos;
list_for_each(pos, &css->clients) {
cur = container_of(pos,
struct gk20a_cs_snapshot_client, list);
cur->snapshot->hw_overflow_events_occured++;
}
gk20a_warn(dev_from_gk20a(g),
"cyclestats: hardware overflow detected\n");
}
/* proceed all items in HW buffer */
sid = 0;
cur = NULL;
dst = NULL;
dst_put = NULL;
src = css->hw_get;
/* proceed all completed records */
while (sid < pending && 0 == src->zero0) {
/* we may have a new perfmon_id which required to */
/* switch to a new client -> let's forget current */
if (cur && !CONTAINS_PERFMON(cur, src->perfmon_id)) {
dst->put = (char *)dst_put - (char *)dst;
dst = NULL;
cur = NULL;
}
/* now we have to select a new current client */
/* the client selection rate depends from experiment */
/* activity but on Android usually happened 1-2 times */
if (!cur) {
cur = css_gr_search_client(&css->clients,
src->perfmon_id);
if (cur) {
/* found - setup all required data */
dst = cur->snapshot;
dst_get = CSS_FIFO_ENTRY(dst, dst->get);
dst_put = CSS_FIFO_ENTRY(dst, dst->put);
dst_head = CSS_FIFO_ENTRY(dst, dst->start);
dst_tail = CSS_FIFO_ENTRY(dst, dst->end) - 1;
} else {
/* client not found - skipping this entry */
gk20a_warn(dev_from_gk20a(g),
"cyclestats: orphaned perfmon %u\n",
src->perfmon_id);
goto next_hw_fifo_entry;
}
}
/* check for software overflows */
if (dst_put + 1 == dst_get ||
(dst_put == dst_tail && dst_get == dst_head)) {
/* no data copy, no pointer updates */
dst->sw_overflow_events_occured++;
gk20a_warn(dev_from_gk20a(g),
"cyclestats: perfmon %u soft overflow\n",
src->perfmon_id);
} else {
*dst_put = *src;
if (dst_put == dst_tail)
dst_put = dst_head;
else
dst_put++;
}
next_hw_fifo_entry:
sid++;
if (++src >= css->hw_end)
src = css->hw_snapshot;
}
/* update client put pointer if necessary */
if (cur && dst)
dst->put = (char *)dst_put - (char *)dst;
/* re-set HW buffer after processing taking wrapping into account */
if (css->hw_get < src) {
memset(css->hw_get, 0xff, (src - css->hw_get) * sizeof(*src));
} else {
memset(css->hw_snapshot, 0xff,
(src - css->hw_snapshot) * sizeof(*src));
memset(css->hw_get, 0xff,
(css->hw_end - css->hw_get) * sizeof(*src));
}
gr->cs_data->hw_get = src;
css_hw_set_handled_snapshots(g, sid);
if (pending != sid) {
/* not all entries proceed correctly. some of problems */
/* reported as overflows, some as orphaned perfmons, */
/* but it will be better notify with summary about it */
gk20a_warn(dev_from_gk20a(g),
"cyclestats: done %u from %u entries\n",
sid, pending);
}
return 0;
}
static u32 css_gr_allocate_perfmon_ids(struct gk20a_cs_snapshot *data,
u32 count)
{
u32 *pids = data->perfmon_ids;
u32 f;
u32 e = CSS_MAX_PERFMON_IDS - count;
if (!count || count > CSS_MAX_PERFMON_IDS - CSS_FIRST_PERFMON_ID)
return 0;
for (f = CSS_FIRST_PERFMON_ID; f <= e; f++) {
u32 slots;
u32 cur;
u32 end;
if (CSS_PERFMON_GET(pids, f))
continue;
/* lookup for continuous hole [f, f+count) of unused bits */
slots = 0;
end = f + count;
for (cur = f; cur < end; cur++) {
if (CSS_PERFMON_GET(pids, cur))
break;
slots++;
}
if (count == slots) {
/* we found of hole of unused bits with required */
/* length -> can occupy it for our perfmon IDs */
for (cur = f; cur < end; cur++)
CSS_PERFMON_USE(pids, cur);
return f;
}
}
return 0;
}
static u32 css_gr_release_perfmon_ids(struct gk20a_cs_snapshot *data,
u32 start,
u32 count)
{
u32 *pids = data->perfmon_ids;
u32 end = start + count;
u32 cnt = 0;
if (start >= CSS_FIRST_PERFMON_ID && end <= CSS_MAX_PERFMON_IDS) {
u32 i;
for (i = start; i < end; i++) {
if (CSS_PERFMON_GET(pids, i)) {
CSS_PERFMON_REL(pids, i);
cnt++;
}
}
}
return cnt;
}
static int css_gr_free_client_data(struct gk20a_cs_snapshot *data,
struct gk20a_cs_snapshot_client *client)
{
int ret = 0;
if (client->list.next && client->list.prev)
list_del(&client->list);
if (client->perfmon_start && client->perfmon_count) {
if (client->perfmon_count != css_gr_release_perfmon_ids(data,
client->perfmon_start, client->perfmon_count))
ret = -EINVAL;
}
if (client->dma_handler) {
if (client->snapshot)
dma_buf_vunmap(client->dma_handler, client->snapshot);
dma_buf_put(client->dma_handler);
}
kfree(client);
return ret;
}
static int css_gr_create_client_data(struct gk20a_cs_snapshot *data,
u32 dmabuf_fd, u32 perfmon_count,
struct gk20a_cs_snapshot_client **client)
{
struct gk20a_cs_snapshot_client *cur;
int ret = 0;
cur = kzalloc(sizeof(*cur), GFP_KERNEL);
if (!cur) {
ret = -ENOMEM;
goto failed;
}
cur->dmabuf_fd = dmabuf_fd;
cur->dma_handler = dma_buf_get(cur->dmabuf_fd);
if (IS_ERR(cur->dma_handler)) {
ret = PTR_ERR(cur->dma_handler);
cur->dma_handler = NULL;
goto failed;
}
cur->snapshot = (struct gk20a_cs_snapshot_fifo *)
dma_buf_vmap(cur->dma_handler);
if (!cur->snapshot) {
ret = -ENOMEM;
goto failed;
}
cur->snapshot_size = cur->dma_handler->size;
if (cur->snapshot_size < CSS_MIN_CLIENT_SNAPSHOT_SIZE) {
ret = -ENOMEM;
goto failed;
}
memset(cur->snapshot, 0, sizeof(*cur->snapshot));
cur->snapshot->start = sizeof(*cur->snapshot);
/* we should be ensure that can fit all fifo entries here */
cur->snapshot->end =
CSS_FIFO_ENTRY_CAPACITY(cur->snapshot_size)
* sizeof(struct gk20a_cs_snapshot_fifo_entry)
+ sizeof(struct gk20a_cs_snapshot_fifo);
cur->snapshot->get = cur->snapshot->start;
cur->snapshot->put = cur->snapshot->start;
cur->perfmon_count = perfmon_count;
if (cur->perfmon_count) {
cur->perfmon_start = css_gr_allocate_perfmon_ids(data,
cur->perfmon_count);
if (!cur->perfmon_start) {
ret = -ENOENT;
goto failed;
}
}
list_add_tail(&cur->list, &data->clients);
*client = cur;
return 0;
failed:
*client = NULL;
if (cur)
css_gr_free_client_data(data, cur);
return ret;
}
int gr_gk20a_css_attach(struct gk20a *g,
u32 dmabuf_fd,
u32 perfmon_count,
u32 *perfmon_start,
struct gk20a_cs_snapshot_client **cs_client)
{
int ret = 0;
struct gr_gk20a *gr;
/* we must have a placeholder to store pointer to client structure */
if (!cs_client)
return -EINVAL;
gr = &g->gr;
*cs_client = NULL;
mutex_lock(&gr->cs_lock);
ret = css_gr_create_shared_data(gr);
if (ret)
goto failed;
ret = css_gr_create_client_data(gr->cs_data,
dmabuf_fd,
perfmon_count,
cs_client);
if (ret)
goto failed;
ret = css_hw_enable_snapshot(gr, (*cs_client)->snapshot_size);
if (ret)
goto failed;
if (perfmon_start)
*perfmon_start = (*cs_client)->perfmon_start;
mutex_unlock(&gr->cs_lock);
return 0;
failed:
if (gr->cs_data) {
if (*cs_client) {
css_gr_free_client_data(gr->cs_data, *cs_client);
*cs_client = NULL;
}
if (list_empty(&gr->cs_data->clients))
css_gr_free_shared_data(gr);
}
mutex_unlock(&gr->cs_lock);
if (perfmon_start)
*perfmon_start = 0;
return ret;
}
int gr_gk20a_css_detach(struct gk20a *g,
struct gk20a_cs_snapshot_client *cs_client)
{
int ret = 0;
struct gr_gk20a *gr;
if (!cs_client)
return -EINVAL;
gr = &g->gr;
mutex_lock(&gr->cs_lock);
if (gr->cs_data) {
struct gk20a_cs_snapshot *data = gr->cs_data;
ret = css_gr_free_client_data(data, cs_client);
if (list_empty(&data->clients))
css_gr_free_shared_data(gr);
} else {
ret = -EBADF;
}
mutex_unlock(&gr->cs_lock);
return ret;
}
int gr_gk20a_css_flush(struct gk20a *g,
struct gk20a_cs_snapshot_client *cs_client)
{
int ret = 0;
struct gr_gk20a *gr;
if (!cs_client)
return -EINVAL;
gr = &g->gr;
mutex_lock(&gr->cs_lock);
ret = css_gr_flush_snapshots(gr);
mutex_unlock(&gr->cs_lock);
return ret;
}
/* helper function with locking to cleanup snapshot code code in gr_gk20a.c */
void gr_gk20a_free_cyclestats_snapshot_data(struct gk20a *g)
{
struct gr_gk20a *gr = &g->gr;
mutex_lock(&gr->cs_lock);
css_gr_free_shared_data(gr);
mutex_unlock(&gr->cs_lock);
mutex_destroy(&gr->cs_lock);
}