// SPDX-License-Identifier: ISC
/* Copyright (C) 2020 Felix Fietkau <nbd@nbd.name> */
#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <poll.h>
#include <fcntl.h>
#include <signal.h>
#include "mt76-test.h"

struct unl unl;
static uint32_t tm_changed[DIV_ROUND_UP(NUM_MT76_TM_ATTRS, 32)];
static const char *progname;

static int phy_lookup_idx(const char *name)
{
	char buf[128];
	FILE *f;
	int len;

	snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", name);
	f = fopen(buf, "r");
	if (!f)
		return -1;

	len = fread(buf, 1, sizeof(buf) - 1, f);
	fclose(f);

	if (!len)
		return -1;

	buf[len] = 0;
	return atoi(buf);
}

void usage(void)
{
	static const char *const commands[] = {
		"set <var>=<val> [...]",
		"dump [stats]",
		"eeprom file",
		"eeprom set <addr>=<val> [...]",
		"eeprom changes",
		"eeprom reset",
	};
	int i;

	fprintf(stderr, "Usage:\n");
	for (i = 0; i < ARRAY_SIZE(commands); i++)
		printf("  %s phyX %s\n", progname, commands[i]);

	exit(1);
}

static int mt76_dump_cb(struct nl_msg *msg, void *arg)
{
	struct nlattr *attr;

	attr = unl_find_attr(&unl, msg, NL80211_ATTR_TESTDATA);
	if (!attr) {
		fprintf(stderr, "Testdata attribute not found\n");
		return NL_SKIP;
	}

	msg_field.print(&msg_field, attr);

	return NL_SKIP;
}

static int mt76_dump(int phy, int argc, char **argv)
{
	struct nl_msg *msg;
	void *data;

	msg = unl_genl_msg(&unl, NL80211_CMD_TESTMODE, true);
	nla_put_u32(msg, NL80211_ATTR_WIPHY, phy);

	data = nla_nest_start(msg, NL80211_ATTR_TESTDATA);

	for (; argc > 0; argc--, argv++) {
		if (!strcmp(argv[0], "stats"))
			nla_put_flag(msg, MT76_TM_ATTR_STATS);
	}

	nla_nest_end(msg, data);

	unl_genl_request(&unl, msg, mt76_dump_cb, NULL);

	return 0;
}

static inline void tm_set_changed(uint32_t id)
{
	tm_changed[id / 32] |= (1U << (id % 32));
}

static inline bool tm_is_changed(uint32_t id)
{
	return tm_changed[id / 32] & (1U << (id % 32));
}

static int mt76_set(int phy, int argc, char **argv)
{
	const struct tm_field *fields = msg_field.fields;
	struct nl_msg *msg;
	void *data;
	int i, ret;

	if (argc < 1)
		return 1;

	msg = unl_genl_msg(&unl, NL80211_CMD_TESTMODE, false);
	nla_put_u32(msg, NL80211_ATTR_WIPHY, phy);

	data = nla_nest_start(msg, NL80211_ATTR_TESTDATA);
	for (; argc > 0; argc--, argv++) {
		char *name = argv[0];
		char *val = strchr(name, '=');

		if (!val) {
			fprintf(stderr, "Invalid argument: %s\n", name);
			return 1;
		}

		*(val++) = 0;

		for (i = 0; i < msg_field.len; i++) {
			if (!fields[i].parse)
				continue;

			if (!strcmp(fields[i].name, name))
				break;
		}

		if (i == msg_field.len) {
			fprintf(stderr, "Unknown field: %s\n", name);
			return 1;
		}

		if (tm_is_changed(i)) {
			fprintf(stderr, "Duplicate field '%s'\n", name);
			return 1;
		}

		if (!fields[i].parse(&fields[i], i, msg, val))
			return 1;

		tm_set_changed(i);
	}

	nla_nest_end(msg, data);

	ret = unl_genl_request(&unl, msg, NULL, NULL);
	if (ret)
		fprintf(stderr, "nl80211 call failed: %s\n", strerror(-ret));

	return ret;
}

int main(int argc, char **argv)
{
	const char *cmd, *phyname;
	int phy;
	int ret = 0;

	progname = argv[0];
	if (argc < 3)
		usage();

	if (unl_genl_init(&unl, "nl80211") < 0) {
		fprintf(stderr, "Failed to connect to nl80211\n");
		return 2;
	}

	phyname = argv[1];
	phy = phy_lookup_idx(phyname);
	if (phy < 0) {
		fprintf(stderr, "Could not find phy '%s'\n", argv[1]);
		return 2;
	}

	cmd = argv[2];
	argv += 3;
	argc -= 3;

	if (!strcmp(cmd, "dump"))
		ret = mt76_dump(phy, argc, argv);
	else if (!strcmp(cmd, "set"))
		ret = mt76_set(phy, argc, argv);
	else if (!strcmp(cmd, "eeprom"))
		ret = mt76_eeprom(phy, argc, argv);
	else if (!strcmp(cmd, "fwlog"))
		ret = mt76_fwlog(phyname, argc, argv);
	else
		usage();

	unl_free(&unl);

	return ret;
}
