/*
 * Copyright (c) 2012 Mike Frysinger <vapier@gentoo.org>
 * Copyright (c) 2012-2021 The strace developers.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "defs.h"

#include DEF_MPERS_TYPE(struct_mtd_oob_buf)

#include <linux/ioctl.h>
#include <mtd/mtd-abi.h>

typedef struct mtd_oob_buf struct_mtd_oob_buf;

#include MPERS_DEFS

#include "xlat/mtd_mode_options.h"
#include "xlat/mtd_file_mode_options.h"
#include "xlat/mtd_type_options.h"
#include "xlat/mtd_flags_options.h"
#include "xlat/mtd_otp_options.h"
#include "xlat/mtd_nandecc_options.h"

static void
decode_erase_info_user(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct erase_info_user einfo;

	if (umove_or_printaddr(tcp, addr, &einfo))
		return;

	tprint_struct_begin();
	PRINT_FIELD_X(einfo, start);
	tprint_struct_next();
	PRINT_FIELD_X(einfo, length);
	tprint_struct_end();
}

static void
decode_erase_info_user64(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct erase_info_user64 einfo64;

	if (umove_or_printaddr(tcp, addr, &einfo64))
		return;

	tprint_struct_begin();
	PRINT_FIELD_X(einfo64, start);
	tprint_struct_next();
	PRINT_FIELD_X(einfo64, length);
	tprint_struct_end();
}

static void
decode_mtd_oob_buf(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct_mtd_oob_buf mbuf;

	if (umove_or_printaddr(tcp, addr, &mbuf))
		return;
	tprint_struct_begin();
	PRINT_FIELD_X(mbuf, start);
	tprint_struct_next();
	PRINT_FIELD_X(mbuf, length);
	tprint_struct_next();
	PRINT_FIELD_PTR(mbuf, ptr);
	tprint_struct_end();
}

static void
decode_mtd_oob_buf64(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct mtd_oob_buf64 mbuf64;

	if (umove_or_printaddr(tcp, addr, &mbuf64))
		return;

	tprint_struct_begin();
	PRINT_FIELD_X(mbuf64, start);
	tprint_struct_next();
	PRINT_FIELD_X(mbuf64, length);
	tprint_struct_next();
	PRINT_FIELD_ADDR64(mbuf64, usr_ptr);
	tprint_struct_end();
}

static void
decode_otp_info(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct otp_info oinfo;

	if (umove_or_printaddr(tcp, addr, &oinfo))
		return;

	tprint_struct_begin();
	PRINT_FIELD_X(oinfo, start);
	tprint_struct_next();
	PRINT_FIELD_X(oinfo, length);
	tprint_struct_next();
	PRINT_FIELD_U(oinfo, locked);
	tprint_struct_end();
}

static void
decode_otp_select(struct tcb *const tcp, const kernel_ulong_t addr)
{
	unsigned int i;

	if (umove_or_printaddr(tcp, addr, &i))
		return;

	tprint_indirect_begin();
	printxval(mtd_otp_options, i, "MTD_OTP_???");
	tprint_indirect_end();
}

static void
decode_mtd_write_req(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct mtd_write_req mreq;

	if (umove_or_printaddr(tcp, addr, &mreq))
		return;

	tprint_struct_begin();
	PRINT_FIELD_X(mreq, start);
	tprint_struct_next();
	PRINT_FIELD_X(mreq, len);
	tprint_struct_next();
	PRINT_FIELD_X(mreq, ooblen);
	tprint_struct_next();
	PRINT_FIELD_ADDR64(mreq, usr_data);
	tprint_struct_next();
	PRINT_FIELD_ADDR64(mreq, usr_oob);
	tprint_struct_next();
	PRINT_FIELD_XVAL(mreq, mode, mtd_mode_options, "MTD_OPS_???");
	tprint_struct_end();
}

static void
decode_mtd_info_user(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct mtd_info_user minfo;

	if (umove_or_printaddr(tcp, addr, &minfo))
		return;

	tprint_struct_begin();
	PRINT_FIELD_XVAL(minfo, type, mtd_type_options, "MTD_???");
	tprint_struct_next();
	PRINT_FIELD_FLAGS(minfo, flags, mtd_flags_options, "MTD_???");
	tprint_struct_next();
	PRINT_FIELD_X(minfo, size);
	tprint_struct_next();
	PRINT_FIELD_X(minfo, erasesize);
	tprint_struct_next();
	PRINT_FIELD_X(minfo, writesize);
	tprint_struct_next();
	PRINT_FIELD_X(minfo, oobsize);
	tprint_struct_next();
	PRINT_FIELD_X(minfo, padding);
	tprint_struct_end();
}

static bool
print_xint32x2_array_member(struct tcb *tcp, void *elem_buf, size_t elem_size,
			    void *data)
{
	print_local_array_ex(tcp, elem_buf, 2, sizeof(int),
			     print_xint_array_member, NULL, 0, NULL, NULL);
	return true;
}

static void
decode_nand_oobinfo(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct nand_oobinfo ninfo;

	if (umove_or_printaddr(tcp, addr, &ninfo))
		return;

	tprint_struct_begin();
	PRINT_FIELD_XVAL(ninfo, useecc, mtd_nandecc_options, "MTD_NANDECC_???");
	tprint_struct_next();
	PRINT_FIELD_X(ninfo, eccbytes);
	tprint_struct_next();
	PRINT_FIELD_ARRAY(ninfo, oobfree, tcp, print_xint32x2_array_member);
	tprint_struct_next();
	PRINT_FIELD_ARRAY(ninfo, eccpos, tcp, print_xint_array_member);
	tprint_struct_end();
}

static bool
print_nand_oobfree_array_member(struct tcb *tcp, void *elem_buf,
				size_t elem_size, void *data)
{
	const struct nand_oobfree *p = elem_buf;
	tprint_struct_begin();
	PRINT_FIELD_X(*p, offset);
	tprint_struct_next();
	PRINT_FIELD_X(*p, length);
	tprint_struct_end();
	return true;
}

static void
decode_nand_ecclayout_user(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct nand_ecclayout_user nlay;

	if (umove_or_printaddr(tcp, addr, &nlay))
		return;

	tprint_struct_begin();
	PRINT_FIELD_X(nlay, eccbytes);
	tprint_struct_next();
	PRINT_FIELD_ARRAY(nlay, eccpos, tcp, print_xint_array_member);
	tprint_struct_next();
	PRINT_FIELD_X(nlay, oobavail);
	tprint_struct_next();
	PRINT_FIELD_ARRAY(nlay, oobfree, tcp, print_nand_oobfree_array_member);
	tprint_struct_end();
}

static void
decode_mtd_ecc_stats(struct tcb *const tcp, const kernel_ulong_t addr)
{
	struct mtd_ecc_stats es;

	if (umove_or_printaddr(tcp, addr, &es))
		return;

	tprint_struct_begin();
	PRINT_FIELD_X(es, corrected);
	tprint_struct_next();
	PRINT_FIELD_X(es, failed);
	tprint_struct_next();
	PRINT_FIELD_X(es, badblocks);
	tprint_struct_next();
	PRINT_FIELD_X(es, bbtblocks);
	tprint_struct_end();
}

MPERS_PRINTER_DECL(int, mtd_ioctl, struct tcb *const tcp,
		   const unsigned int code, const kernel_ulong_t arg)
{
	switch (code) {
	case MEMERASE:
	case MEMLOCK:
	case MEMUNLOCK:
	case MEMISLOCKED:
		tprint_arg_next();
		decode_erase_info_user(tcp, arg);
		break;

	case MEMERASE64:
		tprint_arg_next();
		decode_erase_info_user64(tcp, arg);
		break;

	case MEMWRITEOOB:
	case MEMREADOOB:
		tprint_arg_next();
		decode_mtd_oob_buf(tcp, arg);
		break;

	case MEMWRITEOOB64:
	case MEMREADOOB64:
		tprint_arg_next();
		decode_mtd_oob_buf64(tcp, arg);
		break;

	case MEMWRITE:
		tprint_arg_next();
		decode_mtd_write_req(tcp, arg);
		break;

	case OTPGETREGIONINFO:
		if (entering(tcp))
			return 0;
		ATTRIBUTE_FALLTHROUGH;
	case OTPLOCK:
		tprint_arg_next();
		decode_otp_info(tcp, arg);
		break;

	case OTPSELECT:
		tprint_arg_next();
		decode_otp_select(tcp, arg);
		break;

	case MTDFILEMODE:
		tprint_arg_next();
		printxval64(mtd_file_mode_options, arg, "MTD_FILE_MODE_???");
		break;

	case MEMGETBADBLOCK:
	case MEMSETBADBLOCK:
		tprint_arg_next();
		printnum_int64(tcp, arg, "%" PRIu64);
		break;

	case MEMGETINFO:
		if (entering(tcp))
			return 0;
		tprint_arg_next();
		decode_mtd_info_user(tcp, arg);
		break;

	case MEMGETOOBSEL:
		if (entering(tcp))
			return 0;
		tprint_arg_next();
		decode_nand_oobinfo(tcp, arg);
		break;

	case ECCGETLAYOUT:
		if (entering(tcp))
			return 0;
		tprint_arg_next();
		decode_nand_ecclayout_user(tcp, arg);
		break;

	case ECCGETSTATS:
		if (entering(tcp))
			return 0;
		tprint_arg_next();
		decode_mtd_ecc_stats(tcp, arg);
		break;

	case OTPGETREGIONCOUNT:
		if (entering(tcp))
			return 0;
		tprint_arg_next();
		printnum_int(tcp, arg, "%u");
		break;

	case MEMGETREGIONCOUNT:
		if (entering(tcp))
			return 0;
		tprint_arg_next();
		printnum_int(tcp, arg, "%d");
		break;

	case MEMGETREGIONINFO:
		if (entering(tcp)) {
			struct region_info_user rinfo;

			tprint_arg_next();
			if (umove_or_printaddr(tcp, arg, &rinfo))
				break;
			tprint_struct_begin();
			PRINT_FIELD_X(rinfo, regionindex);
			return 0;
		} else {
			struct region_info_user rinfo;

			if (!syserror(tcp) && !umove(tcp, arg, &rinfo)) {
				tprint_struct_next();
				PRINT_FIELD_X(rinfo, offset);
				tprint_struct_next();
				PRINT_FIELD_X(rinfo, erasesize);
				tprint_struct_next();
				PRINT_FIELD_X(rinfo, numblocks);
			}
			tprint_struct_end();
			break;
		}

	default:
		return RVAL_DECODED;
	}

	return RVAL_IOCTL_DECODED;
}