/*
 * Copyright (c) 2020-2021 The strace developers.
 * All rights reserved.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "defs.h"

#include <linux/gpio.h>

static int
print_gpiochip_info(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpiochip_info info;

	if (entering(tcp))
		return 0;

	tprint_arg_next();
	if (umove_or_printaddr(tcp, arg, &info))
		return RVAL_IOCTL_DECODED;

	tprint_struct_begin();
	PRINT_FIELD_CSTRING(info, name);
	tprint_struct_next();
	PRINT_FIELD_CSTRING(info, label);
	tprint_struct_next();
	PRINT_FIELD_U(info, lines);
	tprint_struct_end();

	return RVAL_IOCTL_DECODED;
}

#include "xlat/gpio_line_flags.h"

static int
print_gpioline_info(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpioline_info info;

	if (entering(tcp))
		tprint_arg_next();
	else if (syserror(tcp))
		return RVAL_IOCTL_DECODED;
	else
		tprint_value_changed();

	if (umove_or_printaddr(tcp, arg, &info))
		return RVAL_IOCTL_DECODED;

	if (entering(tcp)) {
		tprint_struct_begin();
		PRINT_FIELD_U(info, line_offset);
		tprint_struct_end();
		return 0;
	}

	/* exiting */
	tprint_struct_begin();
	PRINT_FIELD_FLAGS(info, flags, gpio_line_flags, "GPIOLINE_FLAG_???");
	tprint_struct_next();
	PRINT_FIELD_CSTRING(info, name);
	tprint_struct_next();
	PRINT_FIELD_CSTRING(info, consumer);
	tprint_struct_end();

	return RVAL_IOCTL_DECODED;
}

static int
print_gpioline_info_unwatch(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct { uint32_t offset; } data;

	tprint_arg_next();
	if (!umove_or_printaddr(tcp, arg, &data)) {
		tprint_struct_begin();
		PRINT_FIELD_U(data, offset);
		tprint_struct_end();
	}

	return RVAL_IOCTL_DECODED;
}

#include "xlat/gpio_handle_flags.h"

static int
print_gpiohandle_request(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpiohandle_request hr;

	if (entering(tcp))
		tprint_arg_next();
	else if (syserror(tcp))
		return RVAL_IOCTL_DECODED;
	else
		tprint_value_changed();

	if (umove_or_printaddr(tcp, arg, &hr))
		return RVAL_IOCTL_DECODED;

	if (exiting(tcp)) {
		tprint_struct_begin();
		PRINT_FIELD_FD(hr, fd, tcp);
		tprint_struct_end();
		return RVAL_IOCTL_DECODED;
	}

	/* entering */
	tprint_struct_begin();
	PRINT_FIELD_U(hr, lines);
	tprint_struct_next();
	PRINT_FIELD_ARRAY_UPTO(hr, lineoffsets, hr.lines, tcp,
			       print_uint_array_member);
	tprint_struct_next();
	PRINT_FIELD_FLAGS(hr, flags, gpio_handle_flags,
			  "GPIOHANDLE_REQUEST_???");
	tprint_struct_next();
	PRINT_FIELD_ARRAY_UPTO(hr, default_values, hr.lines, tcp,
			       print_uint_array_member);
	tprint_struct_next();
	PRINT_FIELD_CSTRING(hr, consumer_label);
	tprint_struct_end();
	return 0;
}

#include "xlat/gpio_event_flags.h"

static int
print_gpioevent_request(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpioevent_request er;

	if (entering(tcp))
		tprint_arg_next();
	else if (syserror(tcp))
		return RVAL_IOCTL_DECODED;
	else
		tprint_value_changed();

	if (umove_or_printaddr(tcp, arg, &er))
		return RVAL_IOCTL_DECODED;

	if (exiting(tcp)) {
		tprint_struct_begin();
		PRINT_FIELD_FD(er, fd, tcp);
		tprint_struct_end();
		return RVAL_IOCTL_DECODED;
	}

	/* entering */
	tprint_struct_begin();
	PRINT_FIELD_U(er, lineoffset);
	tprint_struct_next();
	PRINT_FIELD_FLAGS(er, handleflags, gpio_handle_flags,
			  "GPIOHANDLE_REQUEST_???");
	tprint_struct_next();
	PRINT_FIELD_FLAGS(er, eventflags, gpio_event_flags,
			  "GPIOEVENT_REQUEST_???");
	tprint_struct_next();
	PRINT_FIELD_CSTRING(er, consumer_label);
	tprint_struct_end();
	return 0;
}

static void
print_gpiohandle_data(struct tcb *const tcp, const struct gpiohandle_data *vals)
{
	tprint_struct_begin();
	PRINT_FIELD_ARRAY(*vals, values, tcp, print_uint_array_member);
	tprint_struct_end();
}

static int
print_gpiohandle_get_values(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpiohandle_data vals;

	if (entering(tcp))
		return 0;

	/* exiting */
	tprint_arg_next();
	if (!umove_or_printaddr(tcp, arg, &vals))
		print_gpiohandle_data(tcp, &vals);

	return RVAL_IOCTL_DECODED;
}

static int
print_gpiohandle_set_values(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpiohandle_data vals;

	tprint_arg_next();
	if (!umove_or_printaddr(tcp, arg, &vals))
		print_gpiohandle_data(tcp, &vals);

	return RVAL_IOCTL_DECODED;
}

static int
print_gpiohandle_set_config(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpiohandle_config hc;

	tprint_arg_next();
	if (umove_or_printaddr(tcp, arg, &hc))
		return RVAL_IOCTL_DECODED;

	tprint_struct_begin();
	PRINT_FIELD_FLAGS(hc, flags, gpio_handle_flags, "GPIOHANDLE_REQUEST_???");
	tprint_struct_next();
	PRINT_FIELD_ARRAY(hc, default_values, tcp, print_uint_array_member);
	tprint_struct_end();

	return RVAL_IOCTL_DECODED;
}

#include "xlat/gpio_v2_line_flags.h"
#include "xlat/gpio_v2_line_attr_ids.h"

static void
print_gpio_v2_line_attribute_raw(const struct gpio_v2_line_attribute *attr,
				 bool as_field)
{
	if (as_field) {
		tprints_field_name("attr");
		tprint_struct_begin();
	}
	PRINT_FIELD_U(*attr, id);
	if (attr->padding) {
		tprint_struct_next();
		PRINT_FIELD_X(*attr, padding);
	}
	tprint_struct_next();
	tprints_field_name("data");
	PRINT_VAL_X(attr->values);
	if (as_field)
		tprint_struct_end();
}

static void
print_gpio_v2_line_attribute(const struct gpio_v2_line_attribute *attr,
			     bool as_field)
{
	if (attr->padding) {
		/* unexpected padding usage so decode fields raw */
		print_gpio_v2_line_attribute_raw(attr, as_field);
		return;
	}
	switch (attr->id) {
	case GPIO_V2_LINE_ATTR_ID_FLAGS:
		PRINT_FIELD_FLAGS(*attr, flags, gpio_v2_line_flags,
				  "GPIO_V2_LINE_FLAG_???");
		break;
	case GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES:
		PRINT_FIELD_X(*attr, values);
		break;
	case GPIO_V2_LINE_ATTR_ID_DEBOUNCE:
		PRINT_FIELD_U(*attr, debounce_period_us);
		break;
	default:
		/* unknown id so decode fields raw */
		print_gpio_v2_line_attribute_raw(attr, as_field);
		break;
	}
}

static void
print_gpio_v2_line_config_attribute(const struct gpio_v2_line_config_attribute *attr)
{
	tprint_struct_begin();
	print_gpio_v2_line_attribute(&attr->attr, true);
	tprint_struct_next();
	PRINT_FIELD_X(*attr, mask);
	tprint_struct_end();
}

static bool
print_gpio_v2_line_attr_array_member(struct tcb *tcp, void *elem_buf,
				     size_t elem_size, void *data)
{
	tprint_struct_begin();
	print_gpio_v2_line_attribute(elem_buf, false);
	tprint_struct_end();

	return true;
}

static bool
print_gpio_v2_line_config_attr_array_member(struct tcb *tcp, void *elem_buf,
					    size_t elem_size, void *data)
{
	print_gpio_v2_line_config_attribute(elem_buf);

	return true;
}

static void
print_gpio_v2_line_config(struct tcb *const tcp,
			  const struct gpio_v2_line_config *lc)
{
	tprint_struct_begin();
	PRINT_FIELD_FLAGS(*lc, flags, gpio_v2_line_flags,
			  "GPIO_V2_LINE_FLAG_???");
	tprint_struct_next();
	PRINT_FIELD_U(*lc, num_attrs);
	if (!IS_ARRAY_ZERO(lc->padding)) {
		tprint_struct_next();
		PRINT_FIELD_X_ARRAY(*lc, padding);
	}
	if (lc->num_attrs) {
		tprint_struct_next();
		PRINT_FIELD_ARRAY_UPTO(*lc, attrs, lc->num_attrs, tcp,
				       print_gpio_v2_line_config_attr_array_member);
	}
	tprint_struct_end();
}

static int
print_gpio_v2_line_info(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpio_v2_line_info li;

	if (entering(tcp))
		tprint_arg_next();
	else if (syserror(tcp))
		return RVAL_IOCTL_DECODED;
	else
		tprint_value_changed();

	if (umove_or_printaddr(tcp, arg, &li))
		return RVAL_IOCTL_DECODED;

	if (entering(tcp)) {
		tprint_struct_begin();
		PRINT_FIELD_U(li, offset);
		tprint_struct_end();
		return 0;
	}

	/* exiting */
	tprint_struct_begin();
	PRINT_FIELD_CSTRING(li, name);
	tprint_struct_next();
	PRINT_FIELD_CSTRING(li, consumer);
	tprint_struct_next();
	PRINT_FIELD_FLAGS(li, flags, gpio_v2_line_flags, "GPIO_V2_LINE_FLAG_???");
	tprint_struct_next();
	PRINT_FIELD_U(li, num_attrs);
	if (li.num_attrs) {
		tprint_struct_next();
		PRINT_FIELD_ARRAY_UPTO(li, attrs, li.num_attrs, tcp,
				       print_gpio_v2_line_attr_array_member);
	}
	if (!IS_ARRAY_ZERO(li.padding)) {
		tprint_struct_next();
		PRINT_FIELD_X_ARRAY(li, padding);
	}
	tprint_struct_end();

	return RVAL_IOCTL_DECODED;
}

static int
print_gpio_v2_line_request(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpio_v2_line_request lr;

	if (entering(tcp))
		tprint_arg_next();
	else if (syserror(tcp))
		return RVAL_IOCTL_DECODED;
	else
		tprint_value_changed();

	if (umove_or_printaddr(tcp, arg, &lr))
		return RVAL_IOCTL_DECODED;

	if (exiting(tcp)) {
		tprint_struct_begin();
		PRINT_FIELD_FD(lr, fd, tcp);
		tprint_struct_end();
		return RVAL_IOCTL_DECODED;
	}

	/* entering */
	tprint_struct_begin();
	PRINT_FIELD_U(lr, num_lines);
	tprint_struct_next();
	PRINT_FIELD_ARRAY_UPTO(lr, offsets, lr.num_lines, tcp,
			       print_uint_array_member);
	tprint_struct_next();
	PRINT_FIELD_OBJ_TCB_PTR(lr, config, tcp,
				print_gpio_v2_line_config);
	tprint_struct_next();
	PRINT_FIELD_CSTRING(lr, consumer);
	if (lr.event_buffer_size) {
		tprint_struct_next();
		PRINT_FIELD_U(lr, event_buffer_size);
	}
	if (!IS_ARRAY_ZERO(lr.padding)) {
		tprint_struct_next();
		PRINT_FIELD_X_ARRAY(lr, padding);
	}
	tprint_struct_end();
	return 0;
}

static int
print_gpio_v2_line_get_values(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpio_v2_line_values vals;

	if (entering(tcp))
		tprint_arg_next();
	else if (syserror(tcp))
		return RVAL_IOCTL_DECODED;
	else
		tprint_value_changed();

	if (umove_or_printaddr(tcp, arg, &vals))
		return RVAL_IOCTL_DECODED;

	if (entering(tcp)) {
		tprint_struct_begin();
		PRINT_FIELD_X(vals, mask);
		tprint_struct_end();
		return 0;
	}

	/* exiting */
	tprint_struct_begin();
	PRINT_FIELD_X(vals, bits);
	tprint_struct_end();

	return RVAL_IOCTL_DECODED;
}

static int
print_gpio_v2_line_set_values(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpio_v2_line_values vals;

	tprint_arg_next();
	if (!umove_or_printaddr(tcp, arg, &vals)) {
		tprint_struct_begin();
		PRINT_FIELD_X(vals, bits);
		tprint_struct_next();
		PRINT_FIELD_X(vals, mask);
		tprint_struct_end();
	}

	return RVAL_IOCTL_DECODED;
}

static int
print_gpio_v2_line_set_config(struct tcb *const tcp, const kernel_ulong_t arg)
{
	struct gpio_v2_line_config lc;

	tprint_arg_next();
	if (!umove_or_printaddr(tcp, arg, &lc))
		print_gpio_v2_line_config(tcp, &lc);

	return RVAL_IOCTL_DECODED;
}

int
gpio_ioctl(struct tcb *const tcp, const unsigned int code,
	   const kernel_ulong_t arg)
{
	switch (code) {
	case GPIO_GET_CHIPINFO_IOCTL:
		return print_gpiochip_info(tcp, arg);
	case GPIO_GET_LINEINFO_UNWATCH_IOCTL:
		return print_gpioline_info_unwatch(tcp, arg);
	case GPIO_V2_GET_LINEINFO_IOCTL:
	case GPIO_V2_GET_LINEINFO_WATCH_IOCTL:
		return print_gpio_v2_line_info(tcp, arg);
	case GPIO_V2_GET_LINE_IOCTL:
		return print_gpio_v2_line_request(tcp, arg);
	case GPIO_V2_LINE_SET_CONFIG_IOCTL:
		return print_gpio_v2_line_set_config(tcp, arg);
	case GPIO_V2_LINE_GET_VALUES_IOCTL:
		return print_gpio_v2_line_get_values(tcp, arg);
	case GPIO_V2_LINE_SET_VALUES_IOCTL:
		return print_gpio_v2_line_set_values(tcp, arg);
	case GPIO_GET_LINEINFO_IOCTL:
	case GPIO_GET_LINEINFO_WATCH_IOCTL:
		return print_gpioline_info(tcp, arg);
	case GPIO_GET_LINEHANDLE_IOCTL:
		return print_gpiohandle_request(tcp, arg);
	case GPIO_GET_LINEEVENT_IOCTL:
		return print_gpioevent_request(tcp, arg);
	case GPIOHANDLE_GET_LINE_VALUES_IOCTL:
		return print_gpiohandle_get_values(tcp, arg);
	case GPIOHANDLE_SET_LINE_VALUES_IOCTL:
		return print_gpiohandle_set_values(tcp, arg);
	case GPIOHANDLE_SET_CONFIG_IOCTL:
		return print_gpiohandle_set_config(tcp, arg);
	}
	return RVAL_DECODED;
}