// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2011-2014  Intel Corporation
 *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>

#include "lib/bluetooth.h"
#include "lib/hci.h"

#include "src/shared/io.h"
#include "monitor/bt.h"
#include "btdev.h"
#include "vhci.h"

#define DEBUGFS_PATH "/sys/kernel/debug/bluetooth"
#define DEVCORE_PATH "/sys/class/devcoredump"

struct vhci {
	enum btdev_type type;
	uint16_t index;
	struct io *io;
	struct btdev *btdev;
};

static void vhci_destroy(void *user_data)
{
	struct vhci *vhci = user_data;

	btdev_destroy(vhci->btdev);
	io_destroy(vhci->io);

	free(vhci);
}

static void vhci_write_callback(const struct iovec *iov, int iovlen,
							void *user_data)
{
	struct vhci *vhci = user_data;
	ssize_t written;

	written = io_send(vhci->io, iov, iovlen);
	if (written < 0)
		return;
}

static bool vhci_read_callback(struct io *io, void *user_data)
{
	struct vhci *vhci = user_data;
	int fd = io_get_fd(vhci->io);
	unsigned char buf[4096];
	ssize_t len;

	len = read(fd, buf, sizeof(buf));
	if (len < 1)
		return false;

	btdev_receive_h4(vhci->btdev, buf, len);

	return true;
}

bool vhci_set_debug(struct vhci *vhci, vhci_debug_func_t callback,
			void *user_data, vhci_destroy_func_t destroy)
{
	if (!vhci)
		return false;

	return btdev_set_debug(vhci->btdev, callback, user_data, destroy);
}

struct vhci_create_req {
	uint8_t  pkt_type;
	uint8_t  opcode;
} __attribute__((packed));

struct vhci_create_rsp {
	uint8_t  pkt_type;
	uint8_t  opcode;
	uint16_t index;
} __attribute__((packed));

struct vhci *vhci_open(uint8_t type)
{
	struct vhci *vhci;
	struct vhci_create_req req;
	struct vhci_create_rsp rsp;
	int fd;

	fd = open("/dev/vhci", O_RDWR | O_NONBLOCK);
	if (fd < 0)
		return NULL;

	memset(&req, 0, sizeof(req));
	req.pkt_type = HCI_VENDOR_PKT;

	switch (type) {
	case BTDEV_TYPE_AMP:
		req.opcode = HCI_AMP;
		break;
	default:
		req.opcode = HCI_PRIMARY;
		break;
	}

	if (write(fd, &req, sizeof(req)) != sizeof(req)) {
		close(fd);
		return NULL;
	}

	memset(&rsp, 0, sizeof(rsp));

	if (read(fd, &rsp, sizeof(rsp)) != sizeof(rsp) ||
			rsp.pkt_type != HCI_VENDOR_PKT ||
			rsp.opcode != req.opcode) {
		close(fd);
		return NULL;
	}

	vhci = malloc(sizeof(*vhci));
	if (!vhci) {
		close(fd);
		return NULL;
	}

	memset(vhci, 0, sizeof(*vhci));
	vhci->type = type;
	vhci->index = rsp.index;
	vhci->io = io_new(fd);

	io_set_close_on_destroy(vhci->io, true);

	vhci->btdev = btdev_create(type, rsp.index);
	if (!vhci->btdev) {
		vhci_destroy(vhci);
		return NULL;
	}

	btdev_set_send_handler(vhci->btdev, vhci_write_callback, vhci);

	if (!io_set_read_handler(vhci->io, vhci_read_callback, vhci, NULL)) {
		vhci_destroy(vhci);
		return NULL;
	}

	return vhci;
}

void vhci_close(struct vhci *vhci)
{
	if (!vhci)
		return;

	vhci_destroy(vhci);
}

bool vhci_pause_input(struct vhci *vhci, bool paused)
{
	if (paused)
		return io_set_read_handler(vhci->io, NULL, NULL, NULL);
	else
		return io_set_read_handler(vhci->io, vhci_read_callback, vhci,
									NULL);
}

struct btdev *vhci_get_btdev(struct vhci *vhci)
{
	if (!vhci)
		return NULL;

	return vhci->btdev;
}

static int vhci_debugfs_write(struct vhci *vhci, char *option, const void *data,
			      size_t len)
{
	char path[64];
	int fd, err;
	size_t n;

	if (!vhci)
		return -EINVAL;

	memset(path, 0, sizeof(path));
	sprintf(path, DEBUGFS_PATH "/hci%d/%s", vhci->index, option);

	fd = open(path, O_RDWR);
	if (fd < 0)
		return -errno;

	n = write(fd, data, len);
	if (n == len)
		err = 0;
	else
		err = -errno;

	close(fd);

	return err;
}

int vhci_set_force_suspend(struct vhci *vhci, bool enable)
{
	char val;

	val = (enable) ? 'Y' : 'N';

	return vhci_debugfs_write(vhci, "force_suspend", &val, sizeof(val));
}

int vhci_set_force_wakeup(struct vhci *vhci, bool enable)
{
	char val;

	val = (enable) ? 'Y' : 'N';

	return vhci_debugfs_write(vhci, "force_wakeup", &val, sizeof(val));
}

int vhci_set_msft_opcode(struct vhci *vhci, uint16_t opcode)
{
	int err;
	char val[7];

	snprintf(val, sizeof(val), "0x%4x", opcode);

	err = vhci_debugfs_write(vhci, "msft_opcode", &val, sizeof(val));
	if (err)
		return err;

	return btdev_set_msft_opcode(vhci->btdev, opcode);
}

int vhci_set_aosp_capable(struct vhci *vhci, bool enable)
{
	char val;

	val = (enable) ? 'Y' : 'N';

	return vhci_debugfs_write(vhci, "aosp_capable", &val, sizeof(val));
}

int vhci_set_emu_opcode(struct vhci *vhci, uint16_t opcode)
{
	return btdev_set_emu_opcode(vhci->btdev, opcode);
}

int vhci_set_force_static_address(struct vhci *vhci, bool enable)
{
	char val;

	val = (enable) ? 'Y' : 'N';

	return vhci_debugfs_write(vhci, "force_static_address", &val,
							sizeof(val));
}

int vhci_force_devcd(struct vhci *vhci, const void *data, size_t len)
{
	return vhci_debugfs_write(vhci, "force_devcoredump", data, len);
}

int vhci_read_devcd(struct vhci *vhci, void *buf, size_t size)
{
	DIR *dir;
	struct dirent *entry;
	char filename[PATH_MAX];
	int fd;
	int ret;

	dir = opendir(DEVCORE_PATH);
	if (dir == NULL)
		return -errno;

	while ((entry = readdir(dir)) != NULL) {
		if (strstr(entry->d_name, "devcd"))
			break;
	}

	if (entry == NULL) {
		ret = -ENOENT;
		goto close_dir;
	}

	sprintf(filename, DEVCORE_PATH "/%s/data", entry->d_name);
	fd  = open(filename, O_RDWR);
	if (fd < 0) {
		ret = -errno;
		goto close_dir;
	}

	ret = read(fd, buf, size);
	if (ret < 0) {
		ret = -errno;
		goto close_file;
	}

	/* Once the devcoredump is read, write anything to it to mark it for
	 * cleanup.
	 */
	if (write(fd, "0", 1) < 0) {
		ret = -errno;
		goto close_file;
	}

close_file:
	close(fd);

close_dir:
	closedir(dir);

	return ret;
}
