blob: 006e05ec3971ede2f2a4356448718e4fbfb9d316 [file] [log] [blame]
/*
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;
}