cregit-Linux how code gets into the kernel

Release 4.14 drivers/hwtracing/stm/core.c

/*
 * System Trace Module (STM) infrastructure
 * Copyright (c) 2014, Intel Corporation.
 *
 * 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.
 *
 * STM class implements generic infrastructure for  System Trace Module devices
 * as defined in MIPI STPv2 specification.
 */

#include <linux/pm_runtime.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/compat.h>
#include <linux/kdev_t.h>
#include <linux/srcu.h>
#include <linux/slab.h>
#include <linux/stm.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include "stm.h"

#include <uapi/linux/stm.h>


static unsigned int stm_core_up;

/*
 * The SRCU here makes sure that STM device doesn't disappear from under a
 * stm_source_write() caller, which may want to have as little overhead as
 * possible.
 */

static struct srcu_struct stm_source_srcu;


static ssize_t masters_show(struct device *dev, struct device_attribute *attr, char *buf) { struct stm_device *stm = to_stm_device(dev); int ret; ret = sprintf(buf, "%u %u\n", stm->data->sw_start, stm->data->sw_end); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin57100.00%1100.00%
Total57100.00%1100.00%

static DEVICE_ATTR_RO(masters);
static ssize_t channels_show(struct device *dev, struct device_attribute *attr, char *buf) { struct stm_device *stm = to_stm_device(dev); int ret; ret = sprintf(buf, "%u\n", stm->data->sw_nchannels); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin51100.00%1100.00%
Total51100.00%1100.00%

static DEVICE_ATTR_RO(channels);
static ssize_t hw_override_show(struct device *dev, struct device_attribute *attr, char *buf) { struct stm_device *stm = to_stm_device(dev); int ret; ret = sprintf(buf, "%u\n", stm->data->hw_override); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin51100.00%1100.00%
Total51100.00%1100.00%

static DEVICE_ATTR_RO(hw_override); static struct attribute *stm_attrs[] = { &dev_attr_masters.attr, &dev_attr_channels.attr, &dev_attr_hw_override.attr, NULL, }; ATTRIBUTE_GROUPS(stm); static struct class stm_class = { .name = "stm", .dev_groups = stm_groups, };
static int stm_dev_match(struct device *dev, const void *data) { const char *name = data; return sysfs_streq(name, dev_name(dev)); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin34100.00%1100.00%
Total34100.00%1100.00%

/** * stm_find_device() - find stm device by name * @buf: character buffer containing the name * * This is called when either policy gets assigned to an stm device or an * stm_source device gets linked to an stm device. * * This grabs device's reference (get_device()) and module reference, both * of which the calling path needs to make sure to drop with stm_put_device(). * * Return: stm device pointer or null if lookup failed. */
struct stm_device *stm_find_device(const char *buf) { struct stm_device *stm; struct device *dev; if (!stm_core_up) return NULL; dev = class_find_device(&stm_class, NULL, buf, stm_dev_match); if (!dev) return NULL; stm = to_stm_device(dev); if (!try_module_get(stm->owner)) { /* matches class_find_device() above */ put_device(dev); return NULL; } return stm; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin83100.00%2100.00%
Total83100.00%2100.00%

/** * stm_put_device() - drop references on the stm device * @stm: stm device, previously acquired by stm_find_device() * * This drops the module reference and device reference taken by * stm_find_device() or stm_char_open(). */
void stm_put_device(struct stm_device *stm) { module_put(stm->owner); put_device(&stm->dev); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin25100.00%1100.00%
Total25100.00%1100.00%

/* * Internally we only care about software-writable masters here, that is the * ones in the range [stm_data->sw_start..stm_data..sw_end], however we need * original master numbers to be visible externally, since they are the ones * that will appear in the STP stream. Thus, the internal bookkeeping uses * $master - stm_data->sw_start to reference master descriptors and such. */ #define __stm_master(_s, _m) \ ((_s)->masters[(_m) - (_s)->data->sw_start])
static inline struct stp_master * stm_master(struct stm_device *stm, unsigned int idx) { if (idx < stm->data->sw_start || idx > stm->data->sw_end) return NULL; return __stm_master(stm, idx); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin47100.00%1100.00%
Total47100.00%1100.00%


static int stp_master_alloc(struct stm_device *stm, unsigned int idx) { struct stp_master *master; size_t size; size = ALIGN(stm->data->sw_nchannels, 8) / 8; size += sizeof(struct stp_master); master = kzalloc(size, GFP_ATOMIC); if (!master) return -ENOMEM; master->nr_free = stm->data->sw_nchannels; __stm_master(stm, idx) = master; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin86100.00%1100.00%
Total86100.00%1100.00%


static void stp_master_free(struct stm_device *stm, unsigned int idx) { struct stp_master *master = stm_master(stm, idx); if (!master) return; __stm_master(stm, idx) = NULL; kfree(master); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin47100.00%1100.00%
Total47100.00%1100.00%


static void stm_output_claim(struct stm_device *stm, struct stm_output *output) { struct stp_master *master = stm_master(stm, output->master); lockdep_assert_held(&stm->mc_lock); lockdep_assert_held(&output->lock); if (WARN_ON_ONCE(master->nr_free < output->nr_chans)) return; bitmap_allocate_region(&master->chan_map[0], output->channel, ilog2(output->nr_chans)); master->nr_free -= output->nr_chans; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin90100.00%2100.00%
Total90100.00%2100.00%


static void stm_output_disclaim(struct stm_device *stm, struct stm_output *output) { struct stp_master *master = stm_master(stm, output->master); lockdep_assert_held(&stm->mc_lock); lockdep_assert_held(&output->lock); bitmap_release_region(&master->chan_map[0], output->channel, ilog2(output->nr_chans)); output->nr_chans = 0; master->nr_free += output->nr_chans; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin82100.00%2100.00%
Total82100.00%2100.00%

/* * This is like bitmap_find_free_region(), except it can ignore @start bits * at the beginning. */
static int find_free_channels(unsigned long *bitmap, unsigned int start, unsigned int end, unsigned int width) { unsigned int pos; int i; for (pos = start; pos < end + 1; pos = ALIGN(pos, width)) { pos = find_next_zero_bit(bitmap, end + 1, pos); if (pos + width > end + 1) break; if (pos & (width - 1)) continue; for (i = 1; i < width && !test_bit(pos + i, bitmap); i++) ; if (i == width) return pos; } return -1; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin125100.00%1100.00%
Total125100.00%1100.00%


static int stm_find_master_chan(struct stm_device *stm, unsigned int width, unsigned int *mstart, unsigned int mend, unsigned int *cstart, unsigned int cend) { struct stp_master *master; unsigned int midx; int pos, err; for (midx = *mstart; midx <= mend; midx++) { if (!stm_master(stm, midx)) { err = stp_master_alloc(stm, midx); if (err) return err; } master = stm_master(stm, midx); if (!master->nr_free) continue; pos = find_free_channels(master->chan_map, *cstart, cend, width); if (pos < 0) continue; *mstart = midx; *cstart = pos; return 0; } return -ENOSPC; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin148100.00%1100.00%
Total148100.00%1100.00%


static int stm_output_assign(struct stm_device *stm, unsigned int width, struct stp_policy_node *policy_node, struct stm_output *output) { unsigned int midx, cidx, mend, cend; int ret = -EINVAL; if (width > stm->data->sw_nchannels) return -EINVAL; if (policy_node) { stp_policy_node_get_ranges(policy_node, &midx, &mend, &cidx, &cend); } else { midx = stm->data->sw_start; cidx = 0; mend = stm->data->sw_end; cend = stm->data->sw_nchannels - 1; } spin_lock(&stm->mc_lock); spin_lock(&output->lock); /* output is already assigned -- shouldn't happen */ if (WARN_ON_ONCE(output->nr_chans)) goto unlock; ret = stm_find_master_chan(stm, width, &midx, mend, &cidx, cend); if (ret < 0) goto unlock; output->master = midx; output->channel = cidx; output->nr_chans = width; stm_output_claim(stm, output); dev_dbg(&stm->dev, "assigned %u:%u (+%u)\n", midx, cidx, width); ret = 0; unlock: spin_unlock(&output->lock); spin_unlock(&stm->mc_lock); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin23299.15%266.67%
Lucas Tanure20.85%133.33%
Total234100.00%3100.00%


static void stm_output_free(struct stm_device *stm, struct stm_output *output) { spin_lock(&stm->mc_lock); spin_lock(&output->lock); if (output->nr_chans) stm_output_disclaim(stm, output); spin_unlock(&output->lock); spin_unlock(&stm->mc_lock); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin61100.00%2100.00%
Total61100.00%2100.00%


static void stm_output_init(struct stm_output *output) { spin_lock_init(&output->lock); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin19100.00%1100.00%
Total19100.00%1100.00%


static int major_match(struct device *dev, const void *data) { unsigned int major = *(unsigned int *)data; return MAJOR(dev->devt) == major; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin38100.00%1100.00%
Total38100.00%1100.00%


static int stm_char_open(struct inode *inode, struct file *file) { struct stm_file *stmf; struct device *dev; unsigned int major = imajor(inode); int err = -ENOMEM; dev = class_find_device(&stm_class, NULL, &major, major_match); if (!dev) return -ENODEV; stmf = kzalloc(sizeof(*stmf), GFP_KERNEL); if (!stmf) goto err_put_device; err = -ENODEV; stm_output_init(&stmf->output); stmf->stm = to_stm_device(dev); if (!try_module_get(stmf->stm->owner)) goto err_free; file->private_data = stmf; return nonseekable_open(inode, file); err_free: kfree(stmf); err_put_device: /* matches class_find_device() above */ put_device(dev); return err; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin14090.32%375.00%
Johan Hovold159.68%125.00%
Total155100.00%4100.00%


static int stm_char_release(struct inode *inode, struct file *file) { struct stm_file *stmf = file->private_data; struct stm_device *stm = stmf->stm; if (stm->data->unlink) stm->data->unlink(stm->data, stmf->output.master, stmf->output.channel); stm_output_free(stm, &stmf->output); /* * matches the stm_char_open()'s * class_find_device() + try_module_get() */ stm_put_device(stm); kfree(stmf); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin89100.00%3100.00%
Total89100.00%3100.00%


static int stm_file_assign(struct stm_file *stmf, char *id, unsigned int width) { struct stm_device *stm = stmf->stm; int ret; stmf->policy_node = stp_policy_node_lookup(stm, id); ret = stm_output_assign(stm, width, stmf->policy_node, &stmf->output); if (stmf->policy_node) stp_policy_node_put(stmf->policy_node); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin76100.00%1100.00%
Total76100.00%1100.00%


static ssize_t notrace stm_write(struct stm_data *data, unsigned int master, unsigned int channel, const char *buf, size_t count) { unsigned int flags = STP_PACKET_TIMESTAMPED; const unsigned char *p = buf, nil = 0; size_t pos; ssize_t sz; for (pos = 0, p = buf; count > pos; pos += sz, p += sz) { sz = min_t(unsigned int, count - pos, 8); sz = data->packet(data, master, channel, STP_PACKET_DATA, flags, sz, p); flags = 0; if (sz < 0) break; } data->packet(data, master, channel, STP_PACKET_FLAG, 0, 0, &nil); return pos; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin14199.30%266.67%
Chunyan Zhang10.70%133.33%
Total142100.00%3100.00%


static ssize_t stm_char_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct stm_file *stmf = file->private_data; struct stm_device *stm = stmf->stm; char *kbuf; int err; if (count + 1 > PAGE_SIZE) count = PAGE_SIZE - 1; /* * if no m/c have been assigned to this writer up to this * point, use "default" policy entry */ if (!stmf->output.nr_chans) { err = stm_file_assign(stmf, "default", 1); /* * EBUSY means that somebody else just assigned this * output, which is just fine for write() */ if (err && err != -EBUSY) return err; } kbuf = kmalloc(count + 1, GFP_KERNEL); if (!kbuf) return -ENOMEM; err = copy_from_user(kbuf, buf, count); if (err) { kfree(kbuf); return -EFAULT; } pm_runtime_get_sync(&stm->dev); count = stm_write(stm->data, stmf->output.master, stmf->output.channel, kbuf, count); pm_runtime_mark_last_busy(&stm->dev); pm_runtime_put_autosuspend(&stm->dev); kfree(kbuf); return count; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin202100.00%4100.00%
Total202100.00%4100.00%


static void stm_mmap_open(struct vm_area_struct *vma) { struct stm_file *stmf = vma->vm_file->private_data; struct stm_device *stm = stmf->stm; pm_runtime_get(&stm->dev); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin39100.00%1100.00%
Total39100.00%1100.00%


static void stm_mmap_close(struct vm_area_struct *vma) { struct stm_file *stmf = vma->vm_file->private_data; struct stm_device *stm = stmf->stm; pm_runtime_mark_last_busy(&stm->dev); pm_runtime_put_autosuspend(&stm->dev); }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin47100.00%1100.00%
Total47100.00%1100.00%

static const struct vm_operations_struct stm_mmap_vmops = { .open = stm_mmap_open, .close = stm_mmap_close, };
static int stm_char_mmap(struct file *file, struct vm_area_struct *vma) { struct stm_file *stmf = file->private_data; struct stm_device *stm = stmf->stm; unsigned long size, phys; if (!stm->data->mmio_addr) return -EOPNOTSUPP; if (vma->vm_pgoff) return -EINVAL; size = vma->vm_end - vma->vm_start; if (stmf->output.nr_chans * stm->data->sw_mmiosz != size) return -EINVAL; phys = stm->data->mmio_addr(stm->data, stmf->output.master, stmf->output.channel, stmf->output.nr_chans); if (!phys) return -EINVAL; pm_runtime_get_sync(&stm->dev); vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; vma->vm_ops = &stm_mmap_vmops; vm_iomap_memory(vma, phys, size); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin181100.00%2100.00%
Total181100.00%2100.00%


static int stm_char_policy_set_ioctl(struct stm_file *stmf, void __user *arg) { struct stm_device *stm = stmf->stm; struct stp_policy_id *id; int ret = -EINVAL; u32 size; if (stmf->output.nr_chans) return -EBUSY; if (copy_from_user(&size, arg, sizeof(size))) return -EFAULT; if (size < sizeof(*id) || size >= PATH_MAX + sizeof(*id)) return -EINVAL; /* * size + 1 to make sure the .id string at the bottom is terminated, * which is also why memdup_user() is not useful here */ id = kzalloc(size + 1, GFP_KERNEL); if (!id) return -ENOMEM; if (copy_from_user(id, arg, size)) { ret = -EFAULT; goto err_free; } if (id->__reserved_0 || id->__reserved_1) goto err_free; if (id->width < 1 || id->width > PAGE_SIZE / stm->data->sw_mmiosz) goto err_free; ret = stm_file_assign(stmf, id->id, id->width); if (ret) goto err_free; if (stm->data->link) ret = stm->data->link(stm->data, stmf->output.master, stmf->output.channel); if (ret) stm_output_free(stmf->stm, &stmf->output); err_free: kfree(id); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin24596.84%150.00%
Dan Carpenter83.16%150.00%
Total253100.00%2100.00%


static int stm_char_policy_get_ioctl(struct stm_file *stmf, void __user *arg) { struct stp_policy_id id = { .size = sizeof(id), .master = stmf->output.master, .channel = stmf->output.channel, .width = stmf->output.nr_chans, .__reserved_0 = 0, .__reserved_1 = 0, }; return copy_to_user(arg, &id, id.size) ? -EFAULT : 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Shishkin85100.00%1100.00%
Total85100.00%1100.00%


static long stm_char_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct stm_file *stmf = file->private_data; struct stm_data *stm_data = stmf->stm->data; int err = -ENOTTY; u64 options; switch (cmd) { case STP_POLICY_ID_SET: err = stm_char_policy_set_ioctl(stmf, (void __user *)arg); if (err) return err; return stm_char_policy_get_ioctl(stmf, (void __user *)arg); case STP_POLICY_ID_GET: return stm_char_policy_get_ioctl(stmf, (void __user *)arg); case STP_SET_OPTIONS: if (copy_from_user(&options, (u64 __user *)arg, sizeof