// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note /* * * (C) COPYRIGHT 2019-2021 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 #include /* 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 unsigned int 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); /* The timeline stream file operations structure. */ 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, }; /** * 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. * * Returns: 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_header - 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. * * Returns: 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, ©_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 unsigned int 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 -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 POLLIN; poll_wait(filp, &timeline->event_queue, wait); if (kbasep_timeline_io_packet_pending(timeline, &stream, &rb_idx)) return POLLIN; return 0; } /** * 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) { struct kbase_timeline *timeline; ktime_t elapsed_time; s64 elapsed_time_ms, time_to_sleep; KBASE_DEBUG_ASSERT(inode); KBASE_DEBUG_ASSERT(filp); KBASE_DEBUG_ASSERT(filp->private_data); CSTD_UNUSED(inode); timeline = (struct kbase_timeline *)filp->private_data; /* Get the amount of time passed since the timeline was acquired and ensure * we sleep for long enough such that it has been at least * TIMELINE_HYSTERESIS_TIMEOUT_MS amount of time between acquire and release. * This prevents userspace from spamming acquire and release too quickly. */ elapsed_time = ktime_sub(ktime_get(), timeline->last_acquire_time); elapsed_time_ms = ktime_to_ms(elapsed_time); time_to_sleep = MIN(TIMELINE_HYSTERESIS_TIMEOUT_MS, TIMELINE_HYSTERESIS_TIMEOUT_MS - elapsed_time_ms); if (time_to_sleep > 0) msleep(time_to_sleep); #if MALI_USE_CSF kbase_csf_tl_reader_stop(&timeline->csf_tl_reader); #endif /* Stop autoflush timer before releasing access to streams. */ atomic_set(&timeline->autoflush_timer_active, 0); del_timer_sync(&timeline->autoflush_timer); atomic_set(timeline->timeline_flags, 0); return 0; } static int kbasep_timeline_io_fsync(struct file *filp, loff_t start, loff_t end, int datasync) { struct kbase_timeline *timeline; CSTD_UNUSED(start); CSTD_UNUSED(end); CSTD_UNUSED(datasync); if (WARN_ON(!filp->private_data)) return -EFAULT; timeline = (struct kbase_timeline *)filp->private_data; return kbase_timeline_streams_flush(timeline); }