/*
    V4L2 API compliance input/output ioctl tests.

    Copyright (C) 2011  Hans Verkuil <hverkuil@xs4all.nl>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
 */

#include <map>
#include <set>
#include <vector>

#include <sys/types.h>

#include "v4l2-compliance.h"

#define MAGIC 0x1eadbeef

static int checkEnumFreqBands(struct node *node, __u32 tuner, __u32 type, __u32 caps,
			      __u32 rangelow, __u32 rangehigh)
{
	const __u32 band_caps = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_1HZ;
	__u32 caps_union = 0;
	std::vector<v4l2_frequency_band> bands;
	unsigned low = 0xffffffff;
	unsigned high = 0;
	unsigned i;

	for (i = 0; ; i++) {
		struct v4l2_frequency_band band;
		int ret;

		memset(band.reserved, 0, sizeof(band.reserved));
		band.tuner = tuner;
		band.type = type;
		band.index = i;
		ret = doioctl(node, VIDIOC_ENUM_FREQ_BANDS, &band);
		if (ret == EINVAL && i)
			return 0;
		if (ret)
			return fail("couldn't get freq band\n");
		caps_union |= band.capability;
		if ((caps & band_caps) != (band.capability & band_caps))
			return fail("Inconsistent CAP_LOW/CAP_1HZ usage\n");
		fail_on_test(band.rangelow == 0);
		fail_on_test(band.rangehigh < band.rangelow);
		fail_on_test(band.index != i);
		fail_on_test(band.type != type);
		fail_on_test(band.tuner != tuner);
		fail_on_test((band.capability & V4L2_TUNER_CAP_FREQ_BANDS) == 0);
		fail_on_test(check_0(band.reserved, sizeof(band.reserved)));
		if (band.rangelow < low)
			low = band.rangelow;
		if (band.rangehigh > high)
			high = band.rangehigh;
		bands.push_back(band);
	}
	fail_on_test(caps_union != caps);
	fail_on_test(low != rangelow);
	fail_on_test(high != rangehigh);

	// Check that the bands do not overlap or are adjacent
	for (i = 0; i < bands.size(); i++)
		for (unsigned j = 1; j < bands.size(); j++)
			fail_on_test(bands[i].rangehigh + 1 >= bands[j].rangelow &&
				     bands[i].rangelow - 1 <= bands[j].rangehigh);
	return 0;
}

static int checkTuner(struct node *node, const struct v4l2_tuner &tuner,
		unsigned t, v4l2_std_id std)
{
	bool valid_modes[5] = { true, false, false, false, false };
	bool tv = node->is_video || node->is_vbi || node->is_meta;
	bool hwseek_caps = tuner.capability & (V4L2_TUNER_CAP_HWSEEK_BOUNDED |
			V4L2_TUNER_CAP_HWSEEK_WRAP | V4L2_TUNER_CAP_HWSEEK_PROG_LIM);
	unsigned type = tv ? V4L2_TUNER_ANALOG_TV : V4L2_TUNER_RADIO;
	__u32 audmode;

	if (tuner.index != t)
		return fail("invalid index\n");
	if (check_ustring(tuner.name, sizeof(tuner.name)))
		return fail("invalid name\n");
	if (check_0(tuner.reserved, sizeof(tuner.reserved)))
		return fail("non-zero reserved fields\n");
	if (node->is_sdr) {
		fail_on_test(tuner.type != V4L2_TUNER_SDR && tuner.type != V4L2_TUNER_RF);
	} else if (tuner.type != type) {
		return fail("invalid tuner type %d\n", tuner.type);
	}
	if (tv && (tuner.capability & V4L2_TUNER_CAP_RDS))
		return fail("RDS for TV tuner?\n");
	if (!tv && (tuner.capability & (V4L2_TUNER_CAP_NORM |
					V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2)))
		return fail("TV capabilities for radio tuner?\n");
	if (tv && (tuner.capability & (V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_1HZ)))
		return fail("did not expect to see V4L2_TUNER_CAP_LOW/1HZ set for a tv tuner\n");
	if (node->is_radio && !(tuner.capability & V4L2_TUNER_CAP_LOW))
		return fail("V4L2_TUNER_CAP_LOW was not set for a radio tuner\n");
	fail_on_test((tuner.capability & V4L2_TUNER_CAP_LOW) &&
		     (tuner.capability & V4L2_TUNER_CAP_1HZ));
	if (node->is_sdr)
		fail_on_test(!(V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_1HZ));
	fail_on_test(!(tuner.capability & V4L2_TUNER_CAP_FREQ_BANDS));
	fail_on_test(!(node->g_caps() & V4L2_CAP_HW_FREQ_SEEK) && hwseek_caps);
	fail_on_test((node->g_caps() & V4L2_CAP_HW_FREQ_SEEK) &&
		!(tuner.capability & (V4L2_TUNER_CAP_HWSEEK_BOUNDED | V4L2_TUNER_CAP_HWSEEK_WRAP)));
	if (tuner.rangelow > tuner.rangehigh)
		return fail("rangelow > rangehigh\n");
	if (tuner.rangelow == 0 || tuner.rangehigh == 0xffffffff)
		return fail("invalid rangelow or rangehigh\n");
	if (!(tuner.capability & V4L2_TUNER_CAP_STEREO) &&
			(tuner.rxsubchans & V4L2_TUNER_SUB_STEREO))
		return fail("stereo subchan, but no stereo caps?\n");
	if (!(tuner.capability & V4L2_TUNER_CAP_LANG1) &&
			(tuner.rxsubchans & V4L2_TUNER_SUB_LANG1))
		return fail("lang1 subchan, but no lang1 caps?\n");
	if (!(tuner.capability & V4L2_TUNER_CAP_LANG2) &&
			(tuner.rxsubchans & V4L2_TUNER_SUB_LANG2))
		return fail("lang2 subchan, but no lang2 caps?\n");
	if (!(tuner.capability & V4L2_TUNER_CAP_RDS) &&
			(tuner.rxsubchans & V4L2_TUNER_SUB_RDS))
		return fail("RDS subchan, but no RDS caps?\n");
	bool have_rds = tuner.capability & V4L2_TUNER_CAP_RDS;
	bool have_rds_method = tuner.capability &
                        (V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_RDS_CONTROLS);
	if (have_rds ^ have_rds_method)
		return fail("V4L2_TUNER_CAP_RDS is set, but not V4L2_TUNER_CAP_RDS_* or vice versa\n");
	fail_on_test(node->is_sdr && have_rds);
	if ((tuner.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO) &&
			!(node->g_caps() & V4L2_CAP_READWRITE))
		return fail("V4L2_TUNER_CAP_RDS_BLOCK_IO is set, but not V4L2_CAP_READWRITE\n");
	if (node->is_radio && !(tuner.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO) &&
			(node->g_caps() & V4L2_CAP_READWRITE))
		return fail("V4L2_TUNER_CAP_RDS_BLOCK_IO is not set, but V4L2_CAP_READWRITE is\n");
	if (std == V4L2_STD_NTSC_M && (tuner.rxsubchans & V4L2_TUNER_SUB_LANG1))
		return fail("LANG1 subchan, but NTSC-M standard\n");
	if (tuner.audmode > V4L2_TUNER_MODE_LANG1_LANG2)
		return fail("invalid audio mode\n");
	if (!tv && tuner.audmode > V4L2_TUNER_MODE_STEREO)
		return fail("invalid audio mode for radio device\n");
	if (tuner.signal > 65535)
		return fail("signal too large\n");
	if (tuner.capability & V4L2_TUNER_CAP_STEREO)
		valid_modes[V4L2_TUNER_MODE_STEREO] = true;
	if (tuner.capability & V4L2_TUNER_CAP_LANG1)
		valid_modes[V4L2_TUNER_MODE_LANG1] = true;
	if (tuner.capability & V4L2_TUNER_CAP_LANG2) {
		valid_modes[V4L2_TUNER_MODE_LANG2] = true;
		valid_modes[V4L2_TUNER_MODE_LANG1_LANG2] = true;
	}
	for (audmode = 0; audmode < 5; audmode++) {
		struct v4l2_tuner tun = { 0 };

		tun.index = tuner.index;
		tun.audmode = audmode;
		if (doioctl(node, VIDIOC_S_TUNER, &tun))
			return fail("cannot set audmode %d\n", audmode);
		if (doioctl(node, VIDIOC_G_TUNER, &tun))
			fail("failure to get new tuner audmode\n");
		if (tun.audmode > V4L2_TUNER_MODE_LANG1_LANG2)
			return fail("invalid new audmode\n");
		if (!valid_modes[tun.audmode])
			return fail("accepted invalid audmode %d\n", audmode);
	}
	return checkEnumFreqBands(node, tuner.index, tuner.type, tuner.capability,
			tuner.rangelow, tuner.rangehigh);
}

int testTuner(struct node *node)
{
	struct v4l2_tuner tuner;
	v4l2_std_id std;
	unsigned t = 0;
	bool has_rds = false;
	int ret;

	if (doioctl(node, VIDIOC_G_STD, &std))
		std = 0;

	for (;;) {
		memset(&tuner, 0xff, sizeof(tuner));
		memset(tuner.reserved, 0, sizeof(tuner.reserved));
		tuner.index = t;
		ret = doioctl(node, VIDIOC_G_TUNER, &tuner);
		if (ret == ENOTTY)
			return ret;
		if (ret == EINVAL)
			break;
		if (ret)
			return fail("couldn't get tuner %d\n", t);
		if (checkTuner(node, tuner, t, std))
			return fail("invalid tuner %d\n", t);
		t++;
		node->tuners++;
		if (tuner.capability & V4L2_TUNER_CAP_RDS)
			has_rds = true;
	}
	memset(&tuner, 0, sizeof(tuner));
	tuner.index = t;
	if (doioctl(node, VIDIOC_S_TUNER, &tuner) != EINVAL)
		return fail("could set invalid tuner %d\n", t);
	if (node->tuners && !(node->g_caps() & V4L2_CAP_TUNER))
		return fail("tuners found, but no tuner capability set\n");
	if (!node->tuners && (node->g_caps() & V4L2_CAP_TUNER))
		return fail("no tuners found, but tuner capability set\n");
	if (has_rds && !(node->g_caps() & V4L2_CAP_RDS_CAPTURE))
		return fail("RDS tuner capability, but no RDS capture capability?\n");
	if (!has_rds && (node->g_caps() & V4L2_CAP_RDS_CAPTURE))
		return fail("No RDS tuner capability, but RDS capture capability?\n");
	return 0;
}

int testTunerFreq(struct node *node)
{
	struct v4l2_frequency freq = { 0 };
	enum v4l2_tuner_type last_type = V4L2_TUNER_ANALOG_TV;
	unsigned t;
	int ret;

	for (t = 0; t < node->tuners; t++) {
		struct v4l2_tuner tuner = { 0 };

		tuner.index = t;
		ret = doioctl(node, VIDIOC_G_TUNER, &tuner);
		if (ret)
			return fail("could not get tuner %d\n", t);
		last_type = static_cast<enum v4l2_tuner_type>(tuner.type);
		memset(&freq, 0, sizeof(freq));
		freq.tuner = t;
		ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
		if (ret)
			return fail("could not get frequency for tuner %d\n", t);
		if (check_0(freq.reserved, sizeof(freq.reserved)))
			return fail("reserved was not zeroed\n");
		if (freq.type != V4L2_TUNER_RADIO && freq.type != V4L2_TUNER_ANALOG_TV &&
		    freq.type != V4L2_TUNER_SDR && freq.type != V4L2_TUNER_RF)
			return fail("returned invalid tuner type %d\n", freq.type);
		if (freq.type == V4L2_TUNER_RADIO && !(node->g_caps() & V4L2_CAP_RADIO))
			return fail("radio tuner found but no radio capability set\n");
		if ((freq.type == V4L2_TUNER_SDR || freq.type == V4L2_TUNER_RF) &&
		    !(node->g_caps() & V4L2_CAP_SDR_CAPTURE))
			return fail("sdr tuner found but no sdr capture capability set\n");
		if (freq.type != tuner.type)
			return fail("frequency tuner type and tuner type mismatch\n");
		if (freq.tuner != t)
			return fail("frequency tuner field changed!\n");
		if (freq.frequency == 0)
			return fail("frequency not set\n");
		if (freq.frequency < tuner.rangelow || freq.frequency > tuner.rangehigh)
			warn("returned tuner %d frequency out of range (%d not in [%d...%d])\n",
					t, freq.frequency, tuner.rangelow, tuner.rangehigh);

		freq.type = static_cast<enum v4l2_tuner_type>(0);
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret != EINVAL)
			return fail("did not return EINVAL when passed tuner type 0\n");
		freq.type = tuner.type;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set current frequency\n");
		freq.frequency = tuner.rangelow;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangelow frequency\n");
		freq.frequency = tuner.rangehigh;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangehigh frequency\n");
		freq.frequency = tuner.rangelow - 1;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangelow-1 frequency\n");
		ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
		if (ret || freq.frequency != tuner.rangelow)
			return fail("frequency rangelow-1 wasn't mapped to rangelow\n");
		freq.frequency = tuner.rangehigh + 1;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangehigh+1 frequency\n");
		ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
		if (ret || freq.frequency != tuner.rangehigh)
			return fail("frequency rangehigh+1 wasn't mapped to rangehigh\n");

		for (unsigned i = 0; ; i++) {
			struct v4l2_frequency_band band;

			memset(band.reserved, 0, sizeof(band.reserved));
			band.tuner = t;
			band.type = tuner.type;
			band.index = i;
			ret = doioctl(node, VIDIOC_ENUM_FREQ_BANDS, &band);
			fail_on_test(i == 0 && ret);
			if (ret == EINVAL)
				break;
			fail_on_test(ret);

			freq.frequency = band.rangelow;
			ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
			if (ret)
				return fail("could not set rangelow frequency band\n");
			freq.frequency = band.rangehigh;
			ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
			if (ret)
				return fail("could not set rangehigh frequency band\n");
			freq.frequency = band.rangelow - 1;
			ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
			if (ret)
				return fail("could not set rangelow-1 frequency band\n");
			ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
			if (ret || freq.frequency != band.rangelow)
				return fail("frequency band rangelow-1 wasn't mapped to rangelow\n");
			freq.frequency = band.rangehigh + 1;
			ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
			if (ret)
				return fail("could not set rangehigh+1 frequency band\n");
			ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
			if (ret || freq.frequency != band.rangehigh)
				return fail("frequency band rangehigh+1 wasn't mapped to rangehigh\n");
		}
	}

	/* If this is a modulator device, then skip the remaining tests */
	if (node->g_caps() & V4L2_CAP_MODULATOR)
		return 0;

	freq.tuner = t;
	ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
	if (ret != EINVAL && ret != ENOTTY)
		return fail("could get frequency for invalid tuner %d\n", t);
	freq.tuner = t;
	freq.type = last_type;
	// TV: 400 Mhz Radio: 100 MHz
	freq.frequency = last_type == V4L2_TUNER_ANALOG_TV ? 6400 : 1600000;
	ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
	if (ret != EINVAL && ret != ENOTTY)
		return fail("could set frequency for invalid tuner %d\n", t);
	return node->tuners ? 0 : ENOTTY;
}

int testTunerHwSeek(struct node *node)
{
	struct v4l2_hw_freq_seek seek;
	unsigned t;
	int ret;

	for (t = 0; t < node->tuners; t++) {
		struct v4l2_tuner tuner = { 0 };

		tuner.index = t;
		ret = doioctl(node, VIDIOC_G_TUNER, &tuner);
		if (ret)
			return fail("could not get tuner %d\n", t);

		memset(&seek, 0, sizeof(seek));
		seek.tuner = t;
		seek.type = V4L2_TUNER_RADIO;
		ret = doioctl(node, VIDIOC_S_HW_FREQ_SEEK, &seek);
		if (!(node->g_caps() & V4L2_CAP_HW_FREQ_SEEK) && ret != ENOTTY)
			return fail("hw seek supported but capability not set\n");
		if (!node->is_radio && ret != ENOTTY)
			return fail("hw seek supported on a non-radio node?!\n");
		if (!node->is_radio || !(node->g_caps() & V4L2_CAP_HW_FREQ_SEEK))
			return ENOTTY;
		seek.type = V4L2_TUNER_ANALOG_TV;
		ret = doioctl(node, VIDIOC_S_HW_FREQ_SEEK, &seek);
		if (ret != EINVAL)
			return fail("hw seek accepted TV tuner\n");
		seek.type = V4L2_TUNER_RADIO;
		seek.seek_upward = 1;
		ret = doioctl(node, VIDIOC_S_HW_FREQ_SEEK, &seek);
		if (ret == EINVAL && (tuner.capability & V4L2_TUNER_CAP_HWSEEK_BOUNDED))
			return fail("hw bounded seek failed\n");
		if (ret && ret != EINVAL && ret != ENODATA)
			return fail("hw bounded seek failed with error %d\n", ret);
		seek.wrap_around = 1;
		ret = doioctl(node, VIDIOC_S_HW_FREQ_SEEK, &seek);
		if (ret == EINVAL && (tuner.capability & V4L2_TUNER_CAP_HWSEEK_WRAP))
			return fail("hw wrapped seek failed\n");
		if (ret && ret != EINVAL && ret != ENODATA)
			return fail("hw wrapped seek failed with error %d\n", ret);
		if (check_0(seek.reserved, sizeof(seek.reserved)))
			return fail("non-zero reserved fields\n");
	}
	memset(&seek, 0, sizeof(seek));
	seek.tuner = node->tuners;
	seek.type = V4L2_TUNER_RADIO;
	ret = doioctl(node, VIDIOC_S_HW_FREQ_SEEK, &seek);
	if (ret != EINVAL && ret != ENOTTY)
		return fail("hw seek for invalid tuner didn't return EINVAL or ENOTTY\n");
	return ret == ENOTTY ? ret : 0;
}

static int checkInput(struct node *node, const struct v4l2_input &descr, unsigned i)
{
	__u32 mask = (1 << node->audio_inputs) - 1;
	struct v4l2_selection sel;

	if (descr.index != i)
		return fail("invalid index\n");
	if (check_ustring(descr.name, sizeof(descr.name)))
		return fail("invalid name\n");
	if (descr.type != V4L2_INPUT_TYPE_TUNER &&
	    descr.type != V4L2_INPUT_TYPE_CAMERA &&
	    descr.type != V4L2_INPUT_TYPE_TOUCH)
		return fail("invalid type\n");
	if (descr.type == V4L2_INPUT_TYPE_CAMERA && descr.tuner)
		return fail("invalid tuner\n");
	if (descr.type == V4L2_INPUT_TYPE_TUNER && node->tuners == 0)
		return fail("no tuners found for tuner input\n");
	if (!(descr.capabilities & V4L2_IN_CAP_STD) && descr.std)
		return fail("invalid std\n");
	if ((descr.capabilities & V4L2_IN_CAP_STD) && !descr.std)
		return fail("std == 0\n");
	memset(&sel, 0, sizeof(sel));
	sel.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	sel.target = V4L2_SEL_TGT_NATIVE_SIZE;
	if (descr.capabilities & V4L2_IN_CAP_NATIVE_SIZE) {
		fail_on_test(doioctl(node, VIDIOC_G_SELECTION, &sel));
		fail_on_test(doioctl(node, VIDIOC_S_SELECTION, &sel));
	} else if (!doioctl(node, VIDIOC_G_SELECTION, &sel)) {
		fail_on_test(!doioctl(node, VIDIOC_S_SELECTION, &sel));
	}
	if (descr.capabilities & ~0x7)
		return fail("invalid capabilities\n");
	if (check_0(descr.reserved, sizeof(descr.reserved)))
		return fail("non-zero reserved fields\n");
	if (descr.status & ~0x07070337)
		return fail("invalid status\n");
	if (descr.status & 0x02060000)
		return fail("use of deprecated digital video status\n");
	if (descr.audioset & ~mask)
		return fail("invalid audioset\n");
	if (descr.tuner && descr.tuner >= node->tuners)
		return fail("invalid tuner\n");
	return 0;
}

int testInput(struct node *node)
{
	struct v4l2_input descr;
	int cur_input = MAGIC;
	int input;
	int ret = doioctl(node, VIDIOC_G_INPUT, &cur_input);
	int i = 0;

	if (ret == ENOTTY) {
		if (node->has_inputs) {
			if (media_fd < 0)
				return fail("G_INPUT not supported for a capture device\n");
			node->has_inputs = false;
			return ENOTTY;
		}
		descr.index = 0;
		ret = doioctl(node, VIDIOC_ENUMINPUT, &descr);
		if (ret != ENOTTY)
			return fail("G_INPUT not supported, but ENUMINPUT is\n");
		cur_input = 0;
		ret = doioctl(node, VIDIOC_S_INPUT, &cur_input);
		if (ret != ENOTTY)
			return fail("G_INPUT not supported, but S_INPUT is\n");
		return ENOTTY;
	}
	if (ret)
		return fail("could not get current input\n");
	if (cur_input == MAGIC)
		return fail("VIDIOC_G_INPUT didn't fill in the input\n");
	if (node->is_radio)
		return fail("radio can't have input support\n");
	for (;;) {
		memset(&descr, 0xff, sizeof(descr));
		descr.index = i;
		ret = doioctl(node, VIDIOC_ENUMINPUT, &descr);
		if (ret == EINVAL)
			break;
		if (ret)
			return fail("could not enumerate input %d\n", i);
		input = i;
		if (doioctl(node, VIDIOC_S_INPUT, &input))
			return fail("could not set input to %d\n", i);
		if (input != i)
			return fail("input set to %d, but becomes %d?!\n", i, input);
		if (checkInput(node, descr, i))
			return fail("invalid attributes for input %d\n", i);
		node->inputs++;
		i++;
	}
	input = i;
	if (doioctl(node, VIDIOC_S_INPUT, &input) != EINVAL)
		return fail("could set input to invalid input %d\n", i);
	if (doioctl(node, VIDIOC_S_INPUT, &cur_input))
		return fail("couldn't set input to the original input %d\n", cur_input);
	if (node->inputs && !node->has_inputs)
		return fail("inputs found, but no input capabilities set\n");
	if (!node->inputs && node->has_inputs)
		return fail("no inputs found, but input capabilities set\n");
	fail_on_test(node->is_m2m && node->inputs > 1);
	if (node->is_io_mc) {
		fail_on_test(!node->is_video && !node->is_meta);
		fail_on_test(node->inputs != 1);
	}
	return 0;
}

static int checkInputAudio(const struct v4l2_audio &descr, unsigned i)
{
	if (descr.index != i)
		return fail("invalid index\n");
	if (check_ustring(descr.name, sizeof(descr.name)))
		return fail("invalid name\n");
	if (descr.capability & ~0x3)
		return fail("invalid capabilities\n");
	if (descr.mode != 0 && descr.mode != V4L2_AUDMODE_AVL)
		return fail("invalid mode\n");
	if (!(descr.capability & V4L2_AUDCAP_AVL) && descr.mode)
		return fail("mode != 0\n");
	if (check_0(descr.reserved, sizeof(descr.reserved)))
		return fail("non-zero reserved fields\n");
	return 0;
}

int testEnumInputAudio(struct node *node)
{
	struct v4l2_audio input;
	unsigned i = 0;
	int ret;

	for (;;) {
		memset(&input, 0xff, sizeof(input));
		input.index = i;

		ret = doioctl(node, VIDIOC_ENUMAUDIO, &input);
		if (ret == ENOTTY)
			return ret;
		if (ret == EINVAL)
			break;
		if (ret)
			return fail("could not enumerate audio input %d\n", i);
		if (checkInputAudio(input, i))
			return fail("invalid attributes for audio input %d\n", i);
		node->audio_inputs++;
		i++;
	}
	if (node->audio_inputs && !(node->g_caps() & V4L2_CAP_AUDIO))
		return fail("audio inputs reported, but no CAP_AUDIO set\n");
	return 0;
}

static int checkInputAudioSet(struct node *node, __u32 audioset)
{
	struct v4l2_audio input = { 0 };
	unsigned i;
	int ret;

	ret = doioctl(node, VIDIOC_G_AUDIO, &input);
	if (audioset == 0 && ret != ENOTTY && ret != EINVAL)
		return fail("No audioset, but G_AUDIO did not return ENOTTY or EINVAL\n");
	if (audioset) {
		if (ret)
			return fail("Audio inputs, but G_AUDIO returned an error\n");
		if (input.index >= node->audio_inputs)
			return fail("invalid current audio input %d\n", input.index);
		if (checkInputAudio(input, input.index))
			return fail("invalid attributes for audio input %d\n", input.index);
	}

	for (i = 0; i <= node->audio_inputs; i++) {
		int valid = audioset & (1 << i);

		memset(&input, 0xff, sizeof(input));
		memset(input.reserved, 0, sizeof(input.reserved));
		input.index = i;
		input.mode = 0;
		ret = doioctl(node, VIDIOC_S_AUDIO, &input);
		if (!valid && ret != EINVAL && ret != ENOTTY)
			return fail("can set invalid audio input %d\n", i);
		if (valid && ret)
			return fail("can't set valid audio input %d\n", i);
	}
	return 0;
}

int testInputAudio(struct node *node)
{
	struct v4l2_input vinput = { 0 };
	unsigned i = 0;
	int ret;

	if (node->audio_inputs && node->inputs == 0)
		return fail("audio inputs found but no video inputs?!\n");

	for (i = 0; i < node->inputs; i++) {
		ret = doioctl(node, VIDIOC_S_INPUT, &i);
		if (ret)
			return fail("could not select input %d\n", i);
		vinput.index = i;
		ret = doioctl(node, VIDIOC_ENUMINPUT, &vinput);
		if (ret)
			return fail("could not enumerate input %d\n", i);
		if (checkInputAudioSet(node, vinput.audioset))
			return fail("invalid audioset for input %d\n", i);
	}
	return node->audio_inputs ? 0 : ENOTTY;
}

static int checkModulator(struct node *node, const struct v4l2_modulator &mod, unsigned m)
{
	bool tv = !node->is_radio && !node->is_sdr;

	if (mod.index != m)
		return fail("invalid index\n");
	if (check_ustring(mod.name, sizeof(mod.name)))
		return fail("invalid name\n");
	if (check_0(mod.reserved, sizeof(mod.reserved)))
		return fail("non-zero reserved fields\n");
	if (tv)
		return fail("currently only radio/sdr modulators are supported\n");
	if (node->is_sdr)
		fail_on_test(mod.type != V4L2_TUNER_SDR && mod.type != V4L2_TUNER_RF);
	else if (mod.type != V4L2_TUNER_RADIO)
		return fail("invalid modulator type %d\n", mod.type);

	if (!(mod.capability & (V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_1HZ)))
		return fail("V4L2_TUNER_CAP_LOW/1HZ was not set for a radio modulator\n");
	if (mod.capability & (V4L2_TUNER_CAP_NORM |
					V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2))
		return fail("TV capabilities for radio modulator?\n");
	fail_on_test(!(mod.capability & V4L2_TUNER_CAP_FREQ_BANDS));
	if (mod.rangelow > mod.rangehigh)
		return fail("rangelow > rangehigh\n");
	if (mod.rangelow == 0 || mod.rangehigh == 0xffffffff)
		return fail("invalid rangelow or rangehigh\n");
	if (!(mod.capability & V4L2_TUNER_CAP_STEREO) &&
			(mod.txsubchans & V4L2_TUNER_SUB_STEREO))
		return fail("stereo subchan, but no stereo caps?\n");
	if (!(mod.capability & V4L2_TUNER_CAP_LANG1) &&
			(mod.txsubchans & V4L2_TUNER_SUB_LANG1))
		return fail("lang1 subchan, but no lang1 caps?\n");
	if (!(mod.capability & V4L2_TUNER_CAP_LANG2) &&
			(mod.txsubchans & V4L2_TUNER_SUB_LANG2))
		return fail("lang2 subchan, but no lang2 caps?\n");
	if (!(mod.capability & V4L2_TUNER_CAP_RDS) &&
			(mod.txsubchans & V4L2_TUNER_SUB_RDS))
		return fail("RDS subchan, but no RDS caps?\n");
	bool have_rds = mod.capability & V4L2_TUNER_CAP_RDS;
	bool have_rds_method = mod.capability &
                        (V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_RDS_CONTROLS);
	if (have_rds ^ have_rds_method)
		return fail("V4L2_TUNER_CAP_RDS is set, but not V4L2_TUNER_CAP_RDS_* or vice versa\n");
	if ((mod.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO) &&
			!(node->g_caps() & V4L2_CAP_READWRITE))
		return fail("V4L2_TUNER_CAP_RDS_BLOCK_IO is set, but not V4L2_CAP_READWRITE\n");
	if (!node->is_sdr && !(mod.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO) &&
			(node->g_caps() & V4L2_CAP_READWRITE))
		return fail("V4L2_TUNER_CAP_RDS_BLOCK_IO is not set, but V4L2_CAP_READWRITE is\n");
	return checkEnumFreqBands(node, mod.index, mod.type, mod.capability,
			mod.rangelow, mod.rangehigh);
}

int testModulator(struct node *node)
{
	struct v4l2_modulator mod;
	unsigned m = 0;
	bool has_rds = false;
	int ret;

	for (;;) {
		memset(&mod, 0xff, sizeof(mod));
		memset(mod.reserved, 0, sizeof(mod.reserved));
		mod.index = m;
		ret = doioctl(node, VIDIOC_G_MODULATOR, &mod);
		if (ret == ENOTTY)
			return ret;
		if (ret == EINVAL)
			break;
		if (ret)
			return fail("couldn't get modulator %d\n", m);
		if (checkModulator(node, mod, m))
			return fail("invalid modulator %d\n", m);
		if (doioctl(node, VIDIOC_S_MODULATOR, &mod))
			return fail("cannot set modulator %d\n", m);
		m++;
		node->modulators++;
		if (mod.capability & V4L2_TUNER_CAP_RDS)
			has_rds = true;
	}
	memset(&mod, 0, sizeof(mod));
	mod.index = m;
	if (doioctl(node, VIDIOC_S_MODULATOR, &mod) != EINVAL)
		return fail("could set invalid modulator %d\n", m);
	if (node->modulators && !(node->g_caps() & V4L2_CAP_MODULATOR))
		return fail("modulators found, but no modulator capability set\n");
	if (!node->modulators && (node->g_caps() & V4L2_CAP_MODULATOR))
		return fail("no modulators found, but modulator capability set\n");
	if (has_rds && !(node->g_caps() & V4L2_CAP_RDS_OUTPUT))
		return fail("RDS modulator capability, but no RDS output capability?\n");
	if (!has_rds && (node->g_caps() & V4L2_CAP_RDS_OUTPUT))
		return fail("No RDS modulator capability, but RDS output capability?\n");
	return 0;
}

int testModulatorFreq(struct node *node)
{
	struct v4l2_frequency freq = { 0 };
	unsigned m;
	int ret;

	for (m = 0; m < node->modulators; m++) {
		struct v4l2_modulator modulator;

		modulator.index = m;
		memset(modulator.reserved, 0, sizeof(modulator.reserved));
		ret = doioctl(node, VIDIOC_G_MODULATOR, &modulator);
		if (ret)
			return fail("could not get modulator %d\n", m);
		memset(&freq, 0, sizeof(freq));
		freq.tuner = m;
		ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
		if (ret)
			return fail("could not get frequency for modulator %d\n", m);
		if (check_0(freq.reserved, sizeof(freq.reserved)))
			return fail("reserved was not zeroed\n");
		if (freq.tuner != m)
			return fail("frequency modulator field changed!\n");
		if ((freq.type == V4L2_TUNER_SDR || freq.type == V4L2_TUNER_RF) &&
		    !(node->g_caps() & V4L2_CAP_SDR_OUTPUT))
			return fail("sdr tuner found but no sdr output capability set\n");
		if (freq.frequency == 0)
			return fail("frequency not set\n");
		if (freq.frequency < modulator.rangelow || freq.frequency > modulator.rangehigh)
			warn("returned modulator %d frequency out of range (%d not in [%d...%d])\n",
					m, freq.frequency, modulator.rangelow, modulator.rangehigh);

		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set current frequency\n");
		freq.frequency = modulator.rangelow;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangelow frequency\n");
		freq.frequency = modulator.rangehigh;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangehigh frequency\n");
		freq.frequency = modulator.rangelow - 1;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangelow-1 frequency\n");
		ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
		if (ret || freq.frequency != modulator.rangelow)
			return fail("frequency rangelow-1 wasn't mapped to rangelow\n");
		freq.frequency = modulator.rangehigh + 1;
		ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
		if (ret)
			return fail("could not set rangehigh+1 frequency\n");
		ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
		if (ret || freq.frequency != modulator.rangehigh)
			return fail("frequency rangehigh+1 wasn't mapped to rangehigh\n");
	}

	/* If this is a tuner device, then skip the remaining tests */
	if (node->g_caps() & V4L2_CAP_TUNER)
		return 0;

	freq.tuner = m;
	ret = doioctl(node, VIDIOC_G_FREQUENCY, &freq);
	if (ret != EINVAL && ret != ENOTTY)
		return fail("could get frequency for invalid modulator %d\n", m);
	freq.tuner = m;
	// Radio: 100 MHz
	freq.frequency = 1600000;
	ret = doioctl(node, VIDIOC_S_FREQUENCY, &freq);
	if (ret != EINVAL && ret != ENOTTY)
		return fail("could set frequency for invalid modulator %d\n", m);
	return node->modulators ? 0 : ENOTTY;
}

static int checkOutput(struct node *node, const struct v4l2_output &descr, unsigned o)
{
	__u32 mask = (1 << node->audio_outputs) - 1;
	struct v4l2_selection sel;

	if (descr.index != o)
		return fail("invalid index\n");
	if (check_ustring(descr.name, sizeof(descr.name)))
		return fail("invalid name\n");
	if (descr.type != V4L2_OUTPUT_TYPE_MODULATOR && descr.type != V4L2_OUTPUT_TYPE_ANALOG &&
	    descr.type != V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY)
		return fail("invalid type\n");
	if (descr.type != V4L2_OUTPUT_TYPE_MODULATOR && descr.modulator)
		return fail("invalid modulator\n");
	if (descr.type == V4L2_OUTPUT_TYPE_MODULATOR && node->modulators == 0)
		return fail("no modulators found for modulator output\n");
	if (!(descr.capabilities & V4L2_OUT_CAP_STD) && descr.std)
		return fail("invalid std\n");
	if ((descr.capabilities & V4L2_OUT_CAP_STD) && !descr.std)
		return fail("std == 0\n");
	memset(&sel, 0, sizeof(sel));
	sel.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	sel.target = V4L2_SEL_TGT_NATIVE_SIZE;
	if (descr.capabilities & V4L2_OUT_CAP_NATIVE_SIZE) {
		fail_on_test(doioctl(node, VIDIOC_G_SELECTION, &sel));
		fail_on_test(doioctl(node, VIDIOC_S_SELECTION, &sel));
	} else if (!doioctl(node, VIDIOC_G_SELECTION, &sel)) {
		fail_on_test(!doioctl(node, VIDIOC_S_SELECTION, &sel));
	}
	if (descr.capabilities & ~0x7)
		return fail("invalid capabilities\n");
	if (check_0(descr.reserved, sizeof(descr.reserved)))
		return fail("non-zero reserved fields\n");
	if (descr.audioset & ~mask)
		return fail("invalid audioset\n");
	if (descr.modulator && descr.modulator >= node->modulators)
		return fail("invalid modulator\n");
	return 0;
}

int testOutput(struct node *node)
{
	struct v4l2_output descr;
	int cur_output = MAGIC;
	int output;
	int ret = doioctl(node, VIDIOC_G_OUTPUT, &cur_output);
	int o = 0;

	if (ret == ENOTTY) {
		if (node->has_outputs) {
			if (media_fd < 0)
				return fail("G_OUTPUT not supported for an output device\n");
			node->has_outputs = false;
			return ENOTTY;
		}
		descr.index = 0;
		ret = doioctl(node, VIDIOC_ENUMOUTPUT, &descr);
		if (ret != ENOTTY)
			return fail("G_OUTPUT not supported, but ENUMOUTPUT is\n");
		output = 0;
		ret = doioctl(node, VIDIOC_S_OUTPUT, &output);
		if (ret != ENOTTY)
			return fail("G_OUTPUT not supported, but S_OUTPUT is\n");
	}
	if (ret)
		return ret;
	if (cur_output == MAGIC)
		return fail("VIDIOC_G_OUTPUT didn't fill in the output\n");
	for (;;) {
		memset(&descr, 0xff, sizeof(descr));
		descr.index = o;
		ret = doioctl(node, VIDIOC_ENUMOUTPUT, &descr);
		if (ret)
			break;
		output = o;
		if (doioctl(node, VIDIOC_S_OUTPUT, &output))
			return fail("could not set output to %d\n", o);
		if (output != o)
			return fail("output set to %d, but becomes %d?!\n", o, output);
		if (checkOutput(node, descr, o))
			return fail("invalid attributes for output %d\n", o);
		node->outputs++;
		o++;
	}
	output = o;
	if (doioctl(node, VIDIOC_S_OUTPUT, &output) != EINVAL)
		return fail("could set output to invalid output %d\n", o);
	if (doioctl(node, VIDIOC_S_OUTPUT, &cur_output))
		return fail("couldn't set output to the original output %d\n", cur_output);
	if (node->outputs && !node->has_outputs)
		return fail("outputs found, but no output capabilities set\n");
	if (!node->outputs && node->has_outputs)
		return fail("no outputs found, but output capabilities set\n");
	fail_on_test(node->is_m2m && node->outputs > 1);
	if (node->is_io_mc) {
		fail_on_test(!node->is_video && !node->is_meta);
		fail_on_test(node->outputs != 1);
	}
	return 0;
}

static int checkOutputAudio(const struct v4l2_audioout &descr, unsigned o)
{
	if (descr.index != o)
		return fail("invalid index\n");
	if (check_ustring(descr.name, sizeof(descr.name)))
		return fail("invalid name\n");
	if (descr.capability)
		return fail("invalid capabilities\n");
	if (descr.mode)
		return fail("invalid mode\n");
	if (check_0(descr.reserved, sizeof(descr.reserved)))
		return fail("non-zero reserved fields\n");
	return 0;
}

int testEnumOutputAudio(struct node *node)
{
	struct v4l2_audioout output;
	unsigned o = 0;
	int ret;

	for (;;) {
		memset(&output, 0xff, sizeof(output));
		output.index = o;

		ret = doioctl(node, VIDIOC_ENUMAUDOUT, &output);
		if (ret == ENOTTY)
			return ENOTTY;
		if (ret == EINVAL)
			break;
		if (ret)
			return fail("could not enumerate audio output %d\n", o);
		if (checkOutputAudio(output, o))
			return fail("invalid attributes for audio output %d\n", o);
		node->audio_outputs++;
		o++;
	}

	if (node->audio_outputs && !(node->g_caps() & V4L2_CAP_AUDIO))
		return fail("audio outputs reported, but no CAP_AUDIO set\n");
	return 0;
}

static int checkOutputAudioSet(struct node *node, __u32 audioset)
{
	struct v4l2_audioout output;
	unsigned i;
	int ret;

	memset(output.reserved, 0, sizeof(output.reserved));
	ret = doioctl(node, VIDIOC_G_AUDOUT, &output);
	if (audioset == 0 && ret != EINVAL && ret != ENOTTY)
		return fail("No audio outputs, but G_AUDOUT did not return EINVAL or ENOTTY\n");
	if (audioset) {
		if (ret)
			return fail("Audio outputs, but G_AUDOUT returned an error\n");
		if (output.index >= node->audio_outputs)
			return fail("invalid current audio output %d\n", output.index);
		if (checkOutputAudio(output, output.index))
			return fail("invalid attributes for audio output %d\n", output.index);
	}

	for (i = 0; i <= node->audio_outputs; i++) {
		int valid = audioset & (1 << i);

		memset(&output, 0xff, sizeof(output));
		memset(output.reserved, 0, sizeof(output.reserved));
		output.index = i;
		output.mode = 0;
		ret = doioctl(node, VIDIOC_S_AUDOUT, &output);
		if (!valid && ret != EINVAL && ret != ENOTTY)
			return fail("can set invalid audio output %d\n", i);
		if (valid && ret)
			return fail("can't set valid audio output %d\n", i);
	}
	return 0;
}

int testOutputAudio(struct node *node)
{
	struct v4l2_output voutput;
	unsigned o = 0;
	int ret;

	if (node->audio_outputs && node->outputs == 0)
		return fail("audio outputs found but no video outputs?!\n");

	for (o = 0; o < node->outputs; o++) {
		ret = doioctl(node, VIDIOC_S_OUTPUT, &o);
		if (ret)
			return fail("could not select output %d\n", o);
		voutput.index = o;
		ret = doioctl(node, VIDIOC_ENUMOUTPUT, &voutput);
		if (ret)
			return fail("could not enumerate output %d\n", o);
		if (checkOutputAudioSet(node, voutput.audioset))
			return fail("invalid audioset for output %d\n", o);
	}

	if (node->audio_outputs == 0 && node->audio_inputs == 0 && (node->g_caps() & V4L2_CAP_AUDIO))
		return fail("no audio inputs or outputs reported, but CAP_AUDIO set\n");
	return node->audio_outputs ? 0 : ENOTTY;
}
