// SPDX-License-Identifier: LGPL-2.1
/*
 * Copyright (C) 2023 Google Inc, Steven Rostedt <rostedt@goodmis.org>
 */
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h>
#include "tracefs-local.h"

/**
 * struct trace_buffer_meta - Ring-buffer Meta-page description
 * @meta_page_size:	Size of this meta-page.
 * @meta_struct_len:	Size of this structure.
 * @subbuf_size:	Size of each sub-buffer.
 * @nr_subbufs:		Number of subbfs in the ring-buffer, including the reader.
 * @reader.lost_events:	Number of events lost at the time of the reader swap.
 * @reader.id:		subbuf ID of the current reader. ID range [0 : @nr_subbufs - 1]
 * @reader.read:	Number of bytes read on the reader subbuf.
 * @flags:		Placeholder for now, 0 until new features are supported.
 * @entries:		Number of entries in the ring-buffer.
 * @overrun:		Number of entries lost in the ring-buffer.
 * @read:		Number of entries that have been read.
 * @Reserved1:		Internal use only.
 * @Reserved2:		Internal use only.
 */
struct trace_buffer_meta {
	__u32		meta_page_size;
	__u32		meta_struct_len;

	__u32		subbuf_size;
	__u32		nr_subbufs;

	struct {
		__u64	lost_events;
		__u32	id;
		__u32	read;
	} reader;

	__u64	flags;

	__u64	entries;
	__u64	overrun;
	__u64	read;

	__u64	Reserved1;
	__u64	Reserved2;
};

#define TRACE_MMAP_IOCTL_GET_READER		_IO('R', 0x20)

struct trace_mmap {
	struct trace_buffer_meta	*map;
	struct kbuffer			*kbuf;
	void				*data;
	int				*data_pages;
	int				fd;
	int				last_idx;
	int				last_read;
	int				meta_len;
	int				data_len;
};

/**
 * tfs_mmap - try to mmap the ring buffer
 * @fd: The file descriptor to the trace_pipe_raw file
 * @kbuf: The kbuffer to load the subbuffer to
 *
 * Will try to mmap the ring buffer if it is supported, and
 * if not, will return NULL, otherwise it returns a descriptor
 * to handle the mapping.
 */
__hidden void *tfs_mmap(int fd, struct kbuffer *kbuf)
{
	struct trace_mmap *tmap;
	int page_size;
	void *meta;
	void *data;

	page_size = getpagesize();
	meta = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0);
	if (meta == MAP_FAILED)
		return NULL;

	tmap = calloc(1, sizeof(*tmap));
	if (!tmap) {
		munmap(meta, page_size);
		return NULL;
	}

	tmap->kbuf = kbuffer_dup(kbuf);
	if (!tmap->kbuf) {
		munmap(meta, page_size);
		free(tmap);
	}
	kbuf = tmap->kbuf;

	tmap->fd = fd;

	tmap->map = meta;
	tmap->meta_len = tmap->map->meta_page_size;

	if (tmap->meta_len > page_size) {
		munmap(meta, page_size);
		meta = mmap(NULL, tmap->meta_len, PROT_READ, MAP_SHARED, fd, 0);
		if (meta == MAP_FAILED) {
			kbuffer_free(kbuf);
			free(tmap);
			return NULL;
		}
		tmap->map = meta;
	}

	tmap->data_pages = meta + tmap->meta_len;

	tmap->data_len = tmap->map->subbuf_size * tmap->map->nr_subbufs;

	tmap->data = mmap(NULL, tmap->data_len, PROT_READ, MAP_SHARED,
			  fd, tmap->meta_len);
	if (tmap->data == MAP_FAILED) {
		munmap(meta, tmap->meta_len);
		kbuffer_free(kbuf);
		free(tmap);
		return NULL;
	}

	tmap->last_idx = tmap->map->reader.id;

	data = tmap->data + tmap->map->subbuf_size * tmap->last_idx;
	kbuffer_load_subbuffer(kbuf, data);

	/*
	 * The page could have left over data on it that was already
	 * consumed. Move the "read" forward in that case.
	 */
	if (tmap->map->reader.read) {
		int size = kbuffer_start_of_data(kbuf) + tmap->map->reader.read;
		char tmpbuf[size];
		kbuffer_read_buffer(kbuf, tmpbuf, size);
	}

	return tmap;
}

__hidden void tfs_unmap(void *mapping)
{
	struct trace_mmap *tmap = mapping;

	if (!tmap)
		return;

	munmap(tmap->data, tmap->data_len);
	munmap(tmap->map, tmap->meta_len);
	kbuffer_free(tmap->kbuf);
	free(tmap);
}

static int get_reader(struct trace_mmap *tmap)
{
	return ioctl(tmap->fd, TRACE_MMAP_IOCTL_GET_READER);
}

__hidden int tfs_mmap_load_subbuf(void *mapping, struct kbuffer *kbuf)
{
	struct trace_mmap *tmap = mapping;
	void *data;
	int id;

	if (!tmap)
		return -1;

	id = tmap->map->reader.id;
	data = tmap->data + tmap->map->subbuf_size * id;

	/*
	 * If kbuf doesn't point to the current sub-buffer
	 * just load it and return.
	 */
	if (data != kbuffer_subbuffer(kbuf)) {
		kbuffer_load_subbuffer(kbuf, data);
		/* Move the read pointer forward if need be */
		if (kbuffer_curr_index(tmap->kbuf)) {
			int size = kbuffer_curr_offset(tmap->kbuf);
			char tmpbuf[size];
			kbuffer_read_buffer(kbuf, tmpbuf, size);
		}
		return 1;
	}

	/*
	 * Perhaps the reader page had a write that added
	 * more data.
	 */
	kbuffer_refresh(kbuf);

	/* Are there still events to read? */
	if (kbuffer_curr_size(kbuf)) {
		/* If current is greater than what was read, refresh */
		if (kbuffer_curr_offset(kbuf) + kbuffer_curr_size(kbuf) >
		    tmap->map->reader.read) {
			if (get_reader(tmap) < 0)
				return -1;
		}
		return 1;
	}

	/* See if a new page is ready? */
	if (get_reader(tmap) < 0)
		return -1;
	id = tmap->map->reader.id;
	data = tmap->data + tmap->map->subbuf_size * id;

	/*
	 * If the sub-buffer hasn't changed, then there's no more
	 * events to read.
	 */
	if (data == kbuffer_subbuffer(kbuf))
		return 0;

	kbuffer_load_subbuffer(kbuf, data);
	return 1;
}

__hidden int tfs_mmap_read(void *mapping, void *buffer)
{
	struct trace_mmap *tmap = mapping;
	struct kbuffer *kbuf;
	int ret;

	if (!tmap)
		return -1;

	kbuf = tmap->kbuf;

	ret = tfs_mmap_load_subbuf(mapping, kbuf);
	/* Return for error or no more events */
	if (ret <= 0)
		return ret;

	/* Update the buffer */
	ret = kbuffer_read_buffer(kbuf, buffer, tmap->map->subbuf_size);
	if (ret <= 0)
		return ret;

	/* This needs to include the size of the meta data too */
	return ret + kbuffer_start_of_data(kbuf);
}
