#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
#include <linux/mutex.h>
+#include <linux/wait.h>
+#include <linux/kref.h>
#include "img_types.h"
#include "servicesext.h"
.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;
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;
void pvr_debugfs_cleanup(void)
{
debugfs_remove_recursive(pvr_debugfs_dir);
+ cleanup_hwrec_debugfs_entries();
}