From 0d88ef9df64bcd5110c25ae7bc292399c6938e26 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Fri, 18 Nov 2011 16:38:05 +0200 Subject: [PATCH] gfx: pvr: add hwrec_debugfs entries These entries provide details about an SGX HW recovery event. In general the hwrec_xxx entry will provide a snapshot of the corresponding state as it was at the time of the last recovery, while the xxx entry will give the current state. At the moment we have hwrec_event and hwrec_sgx_registers. hwrec_event is special in that it can be used to step to the next HW recovery event. After it's opened and read all other hwrec_* entries will provide the state for the same event. A second open on hwrec_event will reset all hwrec_* entries, in preparation for the next event. Signed-off-by: Imre Deak Signed-off-by: Kirill A. Shutemov --- drivers/staging/mrst/pvr/pvr_debug_core.h | 6 + drivers/staging/mrst/pvr/pvr_debugfs.c | 428 +++++++++++++++++++++ drivers/staging/mrst/pvr/pvr_debugfs.h | 4 + .../mrst/pvr/services4/srvkm/devices/sgx/sgxinit.c | 3 + 4 files changed, 441 insertions(+) diff --git a/drivers/staging/mrst/pvr/pvr_debug_core.h b/drivers/staging/mrst/pvr/pvr_debug_core.h index 4117056..b1f2e01 100644 --- a/drivers/staging/mrst/pvr/pvr_debug_core.h +++ b/drivers/staging/mrst/pvr/pvr_debug_core.h @@ -4,6 +4,8 @@ #include "sgxinfokm.h" #include "sgx_mkif_km.h" +#define SGX_SAVE_REG_COUNT (0x1000 / 4) + struct sgx_fw_trace_rec { uint32_t v[4]; }; @@ -14,6 +16,10 @@ struct sgx_fw_state { struct sgx_fw_trace_rec trace[SGXMK_TRACE_BUFFER_SIZE]; }; +struct sgx_registers { + uint32_t v[SGX_SAVE_REG_COUNT]; +}; + int sgx_print_fw_status_code(char *buf, size_t buf_size, uint32_t status_code); int sgx_print_fw_trace_rec(char *buf, size_t buf_size, const struct sgx_fw_state *state, int rec_idx); diff --git a/drivers/staging/mrst/pvr/pvr_debugfs.c b/drivers/staging/mrst/pvr/pvr_debugfs.c index 03438a9..d867430 100644 --- a/drivers/staging/mrst/pvr/pvr_debugfs.c +++ b/drivers/staging/mrst/pvr/pvr_debugfs.c @@ -23,7 +23,11 @@ #include #include #include +#include +#include #include +#include +#include #include "img_types.h" #include "servicesext.h" @@ -228,8 +232,417 @@ static const struct file_operations pvr_dbg_fw_state_fops = { .read = pvr_dbg_fw_state_read, }; +static struct sgx_registers *sgx_regs; +static int sgx_regs_busy; +static DEFINE_SPINLOCK(sgx_regs_lock); /* locks sgx_regs_busy */ + +static int pvr_dbg_sgx_regs_open(struct inode *inode, struct file *file) +{ + PVRSRV_DEVICE_NODE *dev_node; + int r = 0; + + dev_node = pvr_get_sgx_dev_node(); + if (!dev_node) + return -ENODEV; + + spin_lock(&sgx_regs_lock); + if (sgx_regs_busy) { + spin_unlock(&sgx_regs_lock); + return -EBUSY; + } + sgx_regs_busy = 1; + spin_unlock(&sgx_regs_lock); + + sgx_regs = vmalloc(sizeof(*sgx_regs)); + if (!sgx_regs) { + r = -ENOMEM; + goto err; + } + + r = sgx_save_registers(dev_node, sgx_regs); + if (r < 0) { + vfree(sgx_regs); + goto err; + } + + return 0; +err: + spin_lock(&sgx_regs_lock); + sgx_regs_busy = 1; + spin_unlock(&sgx_regs_lock); + + return r; +} + +static int pvr_dbg_sgx_regs_release(struct inode *inode, struct file *file) +{ + vfree(sgx_regs); + + spin_lock(&sgx_regs_lock); + sgx_regs_busy = 0; + spin_unlock(&sgx_regs_lock); + + return 0; +} + +static ssize_t pvr_dbg_sgx_regs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + char rec[48]; + ssize_t ret; + + if (*ppos >= ARRAY_SIZE(sgx_regs->v)) + return 0; + + ret = scnprintf(rec, sizeof(rec), "%04lx %08lx\n", + (unsigned long)*ppos * 4, + (unsigned long)sgx_regs->v[*ppos]); + (*ppos)++; + + if (copy_to_user(buffer, rec, ret)) + return -EFAULT; + + return ret; +} + +static const struct file_operations pvr_dbg_sgx_regs_fops = { + .owner = THIS_MODULE, + .open = pvr_dbg_sgx_regs_open, + .release = pvr_dbg_sgx_regs_release, + .read = pvr_dbg_sgx_regs_read, +}; + +/* + * hwrec_event will block until a recovery event happens. Until it's opened + * for the next time the other hwrec_* entries will provide more details about + * the recovery. After this all hwrec_* entries will be reset. + */ +struct hwrec_snapshot { + unsigned count; + unsigned long long time; + struct sgx_registers sgx_regs; + int error; + int reserved; + struct kref ref; /* locks the above in this struct */ + struct list_head entry; +}; + +static atomic_t hwrec_cnt; + +#define HWREC_MAX_SNAPSHOT_CNT 2 +static atomic_t hwrec_snapshot_cnt; +static LIST_HEAD(hwrec_snapshots); +static int hwrec_snapshots_disabled; +/* locks hwrec_snapshots, hwrec_snapshots_disabled */ +static DEFINE_SPINLOCK(hwrec_snapshots_lock); +static DECLARE_WAIT_QUEUE_HEAD(hwrec_wait); + +struct hwrec_debugfs_entry { + ssize_t (*read)(struct hwrec_debugfs_entry *he, char __user *buffer, + size_t count, loff_t *ppos); + const char *name; + int is_lock_entry; + struct hwrec_snapshot *hwrec_snapshot; + int busy; + spinlock_t lock; /* locks busy */ +}; + +static ssize_t pvr_dbg_hwrec_sgx_regs_read(struct hwrec_debugfs_entry *he, + char __user *buffer, size_t count, + loff_t *ppos) +{ + struct sgx_registers *regs = &he->hwrec_snapshot->sgx_regs; + char rec[48]; + int r; + + if (*ppos >= ARRAY_SIZE(regs->v)) + return 0; + + r = scnprintf(rec, sizeof(rec), "%04lx %08lx\n", + (unsigned long)*ppos * 4, + (unsigned long)regs->v[*ppos]); + (*ppos)++; + + if (copy_to_user(buffer, rec, r)) + return -EFAULT; + + return r; +} + +static ssize_t pvr_dbg_hwrec_event_read(struct hwrec_debugfs_entry *he, + char __user *buffer, size_t count, + loff_t *ppos) +{ + struct hwrec_snapshot *s = he->hwrec_snapshot; + char rec[48]; + int r; + + if (*ppos >= 1) + return 0; + + r = scnprintf(rec, sizeof(rec), "%lu %llu\n", + (unsigned long)s->count, s->time); + (*ppos)++; + + if (copy_to_user(buffer, rec, r)) + return -EFAULT; + + return r; +} + +static struct hwrec_debugfs_entry hwrec_debugfs_entries[] = { + { + .name = "hwrec_event", + .read = pvr_dbg_hwrec_event_read, + .is_lock_entry = 1, + }, + { + .name = "hwrec_sgx_registers", + .read = pvr_dbg_hwrec_sgx_regs_read, + }, +}; + +static struct hwrec_snapshot *hwrec_alloc_snapshot(void) +{ + struct hwrec_snapshot *s; + + if (atomic_inc_return(&hwrec_snapshot_cnt) > HWREC_MAX_SNAPSHOT_CNT) + goto err; + + s = vmalloc(sizeof(*s)); + if (!s) + goto err; + kref_init(&s->ref); + + return s; + +err: + atomic_dec(&hwrec_snapshot_cnt); + + return NULL; +} + +static void hwrec_snapshot_get(struct hwrec_snapshot *s) +{ + kref_get(&s->ref); +} + +static void hwrec_release_snapshot(struct kref *kref) +{ + struct hwrec_snapshot *s; + + s = container_of(kref, struct hwrec_snapshot, ref); + vfree(s); + atomic_dec(&hwrec_snapshot_cnt); +} + +static void hwrec_snapshot_put(struct hwrec_snapshot *s) +{ + kref_put(&s->ref, hwrec_release_snapshot); +} + +static int hwrec_wait_snapshot(int dont_block, struct hwrec_snapshot **s_ret) +{ + struct hwrec_snapshot *s; + int r = 0; + + spin_lock(&hwrec_snapshots_lock); + + while (list_empty(&hwrec_snapshots)) { + spin_unlock(&hwrec_snapshots_lock); + + if (dont_block) + return -EAGAIN; + r = wait_event_interruptible(hwrec_wait, + !list_empty(&hwrec_snapshots)); + if (r < 0) + return r; + + spin_lock(&hwrec_snapshots_lock); + } + s = list_first_entry(&hwrec_snapshots, struct hwrec_snapshot, entry); + hwrec_snapshot_get(s); + *s_ret = s; + + spin_unlock(&hwrec_snapshots_lock); + + return r; +} + +void pvr_debugfs_hwrec_create_snapshot(PVRSRV_DEVICE_NODE *dev_node) +{ + struct hwrec_snapshot *s; + unsigned long count; + int disabled; + + count = atomic_inc_return(&hwrec_cnt); + + s = hwrec_alloc_snapshot(); + if (!s) + /* + * no place to record the details of this event, we note + * it only by incrementing hwrec_count. + */ + return; + + s->count = count; + + s->time = cpu_clock(get_cpu()); + put_cpu(); + + sgx_save_registers_no_pwron(dev_node, &s->sgx_regs); + s->error = 0; + + /* + * Note that the ordering of hwrec_snapshots on the list is guaranteed: + * this function won't be re-entered since the HW recovery path is + * non re-entrant in itself. + */ + spin_lock(&hwrec_snapshots_lock); + disabled = hwrec_snapshots_disabled; + if (!disabled) + list_add_tail(&s->entry, &hwrec_snapshots); + spin_unlock(&hwrec_snapshots_lock); + + if (!disabled) + wake_up_interruptible(&hwrec_wait); + else + hwrec_snapshot_put(s); +} + +static void remove_hwrec_snapshot(struct hwrec_snapshot *s) +{ + spin_lock(&hwrec_snapshots_lock); + list_del(&s->entry); + spin_unlock(&hwrec_snapshots_lock); + + hwrec_snapshot_put(s); +} + +static int pvr_dbg_hwrec_open(struct inode *inode, struct file *file) +{ + struct hwrec_debugfs_entry *he = inode->i_private; + + spin_lock(&he->lock); + if (he->busy) { + spin_unlock(&he->lock); + return -EBUSY; + } + he->busy = 1; + spin_unlock(&he->lock); + + file->private_data = he; + + /* remove old snapshot if this is the lock debugfs entry */ + if (he->is_lock_entry && he->hwrec_snapshot) { + remove_hwrec_snapshot(he->hwrec_snapshot); + he->hwrec_snapshot = NULL; + } + + return 0; +} + +static ssize_t pvr_dbg_hwrec_read(struct file *file, + char __user *buffer, + size_t count, loff_t *ppos) +{ + struct hwrec_debugfs_entry *he = file->private_data; + int r; + + if (!he->hwrec_snapshot) { + /* suppress used before init warning */ + struct hwrec_snapshot *s = NULL; + int dont_block = file->f_flags & O_NONBLOCK; + + r = hwrec_wait_snapshot(dont_block, &s); + if (r < 0) + return r; + he->hwrec_snapshot = s; + + r = he->hwrec_snapshot->error; + if (r < 0) + return r; + } + + return he->read(he, buffer, count, ppos); +} + +static int pvr_dbg_hwrec_release(struct inode *inode, struct file *file) +{ + struct hwrec_debugfs_entry *he = file->private_data; + + if (he->hwrec_snapshot) { + hwrec_snapshot_put(he->hwrec_snapshot); + /* + * In case of the lock debugfs entry, we hold on to the + * snapshot, so we can delay the removal of it until the + * debugfs entry is opened next time. + */ + if (!he->is_lock_entry) + he->hwrec_snapshot = NULL; + } + + spin_lock(&he->lock); + he->busy = 0; + spin_unlock(&he->lock); + + return 0; +} + +static unsigned int pvr_dbg_hwrec_poll(struct file *file, poll_table *wait) +{ + struct hwrec_debugfs_entry *he = file->private_data; + + poll_wait(file, &hwrec_wait, wait); + if (!he->hwrec_snapshot) { + int r; + + r = hwrec_wait_snapshot(1, &he->hwrec_snapshot); + if (r == -EAGAIN) + return 0; + if (r < 0 || he->hwrec_snapshot->error) + return POLLERR | POLLHUP; + } + + return POLLIN | POLLRDNORM; +} + +static const struct file_operations pvr_dbg_hwrec_fops = { + .owner = THIS_MODULE, + .open = pvr_dbg_hwrec_open, + .read = pvr_dbg_hwrec_read, + .release = pvr_dbg_hwrec_release, + .poll = pvr_dbg_hwrec_poll, +}; + +static void init_hwrec_debugfs_entries(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hwrec_debugfs_entries); i++) + spin_lock_init(&hwrec_debugfs_entries[i].lock); +} + +static void cleanup_hwrec_debugfs_entries(void) +{ + struct hwrec_snapshot *s, *s_tmp; + + spin_lock(&hwrec_snapshots_lock); + hwrec_snapshots_disabled = 1; + spin_unlock(&hwrec_snapshots_lock); + + /* + * No need to take the lock; hwrec_snapshots_disabled guarantees + * that no one else will modify the list. + */ + list_for_each_entry_safe(s, s_tmp, &hwrec_snapshots, entry) + remove_hwrec_snapshot(s); +} + int pvr_debugfs_init(void) { + int i; + pvr_debugfs_dir = debugfs_create_dir("pvr", NULL); if (!pvr_debugfs_dir) goto err; @@ -247,9 +660,23 @@ int pvr_debugfs_init(void) NULL, &pvr_dbg_fw_state_fops)) goto err; + if (!debugfs_create_file("sgx_registers", S_IRUGO, pvr_debugfs_dir, + NULL, &pvr_dbg_sgx_regs_fops)) + goto err; + + init_hwrec_debugfs_entries(); + for (i = 0; i < ARRAY_SIZE(hwrec_debugfs_entries); i++) { + struct hwrec_debugfs_entry *he = &hwrec_debugfs_entries[i]; + + if (!debugfs_create_file(he->name, S_IRUGO, pvr_debugfs_dir, + he, &pvr_dbg_hwrec_fops)) + goto err; + } + return 0; err: debugfs_remove_recursive(pvr_debugfs_dir); + cleanup_hwrec_debugfs_entries(); pr_err("pvr: debugfs init failed\n"); return -ENODEV; @@ -258,5 +685,6 @@ err: void pvr_debugfs_cleanup(void) { debugfs_remove_recursive(pvr_debugfs_dir); + cleanup_hwrec_debugfs_entries(); } diff --git a/drivers/staging/mrst/pvr/pvr_debugfs.h b/drivers/staging/mrst/pvr/pvr_debugfs.h index ce7c0f16..00c14f5 100644 --- a/drivers/staging/mrst/pvr/pvr_debugfs.h +++ b/drivers/staging/mrst/pvr/pvr_debugfs.h @@ -2,9 +2,13 @@ #define _PVR_DEBUGFS_H_ 1 #ifdef CONFIG_DEBUG_FS +void pvr_debugfs_hwrec_create_snapshot(PVRSRV_DEVICE_NODE *dev_node); int pvr_debugfs_init(void); void pvr_debugfs_cleanup(void); #else +void pvr_debugfs_hwrec_create_snapshot(PVRSRV_DEVICE_NODE *dev_node) +{ +} static inline int pvr_debugfs_init(void) { return 0; diff --git a/drivers/staging/mrst/pvr/services4/srvkm/devices/sgx/sgxinit.c b/drivers/staging/mrst/pvr/services4/srvkm/devices/sgx/sgxinit.c index 1fc54d5..4e8a02f 100644 --- a/drivers/staging/mrst/pvr/services4/srvkm/devices/sgx/sgxinit.c +++ b/drivers/staging/mrst/pvr/services4/srvkm/devices/sgx/sgxinit.c @@ -53,6 +53,7 @@ #include "srvkm.h" #include "pvr_trace_cmd.h" #include "pvr_debug_core.h" +#include "pvr_debugfs.h" #define VAR(x) #x @@ -1049,6 +1050,8 @@ IMG_VOID HWRecoveryResetSGXNoLock(PVRSRV_DEVICE_NODE *psDeviceNode) PVR_LOG(("HWRecoveryResetSGX: SGX Hardware Recovery triggered")); + pvr_debugfs_hwrec_create_snapshot(psDeviceNode); + SGXDumpDebugInfo(psDeviceNode, IMG_TRUE); -- 2.7.4