blob: 6aedfb88f1d097dddad445d407645de2d778ee12 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that 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, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase_timeline_priv.h"
#include "mali_kbase_tlstream.h"
#include "mali_kbase_tracepoints.h"
#include "mali_kbase_timeline.h"
#include <device/mali_kbase_device.h>
#include <linux/poll.h>
#include <linux/version_compat_defs.h>
#include <linux/anon_inodes.h>
#ifndef MALI_STRIP_KBASE_DEVELOPMENT
/* Development builds need to test instrumentation and enable unprivileged
* processes to acquire timeline streams, in order to avoid complications
* with configurations across multiple platforms and systems.
*
* Release builds, instead, shall deny access to unprivileged processes
* because there are no use cases where they are allowed to acquire timeline
* streams, unless they're given special permissions by a privileged process.
*/
static int kbase_unprivileged_global_profiling = 1;
#else
static int kbase_unprivileged_global_profiling;
#endif
/**
* kbase_unprivileged_global_profiling_set - set permissions for unprivileged processes
*
* @val: String containing value to set. Only strings representing positive
* integers are accepted as valid; any non-positive integer (including 0)
* is rejected.
* @kp: Module parameter associated with this method.
*
* This method can only be used to enable permissions for unprivileged processes,
* if they are disabled: for this reason, the only values which are accepted are
* strings representing positive integers. Since it's impossible to disable
* permissions once they're set, any integer which is non-positive is rejected,
* including 0.
*
* Return: 0 if success, otherwise error code.
*/
static int kbase_unprivileged_global_profiling_set(const char *val, const struct kernel_param *kp)
{
int new_val;
int ret = kstrtoint(val, 0, &new_val);
if (ret == 0) {
if (new_val < 1)
return -EINVAL;
kbase_unprivileged_global_profiling = 1;
}
return ret;
}
static const struct kernel_param_ops kbase_global_unprivileged_profiling_ops = {
.get = param_get_int,
.set = kbase_unprivileged_global_profiling_set,
};
module_param_cb(kbase_unprivileged_global_profiling, &kbase_global_unprivileged_profiling_ops,
&kbase_unprivileged_global_profiling, 0600);
/* The timeline stream file operations functions. */
static ssize_t kbasep_timeline_io_read(struct file *filp, char __user *buffer,
size_t size, loff_t *f_pos);
static __poll_t kbasep_timeline_io_poll(struct file *filp, poll_table *wait);
static int kbasep_timeline_io_release(struct inode *inode, struct file *filp);
static int kbasep_timeline_io_fsync(struct file *filp, loff_t start, loff_t end,
int datasync);
static bool timeline_is_permitted(void)
{
#if KERNEL_VERSION(5, 8, 0) <= LINUX_VERSION_CODE
return kbase_unprivileged_global_profiling || perfmon_capable();
#else
return kbase_unprivileged_global_profiling || capable(CAP_SYS_ADMIN);
#endif
}
/**
* kbasep_timeline_io_packet_pending - check timeline streams for pending
* packets
*
* @timeline: Timeline instance
* @ready_stream: Pointer to variable where stream will be placed
* @rb_idx_raw: Pointer to variable where read buffer index will be placed
*
* Function checks all streams for pending packets. It will stop as soon as
* packet ready to be submitted to user space is detected. Variables under
* pointers, passed as the parameters to this function will be updated with
* values pointing to right stream and buffer.
*
* Return: non-zero if any of timeline streams has at last one packet ready
*/
static int
kbasep_timeline_io_packet_pending(struct kbase_timeline *timeline,
struct kbase_tlstream **ready_stream,
unsigned int *rb_idx_raw)
{
enum tl_stream_type i;
KBASE_DEBUG_ASSERT(ready_stream);
KBASE_DEBUG_ASSERT(rb_idx_raw);
for (i = (enum tl_stream_type)0; i < TL_STREAM_TYPE_COUNT; ++i) {
struct kbase_tlstream *stream = &timeline->streams[i];
*rb_idx_raw = atomic_read(&stream->rbi);
/* Read buffer index may be updated by writer in case of
* overflow. Read and write buffer indexes must be
* loaded in correct order.
*/
smp_rmb();
if (atomic_read(&stream->wbi) != *rb_idx_raw) {
*ready_stream = stream;
return 1;
}
}
return 0;
}
/**
* kbasep_timeline_has_header_data() - check timeline headers for pending
* packets
*
* @timeline: Timeline instance
*
* Return: non-zero if any of timeline headers has at last one packet ready.
*/
static int kbasep_timeline_has_header_data(struct kbase_timeline *timeline)
{
return timeline->obj_header_btc || timeline->aux_header_btc
#if MALI_USE_CSF
|| timeline->csf_tl_reader.tl_header.btc
#endif
;
}
/**
* copy_stream_header() - copy timeline stream header.
*
* @buffer: Pointer to the buffer provided by user.
* @size: Maximum amount of data that can be stored in the buffer.
* @copy_len: Pointer to amount of bytes that has been copied already
* within the read system call.
* @hdr: Pointer to the stream header.
* @hdr_size: Header size.
* @hdr_btc: Pointer to the remaining number of bytes to copy.
*
* Return: 0 if success, -1 otherwise.
*/
static inline int copy_stream_header(char __user *buffer, size_t size,
ssize_t *copy_len, const char *hdr,
size_t hdr_size, size_t *hdr_btc)
{
const size_t offset = hdr_size - *hdr_btc;
const size_t copy_size = MIN(size - *copy_len, *hdr_btc);
if (!*hdr_btc)
return 0;
if (WARN_ON(*hdr_btc > hdr_size))
return -1;
if (copy_to_user(&buffer[*copy_len], &hdr[offset], copy_size))
return -1;
*hdr_btc -= copy_size;
*copy_len += copy_size;
return 0;
}
/**
* kbasep_timeline_copy_headers - copy timeline headers to the user
*
* @timeline: Timeline instance
* @buffer: Pointer to the buffer provided by user
* @size: Maximum amount of data that can be stored in the buffer
* @copy_len: Pointer to amount of bytes that has been copied already
* within the read system call.
*
* This helper function checks if timeline headers have not been sent
* to the user, and if so, sends them. copy_len is respectively
* updated.
*
* Return: 0 if success, -1 if copy_to_user has failed.
*/
static inline int kbasep_timeline_copy_headers(struct kbase_timeline *timeline,
char __user *buffer, size_t size,
ssize_t *copy_len)
{
if (copy_stream_header(buffer, size, copy_len, obj_desc_header,
obj_desc_header_size, &timeline->obj_header_btc))
return -1;
if (copy_stream_header(buffer, size, copy_len, aux_desc_header,
aux_desc_header_size, &timeline->aux_header_btc))
return -1;
#if MALI_USE_CSF
if (copy_stream_header(buffer, size, copy_len,
timeline->csf_tl_reader.tl_header.data,
timeline->csf_tl_reader.tl_header.size,
&timeline->csf_tl_reader.tl_header.btc))
return -1;
#endif
return 0;
}
/**
* kbasep_timeline_io_read - copy data from streams to buffer provided by user
*
* @filp: Pointer to file structure
* @buffer: Pointer to the buffer provided by user
* @size: Maximum amount of data that can be stored in the buffer
* @f_pos: Pointer to file offset (unused)
*
* Return: number of bytes stored in the buffer
*/
static ssize_t kbasep_timeline_io_read(struct file *filp, char __user *buffer,
size_t size, loff_t *f_pos)
{
ssize_t copy_len = 0;
struct kbase_timeline *timeline;
KBASE_DEBUG_ASSERT(filp);
KBASE_DEBUG_ASSERT(f_pos);
if (WARN_ON(!filp->private_data))
return -EFAULT;
timeline = (struct kbase_timeline *)filp->private_data;
if (!buffer)
return -EINVAL;
if (*f_pos < 0)
return -EINVAL;
mutex_lock(&timeline->reader_lock);
while (copy_len < size) {
struct kbase_tlstream *stream = NULL;
unsigned int rb_idx_raw = 0;
unsigned int wb_idx_raw;
unsigned int rb_idx;
size_t rb_size;
if (kbasep_timeline_copy_headers(timeline, buffer, size,
&copy_len)) {
copy_len = -EFAULT;
break;
}
/* If we already read some packets and there is no
* packet pending then return back to user.
* If we don't have any data yet, wait for packet to be
* submitted.
*/
if (copy_len > 0) {
if (!kbasep_timeline_io_packet_pending(
timeline, &stream, &rb_idx_raw))
break;
} else {
if (wait_event_interruptible(
timeline->event_queue,
kbasep_timeline_io_packet_pending(
timeline, &stream, &rb_idx_raw))) {
copy_len = -ERESTARTSYS;
break;
}
}
if (WARN_ON(!stream)) {
copy_len = -EFAULT;
break;
}
/* Check if this packet fits into the user buffer.
* If so copy its content.
*/
rb_idx = rb_idx_raw % PACKET_COUNT;
rb_size = atomic_read(&stream->buffer[rb_idx].size);
if (rb_size > size - copy_len)
break;
if (copy_to_user(&buffer[copy_len], stream->buffer[rb_idx].data,
rb_size)) {
copy_len = -EFAULT;
break;
}
/* If the distance between read buffer index and write
* buffer index became more than PACKET_COUNT, then overflow
* happened and we need to ignore the last portion of bytes
* that we have just sent to user.
*/
smp_rmb();
wb_idx_raw = atomic_read(&stream->wbi);
if (wb_idx_raw - rb_idx_raw < PACKET_COUNT) {
copy_len += rb_size;
atomic_inc(&stream->rbi);
#if MALI_UNIT_TEST
atomic_add(rb_size, &timeline->bytes_collected);
#endif /* MALI_UNIT_TEST */
} else {
const unsigned int new_rb_idx_raw =
wb_idx_raw - PACKET_COUNT + 1;
/* Adjust read buffer index to the next valid buffer */
atomic_set(&stream->rbi, new_rb_idx_raw);
}
}
mutex_unlock(&timeline->reader_lock);
return copy_len;
}
/**
* kbasep_timeline_io_poll - poll timeline stream for packets
* @filp: Pointer to file structure
* @wait: Pointer to poll table
*
* Return: POLLIN if data can be read without blocking, otherwise zero
*/
static __poll_t kbasep_timeline_io_poll(struct file *filp, poll_table *wait)
{
struct kbase_tlstream *stream;
unsigned int rb_idx;
struct kbase_timeline *timeline;
KBASE_DEBUG_ASSERT(filp);
KBASE_DEBUG_ASSERT(wait);
if (WARN_ON(!filp->private_data))
return (__force __poll_t)-EFAULT;
timeline = (struct kbase_timeline *)filp->private_data;
/* If there are header bytes to copy, read will not block */
if (kbasep_timeline_has_header_data(timeline))
return (__force __poll_t)POLLIN;
poll_wait(filp, &timeline->event_queue, wait);
if (kbasep_timeline_io_packet_pending(timeline, &stream, &rb_idx))
return (__force __poll_t)POLLIN;
return 0;
}
int kbase_timeline_io_acquire(struct kbase_device *kbdev, u32 flags)
{
/* The timeline stream file operations structure. */
static const struct file_operations kbasep_tlstream_fops = {
.owner = THIS_MODULE,
.release = kbasep_timeline_io_release,
.read = kbasep_timeline_io_read,
.poll = kbasep_timeline_io_poll,
.fsync = kbasep_timeline_io_fsync,
};
int err;
if (!timeline_is_permitted())
return -EPERM;
if (WARN_ON(!kbdev) || (flags & ~BASE_TLSTREAM_FLAGS_MASK))
return -EINVAL;
err = kbase_timeline_acquire(kbdev, flags);
if (err)
return err;
err = anon_inode_getfd("[mali_tlstream]", &kbasep_tlstream_fops, kbdev->timeline,
O_RDONLY | O_CLOEXEC);
if (err < 0)
kbase_timeline_release(kbdev->timeline);
return err;
}
#if IS_ENABLED(CONFIG_DEBUG_FS)
static int kbasep_timeline_io_open(struct inode *in, struct file *file)
{
struct kbase_device *const kbdev = in->i_private;
if (WARN_ON(!kbdev))
return -EFAULT;
file->private_data = kbdev->timeline;
return kbase_timeline_acquire(kbdev, BASE_TLSTREAM_FLAGS_MASK &
~BASE_TLSTREAM_JOB_DUMPING_ENABLED);
}
void kbase_timeline_io_debugfs_init(struct kbase_device *const kbdev)
{
static const struct file_operations kbasep_tlstream_debugfs_fops = {
.owner = THIS_MODULE,
.open = kbasep_timeline_io_open,
.release = kbasep_timeline_io_release,
.read = kbasep_timeline_io_read,
.poll = kbasep_timeline_io_poll,
.fsync = kbasep_timeline_io_fsync,
};
struct dentry *file;
if (WARN_ON(!kbdev) || WARN_ON(IS_ERR_OR_NULL(kbdev->mali_debugfs_directory)))
return;
file = debugfs_create_file("tlstream", 0400, kbdev->mali_debugfs_directory, kbdev,
&kbasep_tlstream_debugfs_fops);
if (IS_ERR_OR_NULL(file))
dev_warn(kbdev->dev, "Unable to create timeline debugfs entry");
}
#else
/*
* Stub function for when debugfs is disabled
*/
void kbase_timeline_io_debugfs_init(struct kbase_device *const kbdev)
{
}
#endif
/**
* kbasep_timeline_io_release - release timeline stream descriptor
* @inode: Pointer to inode structure
* @filp: Pointer to file structure
*
* Return: always return zero
*/
static int kbasep_timeline_io_release(struct inode *inode, struct file *filp)
{
CSTD_UNUSED(inode);
kbase_timeline_release(filp->private_data);
return 0;
}
static int kbasep_timeline_io_fsync(struct file *filp, loff_t start, loff_t end,
int datasync)
{
CSTD_UNUSED(start);
CSTD_UNUSED(end);
CSTD_UNUSED(datasync);
return kbase_timeline_streams_flush(filp->private_data);
}