/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * CEC API compliance test tool.
 *
 * Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 */

#ifndef _CEC_COMPLIANCE_H_
#define _CEC_COMPLIANCE_H_

#include <linux/cec-funcs.h>
#include "cec-htng-funcs.h"

#include <cec-info.h>

#include <vector>

#define TAG_AUDIO_RATE_CONTROL		1
#define TAG_ARC_CONTROL 		(1 << 1)
#define TAG_CAP_DISCOVERY_CONTROL 	(1 << 2)
#define TAG_DECK_CONTROL 		(1 << 3)
#define TAG_DEVICE_MENU_CONTROL 	(1 << 4)
#define TAG_DEVICE_OSD_TRANSFER 	(1 << 5)
#define TAG_DYNAMIC_AUTO_LIPSYNC 	(1 << 6)
#define TAG_OSD_DISPLAY 		(1 << 7)
#define TAG_ONE_TOUCH_PLAY 		(1 << 8)
#define TAG_ONE_TOUCH_RECORD 		(1 << 9)
#define TAG_POWER_STATUS 		(1 << 10)
#define TAG_REMOTE_CONTROL_PASSTHROUGH	(1 << 11)
#define TAG_ROUTING_CONTROL 		(1 << 12)
#define TAG_SYSTEM_AUDIO_CONTROL 	(1 << 13)
#define TAG_SYSTEM_INFORMATION		(1 << 14)
#define TAG_TIMER_PROGRAMMING 		(1 << 15)
#define TAG_TUNER_CONTROL 		(1 << 16)
#define TAG_VENDOR_SPECIFIC_COMMANDS 	(1 << 17)
#define TAG_STANDBY_RESUME		(1 << 18)

#define TAG_INTERACTIVE		(1 << 19)
#define TAG_CORE 0
#define TAG_ALL (~TAG_INTERACTIVE)

#define NUM_TAGS 21

struct short_audio_desc {
	/* Byte 1 */
	__u8 num_channels;
	__u8 format_code;

	/* Byte 2 */
	__u8 sample_freq_mask;

	/* Byte 3 */
	union {
		__u8 bit_depth_mask;    // LPCM
		__u8 max_bitrate;       // Format codes 2-8
		__u8 format_dependent;  // Format codes 9-13
		__u8 wma_profile;       // WMA Pro
		__u8 frame_length_mask; // Extension type codes 4-6, 8-10
	};
	__u8 mps;                       // Format codes 8-10
	__u8 extension_type_code;
};

#define SAD_FMT_CODE_LPCM 		1
#define SAD_FMT_CODE_AC3		2
#define SAD_FMT_CODE_MPEG1		3
#define SAD_FMT_CODE_MP3 		4
#define SAD_FMT_CODE_MPEG2 		5
#define SAD_FMT_CODE_AAC_LC 		6
#define SAD_FMT_CODE_DTS 		7
#define SAD_FMT_CODE_ATRAC 		8
#define SAD_FMT_CODE_ONE_BIT_AUDIO	9
#define SAD_FMT_CODE_ENHANCED_AC3	10
#define SAD_FMT_CODE_DTS_HD 		11
#define SAD_FMT_CODE_MAT 		12
#define SAD_FMT_CODE_DST 		13
#define SAD_FMT_CODE_WMA_PRO 		14
#define SAD_FMT_CODE_EXTENDED 		15

#define SAD_BIT_DEPTH_MASK_16 		1
#define SAD_BIT_DEPTH_MASK_20 		(1 << 1)
#define SAD_BIT_DEPTH_MASK_24 		(1 << 2)

#define SAD_SAMPLE_FREQ_MASK_32 	1
#define SAD_SAMPLE_FREQ_MASK_44_1 	(1 << 1)
#define SAD_SAMPLE_FREQ_MASK_48 	(1 << 2)
#define SAD_SAMPLE_FREQ_MASK_88_2 	(1 << 3)
#define SAD_SAMPLE_FREQ_MASK_96 	(1 << 4)
#define SAD_SAMPLE_FREQ_MASK_176_4 	(1 << 5)
#define SAD_SAMPLE_FREQ_MASK_192 	(1 << 6)

#define SAD_FRAME_LENGTH_MASK_960 	1
#define SAD_FRAME_LENGTH_MASK_1024	(1 << 1)

#define SAD_EXT_TYPE_MPEG4_HE_AAC 		4
#define SAD_EXT_TYPE_MPEG4_HE_AACv2 		5
#define SAD_EXT_TYPE_MPEG4_AAC_LC 		6
#define SAD_EXT_TYPE_DRA 			7
#define SAD_EXT_TYPE_MPEG4_HE_AAC_SURROUND 	8
#define SAD_EXT_TYPE_MPEG4_AAC_LC_SURROUND	10
#define SAD_EXT_TYPE_MPEG_H_3D_AUDIO		11
#define SAD_EXT_TYPE_AC_4			12
#define SAD_EXT_TYPE_LPCM_3D_AUDIO		13

extern bool show_info;
extern bool show_colors;
extern bool show_warnings;
extern bool exit_on_fail;
extern bool exit_on_warn;
extern unsigned warnings;
extern unsigned reply_threshold;
extern time_t long_timeout;

struct remote {
	bool recognized_op[256];
	bool unrecognized_op[256];
	unsigned prim_type;
	__u16 phys_addr;
	char osd_name[15];
	char language[4];
	__u8 cec_version;
	__u8 rc_profile;
	__u8 dev_features;
	__u8 all_device_types;
	__u32 vendor_id;
	bool has_osd;
	bool has_power_status;
	bool has_image_view_on;
	bool has_text_view_on;
	bool in_standby;
	bool has_remote_control_passthrough;
	bool source_has_arc_rx;
	bool sink_has_arc_tx;
	bool arc_initiated;
	bool has_sys_audio_mode_req;
	bool has_set_sys_audio_mode;
	bool has_sad;
	__u8 supp_format_id;
	__u8 supp_format_code;
	__u8 volume;
	__u8 mute;
	bool has_aud_rate;
	bool has_deck_ctl;
	bool has_analogue_timer;
	__u8 bcast_sys;
	__u8 dig_bcast_sys;
	bool has_rec_tv;
	bool has_cdc;
};

struct node {
	int fd;
	const char *device;
	bool has_cec20;
	unsigned caps;
	unsigned available_log_addrs;
	unsigned num_log_addrs;
	unsigned adap_la_mask;
	__u8 log_addr[CEC_MAX_LOG_ADDRS];
	unsigned remote_la_mask;
	struct remote remote[16];
	__u16 phys_addr;
	bool in_standby;
	__u8 prim_devtype;
	time_t current_time;
};

struct remote_subtest {
	const char *name;
	const __u16 la_mask;
	int (*const test_fn)(struct node *node, unsigned me, unsigned la, bool interactive);
	bool in_standby;
	bool for_cec20;
};

using vec_remote_subtests = std::vector<remote_subtest>;

#ifndef __FILE_NAME__
#define __FILE_NAME__ __FILE__
#endif

#define OK			0
#define FAIL			1
#define OK_PRESUMED		2
#define OK_NOT_SUPPORTED	3
#define OK_REFUSED		4
#define OK_UNEXPECTED		5
#define OK_EXPECTED_FAIL	6
#define NOTAPPLICABLE		7
#define FAIL_CRITICAL		8
#define DONT_CARE		255

#define CEC_LOG_ADDR_MASK_ALL 0xffff

#define COLOR_GREEN(s) "\033[32m" s "\033[0m"
#define COLOR_RED(s) "\033[1;31m" s "\033[0m"
#define COLOR_BOLD(s) "\033[1m" s "\033[0m"

#define info(fmt, args...) 						\
	do {								\
		if (show_info)						\
			printf("\t\tinfo: " fmt, ##args);		\
	} while (0)

#define announce(fmt, args...) 						\
	do {								\
		printf("\t\t>>> " fmt "\n", ##args);			\
	} while (0)

#define interactive_info(block, fmt, args...)				\
	do {								\
		if (interactive) {					\
			printf("\t\t>>> " fmt "\n", ##args);		\
			if (block) {					\
				printf("\t\t>>> Press ENTER to proceed.\n"); \
				getchar();				\
			}						\
		}							\
	} while(0)

#define warn(fmt, args...) 						\
({									\
	warnings++;							\
	if (show_warnings)						\
		printf("\t\%s: %s(%d): " fmt,				\
		       show_colors ? COLOR_BOLD("warn") : "warn",	\
		       __FILE_NAME__, __LINE__, ##args);			\
	if (exit_on_warn)						\
		std::exit(EXIT_FAILURE);				\
	0;								\
})

#define warn_once(fmt, args...)						\
	do {								\
		static bool show;					\
									\
		if (!show) {						\
			show = true;					\
			warn(fmt, ##args);				\
		}							\
	} while (0)

#define warn_on_test(test) 						\
	do {								\
		if (test)						\
			warn("%s\n", #test);				\
	} while (0)

#define warn_once_on_test(test) 					\
	do {								\
		if (test)						\
			warn_once("%s\n", #test);			\
	} while (0)

#define fail(fmt, args...) 						\
({ 									\
	printf("\t\t%s: %s(%d): " fmt, show_colors ?			\
	       COLOR_RED("fail") : "fail", __FILE_NAME__, __LINE__, ##args);	\
	if (exit_on_fail)						\
		std::exit(EXIT_FAILURE);				\
	FAIL;								\
})

#define fail_once(fmt, args...)						\
	do {								\
		static bool show;					\
									\
		if (!show) {						\
			show = true;					\
			fail(fmt, ##args);				\
		}							\
	} while (0)

#define fail_or_warn(node, fmt, args...)				\
({									\
 	if ((node)->in_standby)						\
		warn(fmt, ##args);					\
	else								\
		fail(fmt, ##args);					\
	(node)->in_standby ? 0 : FAIL;					\
})

#define fail_on_test(test) 						\
	do {								\
		if (test)						\
			return fail("%s\n", #test);			\
	} while (0)

#define fail_on_test_v2(version, test) fail_on_test(version >= CEC_OP_CEC_VERSION_2_0 && (test))

#define fail_on_test_v2_warn(version, test)				\
	do {								\
		if (test) {						\
			if (version >= CEC_OP_CEC_VERSION_2_0)		\
				return fail("%s\n", #test);		\
			else						\
				warn("fails in CEC 2.0: %s\n", #test);	\
		}							\
	} while(0)

static inline char get_yn()
{
	char c;

	while ((c = tolower(getchar())) != 'y' && c != 'n');
	getchar();
	return c;
}

static inline bool question(const char* prompt)
{
	printf("\t\t>>> %s (y/n) ", prompt);
	return get_yn() == 'y';
}

int cec_named_ioctl(struct node *node, const char *name,
		    unsigned long int request, void *parm);

#define doioctl(n, r, p) cec_named_ioctl(n, #r, r, p)

const char *opcode2s(__u8 opcode);
std::string opcode2s(const struct cec_msg *msg);

static inline bool is_tv(unsigned la, unsigned prim_type)
{
	return cec_has_tv(1 << la) ||
		(cec_has_specific(1 << la) && prim_type == CEC_OP_PRIM_DEVTYPE_TV);
}

static inline bool is_playback_or_rec(unsigned la)
{
	return cec_has_playback(1 << la) || cec_has_record(1 << la);
}

static inline bool cec_msg_status_is_abort(const struct cec_msg *msg)
{
	return msg->rx_status & CEC_RX_STATUS_FEATURE_ABORT;
}

static inline __u8 abort_reason(const struct cec_msg *msg)
{
	return msg->msg[3];
}

static inline bool unrecognized_op(const struct cec_msg *msg)
{
	if (!cec_msg_status_is_abort(msg))
		return false;
	if (abort_reason(msg) == CEC_OP_ABORT_UNRECOGNIZED_OP)
		return true;
	if (abort_reason(msg) == CEC_OP_ABORT_UNDETERMINED) {
		warn("Opcode %x was undetermined and is treated as not supported.\n", msg->msg[2]);
		return true;
	}
	return false;
}

static inline bool refused(const struct cec_msg *msg)
{
	return cec_msg_status_is_abort(msg) && abort_reason(msg) == CEC_OP_ABORT_REFUSED;
}

static inline bool incorrect_mode(const struct cec_msg *msg)
{
	return cec_msg_status_is_abort(msg) && abort_reason(msg) == CEC_OP_ABORT_INCORRECT_MODE;
}

static inline bool timed_out(const struct cec_msg *msg)
{
	return msg->rx_status & CEC_RX_STATUS_TIMEOUT;
}

static inline bool timed_out_or_abort(const struct cec_msg *msg)
{
	return timed_out(msg) || cec_msg_status_is_abort(msg);
}

static inline unsigned response_time_ms(const struct cec_msg *msg)
{
	unsigned ms = (msg->rx_ts - msg->tx_ts) / 1000000;

	// Compensate for the time it took (approx.) to receive the
	// message.
	if (ms >= msg->len * 24)
		return ms - msg->len * 24;
	return 0;
}

bool transmit_timeout(struct node *node, struct cec_msg *msg,
		      unsigned timeout = 2000);

static inline bool transmit(struct node *node, struct cec_msg *msg)
{
	return transmit_timeout(node, msg, 0);
}

static inline void mode_set_follower(struct node *node)
{
	__u32 mode = CEC_MODE_INITIATOR | CEC_MODE_FOLLOWER;

	doioctl(node, CEC_S_MODE, &mode);
}

static inline void mode_set_initiator(struct node *node)
{
	__u32 mode = CEC_MODE_INITIATOR;

	doioctl(node, CEC_S_MODE, &mode);
}

static inline unsigned get_ts_ms()
{
	struct timespec timespec;

	clock_gettime(CLOCK_MONOTONIC, &timespec);
	return timespec.tv_sec * 1000ull + timespec.tv_nsec / 1000000;
}

const char *result_name(int res, bool show_colors);
const char *ok(int res);
const char *power_status2s(__u8 power_status);
const char *bcast_system2s(__u8 bcast_system);
const char *dig_bcast_system2s(__u8 bcast_system);
int check_0(const void *p, int len);
int util_receive(struct node *node, unsigned la, unsigned timeout,
		 struct cec_msg *msg, __u8 sent_msg,
		 __u8 reply1, __u8 reply2 = 0);
std::string safename(const char *name);

// CEC adapter tests
void testAdapter(struct node &node, struct cec_log_addrs &laddrs,
		 const char *device);

// CEC fuzzing test
int testFuzzing(struct node &node, unsigned me, unsigned la);

// CEC core tests
int testCore(struct node *node);
int core_unknown(struct node *node, unsigned me, unsigned la, bool interactive);
int core_abort(struct node *node, unsigned me, unsigned la, bool interactive);
int system_info_polling(struct node *node, unsigned me, unsigned la, bool interactive);
int system_info_phys_addr(struct node *node, unsigned me, unsigned la, bool interactive);
int system_info_version(struct node *node, unsigned me, unsigned la, bool interactive);
int system_info_get_menu_lang(struct node *node, unsigned me, unsigned la, bool interactive);
int system_info_give_features(struct node *node, unsigned me, unsigned la, bool interactive);
int vendor_specific_commands_id(struct node *node, unsigned me, unsigned la, bool interactive);
int device_osd_transfer_give(struct node *node, unsigned me, unsigned la, bool interactive);

// CEC processing
int testProcessing(struct node *node, unsigned me);

// CEC testing
void collectTests(void);
void listTests(void);
int setExpectedResult(char *optarg, bool no_warnings);
void testRemote(struct node *node, unsigned me, unsigned la, unsigned test_tags,
			     bool interactive);

// cec-test-tuner-record-timer.cpp
extern const vec_remote_subtests tuner_ctl_subtests;
extern const vec_remote_subtests one_touch_rec_subtests;
extern const vec_remote_subtests timer_prog_subtests;

// cec-test-audio.cpp
extern const vec_remote_subtests sac_subtests;
extern const vec_remote_subtests dal_subtests;
extern const vec_remote_subtests arc_subtests;
extern const vec_remote_subtests audio_rate_ctl_subtests;

// cec-test-power.cpp
bool util_interactive_ensure_power_state(struct node *node, unsigned me, unsigned la, bool interactive,
					 __u8 target_pwr);
int standby_resume_wakeup(struct node *node, unsigned me, unsigned la, bool interactive);
extern const vec_remote_subtests standby_subtests;
extern const vec_remote_subtests one_touch_play_subtests;
extern const vec_remote_subtests power_status_subtests;
extern const vec_remote_subtests standby_resume_subtests;

#endif
