| // 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 <linux/delay.h> |
| #include <linux/poll.h> |
| |
| /* 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); |
| } |