diff options
Diffstat (limited to 'drivers/gpu/drm/i915/i915_debugfs.c')
-rw-r--r-- | drivers/gpu/drm/i915/i915_debugfs.c | 570 |
1 files changed, 536 insertions, 34 deletions
diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c index 61fd61969e2..061182a0ce1 100644 --- a/drivers/gpu/drm/i915/i915_debugfs.c +++ b/drivers/gpu/drm/i915/i915_debugfs.c @@ -27,6 +27,8 @@ */ #include <linux/seq_file.h> +#include <linux/circ_buf.h> +#include <linux/ctype.h> #include <linux/debugfs.h> #include <linux/slab.h> #include <linux/export.h> @@ -38,9 +40,6 @@ #include <drm/i915_drm.h> #include "i915_drv.h" -#define DRM_I915_RING_DEBUG 1 - - #if defined(CONFIG_DEBUG_FS) enum { @@ -54,6 +53,32 @@ static const char *yesno(int v) return v ? "yes" : "no"; } +/* As the drm_debugfs_init() routines are called before dev->dev_private is + * allocated we need to hook into the minor for release. */ +static int +drm_add_fake_info_node(struct drm_minor *minor, + struct dentry *ent, + const void *key) +{ + struct drm_info_node *node; + + node = kmalloc(sizeof(*node), GFP_KERNEL); + if (node == NULL) { + debugfs_remove(ent); + return -ENOMEM; + } + + node->minor = minor; + node->dent = ent; + node->info_ent = (void *) key; + + mutex_lock(&minor->debugfs_lock); + list_add(&node->list, &minor->debugfs_list); + mutex_unlock(&minor->debugfs_lock); + + return 0; +} + static int i915_capabilities(struct seq_file *m, void *data) { struct drm_info_node *node = (struct drm_info_node *) m->private; @@ -850,6 +875,8 @@ static int i915_cur_delayinfo(struct seq_file *m, void *unused) drm_i915_private_t *dev_priv = dev->dev_private; int ret; + flush_delayed_work(&dev_priv->rps.delayed_resume_work); + if (IS_GEN5(dev)) { u16 rgvswctl = I915_READ16(MEMSWCTL); u16 rgvstat = I915_READ16(MEMSTAT_ILK); @@ -1328,6 +1355,8 @@ static int i915_ring_freq_table(struct seq_file *m, void *unused) return 0; } + flush_delayed_work(&dev_priv->rps.delayed_resume_work); + ret = mutex_lock_interruptible(&dev_priv->rps.hw_lock); if (ret) return ret; @@ -1402,12 +1431,12 @@ static int i915_gem_framebuffer_info(struct seq_file *m, void *data) { struct drm_info_node *node = (struct drm_info_node *) m->private; struct drm_device *dev = node->minor->dev; - drm_i915_private_t *dev_priv = dev->dev_private; - struct intel_fbdev *ifbdev; + struct intel_fbdev *ifbdev = NULL; struct intel_framebuffer *fb; - int ret; - ret = mutex_lock_interruptible(&dev->mode_config.mutex); +#ifdef CONFIG_DRM_I915_FBDEV + struct drm_i915_private *dev_priv = dev->dev_private; + int ret = mutex_lock_interruptible(&dev->mode_config.mutex); if (ret) return ret; @@ -1423,10 +1452,11 @@ static int i915_gem_framebuffer_info(struct seq_file *m, void *data) describe_obj(m, fb->obj); seq_putc(m, '\n'); mutex_unlock(&dev->mode_config.mutex); +#endif mutex_lock(&dev->mode_config.fb_lock); list_for_each_entry(fb, &dev->mode_config.fb_list, base.head) { - if (&fb->base == ifbdev->helper.fb) + if (ifbdev && &fb->base == ifbdev->helper.fb) continue; seq_printf(m, "user size: %d x %d, depth %d, %d bpp, refcount %d, obj ", @@ -1730,6 +1760,467 @@ static int i915_pc8_status(struct seq_file *m, void *unused) return 0; } +struct pipe_crc_info { + const char *name; + struct drm_device *dev; + enum pipe pipe; +}; + +static int i915_pipe_crc_open(struct inode *inode, struct file *filep) +{ + struct pipe_crc_info *info = inode->i_private; + struct drm_i915_private *dev_priv = info->dev->dev_private; + struct intel_pipe_crc *pipe_crc = &dev_priv->pipe_crc[info->pipe]; + + if (!atomic_dec_and_test(&pipe_crc->available)) { + atomic_inc(&pipe_crc->available); + return -EBUSY; /* already open */ + } + + filep->private_data = inode->i_private; + + return 0; +} + +static int i915_pipe_crc_release(struct inode *inode, struct file *filep) +{ + struct pipe_crc_info *info = inode->i_private; + struct drm_i915_private *dev_priv = info->dev->dev_private; + struct intel_pipe_crc *pipe_crc = &dev_priv->pipe_crc[info->pipe]; + + atomic_inc(&pipe_crc->available); /* release the device */ + + return 0; +} + +/* (6 fields, 8 chars each, space separated (5) + '\n') */ +#define PIPE_CRC_LINE_LEN (6 * 8 + 5 + 1) +/* account for \'0' */ +#define PIPE_CRC_BUFFER_LEN (PIPE_CRC_LINE_LEN + 1) + +static int pipe_crc_data_count(struct intel_pipe_crc *pipe_crc) +{ + int head, tail; + + head = atomic_read(&pipe_crc->head); + tail = atomic_read(&pipe_crc->tail); + + return CIRC_CNT(head, tail, INTEL_PIPE_CRC_ENTRIES_NR); +} + +static ssize_t +i915_pipe_crc_read(struct file *filep, char __user *user_buf, size_t count, + loff_t *pos) +{ + struct pipe_crc_info *info = filep->private_data; + struct drm_device *dev = info->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_pipe_crc *pipe_crc = &dev_priv->pipe_crc[info->pipe]; + char buf[PIPE_CRC_BUFFER_LEN]; + int head, tail, n_entries, n; + ssize_t bytes_read; + + /* + * Don't allow user space to provide buffers not big enough to hold + * a line of data. + */ + if (count < PIPE_CRC_LINE_LEN) + return -EINVAL; + + if (pipe_crc->source == INTEL_PIPE_CRC_SOURCE_NONE) + return 0; + + /* nothing to read */ + while (pipe_crc_data_count(pipe_crc) == 0) { + if (filep->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(pipe_crc->wq, + pipe_crc_data_count(pipe_crc))) + return -ERESTARTSYS; + } + + /* We now have one or more entries to read */ + head = atomic_read(&pipe_crc->head); + tail = atomic_read(&pipe_crc->tail); + n_entries = min((size_t)CIRC_CNT(head, tail, INTEL_PIPE_CRC_ENTRIES_NR), + count / PIPE_CRC_LINE_LEN); + bytes_read = 0; + n = 0; + do { + struct intel_pipe_crc_entry *entry = &pipe_crc->entries[tail]; + int ret; + + bytes_read += snprintf(buf, PIPE_CRC_BUFFER_LEN, + "%8u %8x %8x %8x %8x %8x\n", + entry->frame, entry->crc[0], + entry->crc[1], entry->crc[2], + entry->crc[3], entry->crc[4]); + + ret = copy_to_user(user_buf + n * PIPE_CRC_LINE_LEN, + buf, PIPE_CRC_LINE_LEN); + if (ret == PIPE_CRC_LINE_LEN) + return -EFAULT; + + BUILD_BUG_ON_NOT_POWER_OF_2(INTEL_PIPE_CRC_ENTRIES_NR); + tail = (tail + 1) & (INTEL_PIPE_CRC_ENTRIES_NR - 1); + atomic_set(&pipe_crc->tail, tail); + n++; + } while (--n_entries); + + return bytes_read; +} + +static const struct file_operations i915_pipe_crc_fops = { + .owner = THIS_MODULE, + .open = i915_pipe_crc_open, + .read = i915_pipe_crc_read, + .release = i915_pipe_crc_release, +}; + +static struct pipe_crc_info i915_pipe_crc_data[I915_MAX_PIPES] = { + { + .name = "i915_pipe_A_crc", + .pipe = PIPE_A, + }, + { + .name = "i915_pipe_B_crc", + .pipe = PIPE_B, + }, + { + .name = "i915_pipe_C_crc", + .pipe = PIPE_C, + }, +}; + +static int i915_pipe_crc_create(struct dentry *root, struct drm_minor *minor, + enum pipe pipe) +{ + struct drm_device *dev = minor->dev; + struct dentry *ent; + struct pipe_crc_info *info = &i915_pipe_crc_data[pipe]; + + info->dev = dev; + ent = debugfs_create_file(info->name, S_IRUGO, root, info, + &i915_pipe_crc_fops); + if (IS_ERR(ent)) + return PTR_ERR(ent); + + return drm_add_fake_info_node(minor, ent, info); +} + +static const char * const pipe_crc_sources[] = { + "none", + "plane1", + "plane2", + "pf", + "pipe", +}; + +static const char *pipe_crc_source_name(enum intel_pipe_crc_source source) +{ + BUILD_BUG_ON(ARRAY_SIZE(pipe_crc_sources) != INTEL_PIPE_CRC_SOURCE_MAX); + return pipe_crc_sources[source]; +} + +static int display_crc_ctl_show(struct seq_file *m, void *data) +{ + struct drm_device *dev = m->private; + struct drm_i915_private *dev_priv = dev->dev_private; + int i; + + for (i = 0; i < I915_MAX_PIPES; i++) + seq_printf(m, "%c %s\n", pipe_name(i), + pipe_crc_source_name(dev_priv->pipe_crc[i].source)); + + return 0; +} + +static int display_crc_ctl_open(struct inode *inode, struct file *file) +{ + struct drm_device *dev = inode->i_private; + + return single_open(file, display_crc_ctl_show, dev); +} + +static int ilk_pipe_crc_ctl_reg(enum intel_pipe_crc_source source, + uint32_t *val) +{ + switch (source) { + case INTEL_PIPE_CRC_SOURCE_PLANE1: + *val = PIPE_CRC_ENABLE | PIPE_CRC_SOURCE_PRIMARY_ILK; + break; + case INTEL_PIPE_CRC_SOURCE_PLANE2: + *val = PIPE_CRC_ENABLE | PIPE_CRC_SOURCE_SPRITE_ILK; + break; + case INTEL_PIPE_CRC_SOURCE_PF: + return -EINVAL; + case INTEL_PIPE_CRC_SOURCE_PIPE: + *val = PIPE_CRC_ENABLE | PIPE_CRC_SOURCE_PIPE_ILK; + break; + default: + *val = 0; + break; + } + + return 0; +} + +static int ivb_pipe_crc_ctl_reg(enum intel_pipe_crc_source source, + uint32_t *val) +{ + switch (source) { + case INTEL_PIPE_CRC_SOURCE_PLANE1: + *val = PIPE_CRC_ENABLE | PIPE_CRC_SOURCE_PRIMARY_IVB; + break; + case INTEL_PIPE_CRC_SOURCE_PLANE2: + *val = PIPE_CRC_ENABLE | PIPE_CRC_SOURCE_SPRITE_IVB; + break; + case INTEL_PIPE_CRC_SOURCE_PF: + *val = PIPE_CRC_ENABLE | PIPE_CRC_SOURCE_PF_IVB; + break; + case INTEL_PIPE_CRC_SOURCE_PIPE: + return -EINVAL; + default: + *val = 0; + break; + } + + return 0; +} + +static int pipe_crc_set_source(struct drm_device *dev, enum pipe pipe, + enum intel_pipe_crc_source source) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_pipe_crc *pipe_crc = &dev_priv->pipe_crc[pipe]; + u32 val; + int ret; + + if (!(INTEL_INFO(dev)->gen >= 5 && !IS_VALLEYVIEW(dev))) + return -ENODEV; + + if (pipe_crc->source == source) + return 0; + + /* forbid changing the source without going back to 'none' */ + if (pipe_crc->source && source) + return -EINVAL; + + if (IS_GEN5(dev) || IS_GEN6(dev)) + ret = ilk_pipe_crc_ctl_reg(source, &val); + else + ret = ivb_pipe_crc_ctl_reg(source, &val); + + if (ret != 0) + return ret; + + /* none -> real source transition */ + if (source) { + DRM_DEBUG_DRIVER("collecting CRCs for pipe %c, %s\n", + pipe_name(pipe), pipe_crc_source_name(source)); + + pipe_crc->entries = kzalloc(sizeof(*pipe_crc->entries) * + INTEL_PIPE_CRC_ENTRIES_NR, + GFP_KERNEL); + if (!pipe_crc->entries) + return -ENOMEM; + + atomic_set(&pipe_crc->head, 0); + atomic_set(&pipe_crc->tail, 0); + } + + pipe_crc->source = source; + + I915_WRITE(PIPE_CRC_CTL(pipe), val); + POSTING_READ(PIPE_CRC_CTL(pipe)); + + /* real source -> none transition */ + if (source == INTEL_PIPE_CRC_SOURCE_NONE) { + DRM_DEBUG_DRIVER("stopping CRCs for pipe %c\n", + pipe_name(pipe)); + + intel_wait_for_vblank(dev, pipe); + + kfree(pipe_crc->entries); + pipe_crc->entries = NULL; + } + + return 0; +} + +/* + * Parse pipe CRC command strings: + * command: wsp* object wsp+ name wsp+ source wsp* + * object: 'pipe' + * name: (A | B | C) + * source: (none | plane1 | plane2 | pf) + * wsp: (#0x20 | #0x9 | #0xA)+ + * + * eg.: + * "pipe A plane1" -> Start CRC computations on plane1 of pipe A + * "pipe A none" -> Stop CRC + */ +static int display_crc_ctl_tokenize(char *buf, char *words[], int max_words) +{ + int n_words = 0; + + while (*buf) { + char *end; + + /* skip leading white space */ + buf = skip_spaces(buf); + if (!*buf) + break; /* end of buffer */ + + /* find end of word */ + for (end = buf; *end && !isspace(*end); end++) + ; + + if (n_words == max_words) { + DRM_DEBUG_DRIVER("too many words, allowed <= %d\n", + max_words); + return -EINVAL; /* ran out of words[] before bytes */ + } + + if (*end) + *end++ = '\0'; + words[n_words++] = buf; + buf = end; + } + + return n_words; +} + +enum intel_pipe_crc_object { + PIPE_CRC_OBJECT_PIPE, +}; + +static const char * const pipe_crc_objects[] = { + "pipe", +}; + +static int +display_crc_ctl_parse_object(const char *buf, enum intel_pipe_crc_object *o) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pipe_crc_objects); i++) + if (!strcmp(buf, pipe_crc_objects[i])) { + *o = i; + return 0; + } + + return -EINVAL; +} + +static int display_crc_ctl_parse_pipe(const char *buf, enum pipe *pipe) +{ + const char name = buf[0]; + + if (name < 'A' || name >= pipe_name(I915_MAX_PIPES)) + return -EINVAL; + + *pipe = name - 'A'; + + return 0; +} + +static int +display_crc_ctl_parse_source(const char *buf, enum intel_pipe_crc_source *s) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pipe_crc_sources); i++) + if (!strcmp(buf, pipe_crc_sources[i])) { + *s = i; + return 0; + } + + return -EINVAL; +} + +static int display_crc_ctl_parse(struct drm_device *dev, char *buf, size_t len) +{ +#define N_WORDS 3 + int n_words; + char *words[N_WORDS]; + enum pipe pipe; + enum intel_pipe_crc_object object; + enum intel_pipe_crc_source source; + + n_words = display_crc_ctl_tokenize(buf, words, N_WORDS); + if (n_words != N_WORDS) { + DRM_DEBUG_DRIVER("tokenize failed, a command is %d words\n", + N_WORDS); + return -EINVAL; + } + + if (display_crc_ctl_parse_object(words[0], &object) < 0) { + DRM_DEBUG_DRIVER("unknown object %s\n", words[0]); + return -EINVAL; + } + + if (display_crc_ctl_parse_pipe(words[1], &pipe) < 0) { + DRM_DEBUG_DRIVER("unknown pipe %s\n", words[1]); + return -EINVAL; + } + + if (display_crc_ctl_parse_source(words[2], &source) < 0) { + DRM_DEBUG_DRIVER("unknown source %s\n", words[2]); + return -EINVAL; + } + + return pipe_crc_set_source(dev, pipe, source); +} + +static ssize_t display_crc_ctl_write(struct file *file, const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_device *dev = m->private; + char *tmpbuf; + int ret; + + if (len == 0) + return 0; + + if (len > PAGE_SIZE - 1) { + DRM_DEBUG_DRIVER("expected <%lu bytes into pipe crc control\n", + PAGE_SIZE); + return -E2BIG; + } + + tmpbuf = kmalloc(len + 1, GFP_KERNEL); + if (!tmpbuf) + return -ENOMEM; + + if (copy_from_user(tmpbuf, ubuf, len)) { + ret = -EFAULT; + goto out; + } + tmpbuf[len] = '\0'; + + ret = display_crc_ctl_parse(dev, tmpbuf, len); + +out: + kfree(tmpbuf); + if (ret < 0) + return ret; + + *offp += len; + return len; +} + +static const struct file_operations i915_display_crc_ctl_fops = { + .owner = THIS_MODULE, + .open = display_crc_ctl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = display_crc_ctl_write +}; + static int i915_wedged_get(void *data, u64 *val) { @@ -1943,6 +2434,8 @@ i915_max_freq_get(void *data, u64 *val) if (!(IS_GEN6(dev) || IS_GEN7(dev))) return -ENODEV; + flush_delayed_work(&dev_priv->rps.delayed_resume_work); + ret = mutex_lock_interruptible(&dev_priv->rps.hw_lock); if (ret) return ret; @@ -1967,6 +2460,8 @@ i915_max_freq_set(void *data, u64 val) if (!(IS_GEN6(dev) || IS_GEN7(dev))) return -ENODEV; + flush_delayed_work(&dev_priv->rps.delayed_resume_work); + DRM_DEBUG_DRIVER("Manually setting max freq to %llu\n", val); ret = mutex_lock_interruptible(&dev_priv->rps.hw_lock); @@ -2005,6 +2500,8 @@ i915_min_freq_get(void *data, u64 *val) if (!(IS_GEN6(dev) || IS_GEN7(dev))) return -ENODEV; + flush_delayed_work(&dev_priv->rps.delayed_resume_work); + ret = mutex_lock_interruptible(&dev_priv->rps.hw_lock); if (ret) return ret; @@ -2029,6 +2526,8 @@ i915_min_freq_set(void *data, u64 val) if (!(IS_GEN6(dev) || IS_GEN7(dev))) return -ENODEV; + flush_delayed_work(&dev_priv->rps.delayed_resume_work); + DRM_DEBUG_DRIVER("Manually setting min freq to %llu\n", val); ret = mutex_lock_interruptible(&dev_priv->rps.hw_lock); @@ -2107,32 +2606,6 @@ DEFINE_SIMPLE_ATTRIBUTE(i915_cache_sharing_fops, i915_cache_sharing_get, i915_cache_sharing_set, "%llu\n"); -/* As the drm_debugfs_init() routines are called before dev->dev_private is - * allocated we need to hook into the minor for release. */ -static int -drm_add_fake_info_node(struct drm_minor *minor, - struct dentry *ent, - const void *key) -{ - struct drm_info_node *node; - - node = kmalloc(sizeof(*node), GFP_KERNEL); - if (node == NULL) { - debugfs_remove(ent); - return -ENOMEM; - } - - node->minor = minor; - node->dent = ent; - node->info_ent = (void *) key; - - mutex_lock(&minor->debugfs_lock); - list_add(&node->list, &minor->debugfs_list); - mutex_unlock(&minor->debugfs_lock); - - return 0; -} - static int i915_forcewake_open(struct inode *inode, struct file *file) { struct drm_device *dev = inode->i_private; @@ -2254,8 +2727,22 @@ static struct i915_debugfs_files { {"i915_gem_drop_caches", &i915_drop_caches_fops}, {"i915_error_state", &i915_error_state_fops}, {"i915_next_seqno", &i915_next_seqno_fops}, + {"i915_display_crc_ctl", &i915_display_crc_ctl_fops}, }; +void intel_display_crc_init(struct drm_device *dev) +{ + struct drm_i915_private *dev_priv = dev->dev_private; + int i; + + for (i = 0; i < INTEL_INFO(dev)->num_pipes; i++) { + struct intel_pipe_crc *pipe_crc = &dev_priv->pipe_crc[i]; + + atomic_set(&pipe_crc->available, 1); + init_waitqueue_head(&pipe_crc->wq); + } +} + int i915_debugfs_init(struct drm_minor *minor) { int ret, i; @@ -2264,6 +2751,12 @@ int i915_debugfs_init(struct drm_minor *minor) if (ret) return ret; + for (i = 0; i < ARRAY_SIZE(i915_pipe_crc_data); i++) { + ret = i915_pipe_crc_create(minor->debugfs_root, minor, i); + if (ret) + return ret; + } + for (i = 0; i < ARRAY_SIZE(i915_debugfs_files); i++) { ret = i915_debugfs_create(minor->debugfs_root, minor, i915_debugfs_files[i].name, @@ -2283,8 +2776,17 @@ void i915_debugfs_cleanup(struct drm_minor *minor) drm_debugfs_remove_files(i915_debugfs_list, I915_DEBUGFS_ENTRIES, minor); + drm_debugfs_remove_files((struct drm_info_list *) &i915_forcewake_fops, 1, minor); + + for (i = 0; i < ARRAY_SIZE(i915_pipe_crc_data); i++) { + struct drm_info_list *info_list = + (struct drm_info_list *)&i915_pipe_crc_data[i]; + + drm_debugfs_remove_files(info_list, 1, minor); + } + for (i = 0; i < ARRAY_SIZE(i915_debugfs_files); i++) { struct drm_info_list *info_list = (struct drm_info_list *) i915_debugfs_files[i].fops; |