/*-
 * Copyright (c) 2019 Adrian Chadd <adrian@FreeBSD.org>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 */
#include "diag.h"

#include "ah.h"
#include "ah_internal.h"

#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <err.h>

#include "../common/ctrl.h"

/*
 * This is a simple wrapper program around the ANI diagnostic interface.
 * It is for fetching and setting the live ANI configuration when trying
 * to diagnose a noisy environment.
 */

/*
 * HAL_DIAG_ANI_CMD is used to set the ANI configuration.
 * HAL_DIAG_ANI_CURRENT is used to fetch the current ANI configuration.
 */

struct ani_var {
  const char *name;
  int id;
};

static struct ani_var ani_labels[] = {
  { "ofdm_noise_immunity_level", 1, },
  { "noise_immunity_level", 1, },
  { "ofdm_weak_signal_detect", 2, },
  { "cck_weak_signal_threshold", 3, },
  { "firstep_level", 4, },
  { "spur_immunity_level", 5, },
  { "mrc_cck", 8, },
  { "cck_noise_immunity_level", 9, },
  { NULL, -1, },
};

static void
usage(void)
{
	fprintf(stderr, "usage: athani [-i interface] [-l]\n");
	fprintf(stderr, "    -i: interface\n");
	fprintf(stderr, "    -l: list ANI labels\n");
	fprintf(stderr, "  If no args are given after flags, the ANI state will be listed.\n");
	fprintf(stderr, "  To set, use '<label> <value>' to set the state\n");
	exit(-1);
}

static void
list_labels(void)
{
	int i;

	for (i = 0; ani_labels[i].name != NULL; i++) {
		printf("%s (%d)\n", ani_labels[i].name, ani_labels[i].id);
	}
}

static int
ani_write_state(struct ath_driver_req *req, const char *ifname,
  const char *label, const char *value)
{
	struct ath_diag atd;
	uint32_t args[2];
	uint32_t cmd, val;
	size_t sl;
	int i;

	/* Find the label */
	sl = strlen(label);
	for (i = 0; ani_labels[i].name != NULL; i++) {
		if ((strlen(ani_labels[i].name) == sl) &&
		    (strcmp(label, ani_labels[i].name) == 0)) {
			cmd = ani_labels[i].id;
			break;
		}
	}
	if (ani_labels[i].name == NULL) {
		fprintf(stderr, "%s: couldn't find ANI label (%s)\n",
		    __func__, label);
		return (-1);
	}

	val = strtoul(value, NULL, 0);

	/*
	 * Whilst we're doing the ath_diag pieces, we have to set this
	 * ourselves.
	 */
	strncpy(atd.ad_name, ifname, sizeof (atd.ad_name));

	/*
	 * Populate HAL_DIAG_ANI_CMD fields.
	 */
	args[0] = cmd;
	args[1] = val;

	atd.ad_id = HAL_DIAG_ANI_CMD | ATH_DIAG_IN;
	atd.ad_out_data = NULL;
	atd.ad_out_size = 0;
	atd.ad_in_data = (void *) &args;
	atd.ad_in_size = sizeof(args);

	if (ath_driver_req_fetch_diag(req, SIOCGATHDIAG, &atd) < 0) {
		warn("SIOCGATHDIAG HAL_DIAG_ANI_CMD (%s)", atd.ad_name);
		return (-1);
	}

	return (0);
}

static void
ani_read_state(struct ath_driver_req *req, const char *ifname)
{
	struct ath_diag atd;
	HAL_ANI_STATE state;

	/*
	 * Whilst we're doing the ath_diag pieces, we have to set this
	 * ourselves.
	 */
	strncpy(atd.ad_name, ifname, sizeof (atd.ad_name));

	atd.ad_id = HAL_DIAG_ANI_CURRENT; /* XXX | DIAG_DYN? */
	atd.ad_out_data = (caddr_t) &state;
	atd.ad_out_size = sizeof(state);

	if (ath_driver_req_fetch_diag(req, SIOCGATHDIAG, &atd) < 0)
		err(1, "%s", atd.ad_name);


	printf("  ofdm_noise_immunity_level=%d\n", state.noiseImmunityLevel);
	printf("  cck_noise_immunity_level=%d\n", state.cckNoiseImmunityLevel);
	printf("  spur_immunity_level=%d\n", state.spurImmunityLevel);
	printf("  firstep_level=%d\n", state.firstepLevel);
	printf("  ofdm_weak_signal_detect=%d\n", state.ofdmWeakSigDetectOff);
	printf("  cck_weak_signal_threshold=%d\n", state.cckWeakSigThreshold);
	printf("  mrc_cck=%d\n", state.mrcCck);
	/* XXX TODO: cycle counts? */
}

int
main(int argc, char *argv[])
{
	struct ath_diag atd;
	const char *ifname;
	struct ath_driver_req req;
	int what, c;

	ath_driver_req_init(&req);

	ifname = getenv("ATH");
	if (!ifname)
		ifname = ATH_DEFAULT;

	what = 0;
	while ((c = getopt(argc, argv, "i:l")) != -1)
		switch (c) {
		case 'i':
			ifname = optarg;
			break;
		case 'l':
			list_labels();
			exit(0);
		default:
			usage();
			/*NOTREACHED*/
		}

	/* Initialise the driver interface */
	if (ath_driver_req_open(&req, ifname) < 0) {
		exit(127);
	}

	argc -= optind;
	argv += optind;

	if (argc == 0) {
		ani_read_state(&req, ifname);
		exit(0);
	}

	if (argc < 2) {
		usage();
		/*NOTREACHED*/
	}

	if (ani_write_state(&req, ifname, argv[0], argv[1]) != 0)
		exit(1);

	exit(0);
}
