| /* |
| 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; |
| } |