/*
 * Copyright (c) 2020-2026 Amazon.com, Inc. or its affiliates.
 * All Rights Reserved.
 *
 * SPDX-License-Identifier: GPL-2.0
 */

#include <linux/module.h>
#include <linux/kobject.h>

struct idx_len {
	u8 idx;
	u8 len;
};

struct dsn_data {
	struct idx_len prod_code;
	struct idx_len rev_code;
	struct idx_len year;
	u32 base_year;
	struct idx_len week;
};

static const unsigned int BOARD_ID_PROD_ID_INDEX; // = 0
static const unsigned int BOARD_ID_PROD_ID_LEN      =  4;
static const unsigned int BOARD_ID_2ND_SOURCE_INDEX =  4;
static const unsigned int BOARD_ID_PROTO_PROD_INDEX =  6;
static const unsigned int BOARD_ID_MAJOR_REV_INDEX  =  7;
static const unsigned int BOARD_ID_MINOR_REV_INDEX  =  8;
static const unsigned int BOARD_ID_WFO_WAN_INDEX    =  9;
static const unsigned int BOARD_ID_JTAG_INDEX       = 10;
static const unsigned int BOARD_ID_CM_INDEX         = 11;
static const unsigned int BOARD_ID_YEAR_INDEX       = 14;
static const unsigned int BOARD_ID_YEAR_LEN         =  2;

static const unsigned int SERIAL_ID_VER_IDX; // 0
/* For DSNs starting Bxxx */
static const struct dsn_data b_serial = {
	.prod_code = {0, 3},
	.rev_code = {3, 1},
	.year = {4, 2},
	.base_year = 2000,
	.week = {6, 2},
};

/* For DSNs starting Gxxx */
static const struct dsn_data g_serial = {
	.prod_code = {3, 3},
	.rev_code = {6, 2},
	.year = {8, 1},
	.base_year = 2020,
	.week = {9, 2}
};

/* Product ID mappings. Ordering is important!! */
static const char * const BOARD_ID_PROD_ID_STRS[] = {
	"c0ca",
	"c0cb",
	"c0cc",
	"c0cd",
	"c0d2",
	"c0d3",
	"c0d4"
};

enum RB_PROD_ID_TYPES {
	RB_PRODUCT_ABA,
	RB_PRODUCT_ABC,
	RB_PRODUCT_ABG,
	RB_PRODUCT_ABR,
	RB_PRODUCT_ABH,
	RB_PRODUCT_ABU,
	RB_PRODUCT_ABV,
	RB_PRODUCT_UNKNOWN
};

static const char * const RB_PROD_ID_STRS[] = {
	"Cocoa",
	"ABC",
	"ABG",
	"ABR",
	"ABH",
	"ABU",
	"ABV",
	"Unknown"
};

static const char *ps5250_str = "ps5250";
static const char *sp2309_str = "sp2309";
static const char *bt656_str = "bt656";

enum RB_MAJOR_REVS {
	RB_MAJOR_REV_UNKNOWN = 0,

	RB_MAJOR_REV_MIN = '0',

	RB_MAJOR_REV_PROTO = RB_MAJOR_REV_MIN,
	RB_MAJOR_REV_HVT,
	RB_MAJOR_REV_EVT,
	RB_MAJOR_REV_DVT,
	RB_MAJOR_REV_PVT,
	RB_MAJOR_REV_MP,

	RB_MAJOR_REV_MAX
};

static const char * const RB_MAJOR_REV_STRS[] = {
	"PROTO",
	"HVT",
	"EVT",
	"DVT",
	"PVT",
	"MP"
};

/* Helper macros for creating/accessing sysfs node attributes */
#define RB_ATTR_RO(_name) \
	static struct kobj_attribute rb_attr_##_name = __ATTR_RO(_name)

#define RB_GET_ATTR(_name) \
	(&rb_attr_##_name.attr)

static const char *DEVICE_NAME = "ringboot";

static struct kobject *ringboot_kobj;

static char *boardid;
static char *serialno;
static char *validated_kernel;
static char *unlocked_kernel;
static char *rootfs_name;
static char *rootfs_offset;

static int factory_test_port;

/* Default to lockdown state */
static bool is_unlocked;
static bool is_validated_boot;

static enum RB_PROD_ID_TYPES product_type = RB_PRODUCT_UNKNOWN;
static enum RB_MAJOR_REVS    major_rev = RB_MAJOR_REV_UNKNOWN;
static int second_source_ver;
static int minor_rev;
static int product_year;
static int manufacture_year;
static int manufacture_week;
#define ALT_REV_CODE_MAX_LEN 3 /* including trailing 0 */
static char alt_rev_code[ALT_REV_CODE_MAX_LEN];

static bool backup_kernel;

static ssize_t is_validated_boot_show(struct kobject *kobj,
				      struct kobj_attribute *attr,
				      char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%s\n", is_validated_boot ? "true" :
								     "false");
}

static ssize_t is_unlocked_show(struct kobject *kobj,
				struct kobj_attribute *attr,
				char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%s\n", is_unlocked ? "true" :
							       "false");
}

static ssize_t product_type_str_show(struct kobject *kobj,
				     struct kobj_attribute *attr,
				     char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%s\n", RB_PROD_ID_STRS[product_type]);
}

static ssize_t second_source_ver_show(struct kobject *kobj,
				      struct kobj_attribute *attr,
				      char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%d\n", second_source_ver);
}

static ssize_t sensor_type_str_show(struct kobject *kobj,
				    struct kobj_attribute *attr,
				    char *buf)
{
	const char *sensor = ps5250_str;

	switch (product_type) {
	case RB_PRODUCT_ABC:
		if (second_source_ver > 0)
			sensor = sp2309_str;
		break;
	case RB_PRODUCT_ABG:
		/* ABG is switching to SP2309 from DVT2.1
		 * (DVT3 in internal numbering)
		 */
		if ((major_rev > RB_MAJOR_REV_DVT) ||
		    ((major_rev == RB_MAJOR_REV_DVT) && (minor_rev >= 3)))
			sensor = sp2309_str;
		break;
	case RB_PRODUCT_ABH:
	case RB_PRODUCT_ABU:
	case RB_PRODUCT_ABV:
		sensor = bt656_str;
		break;
	default:
		break;
	}
	return scnprintf(buf, PAGE_SIZE, "%s\n", sensor);
}

static ssize_t major_rev_str_show(struct kobject *kobj,
				  struct kobj_attribute *attr,
				  char *buf)
{
	if (major_rev > RB_MAJOR_REV_UNKNOWN) {
		return scnprintf(buf, PAGE_SIZE, "%s\n",
				 RB_MAJOR_REV_STRS[major_rev -
						       RB_MAJOR_REV_MIN]);
	} else {
		return scnprintf(buf, PAGE_SIZE, "%s\n", "Unknown");
	}
}

static ssize_t major_rev_num_show(struct kobject *kobj,
				  struct kobj_attribute *attr,
				  char *buf)
{
	if (major_rev > RB_MAJOR_REV_UNKNOWN) {
		return scnprintf(buf, PAGE_SIZE, "%d\n",
				 major_rev - RB_MAJOR_REV_MIN);
	} else {
		return scnprintf(buf, PAGE_SIZE, "%d\n", -1);
	}
}

static ssize_t minor_rev_num_show(struct kobject *kobj,
				  struct kobj_attribute *attr,
				  char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%d\n", minor_rev);
}

static ssize_t alt_rev_code_show(struct kobject *kobj,
				 struct kobj_attribute *attr,
				 char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%s\n", alt_rev_code);
}

static ssize_t product_year_show(struct kobject *kobj,
				 struct kobj_attribute *attr,
				 char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%d\n", product_year);
}

static ssize_t manufacture_year_show(struct kobject *kobj,
				     struct kobj_attribute *attr,
				     char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%d\n", manufacture_year);
}

static ssize_t manufacture_week_show(struct kobject *kobj,
				     struct kobj_attribute *attr,
				     char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%d\n", manufacture_week);
}

static ssize_t
build_number_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
	return scnprintf(buf, PAGE_SIZE, "%s\n", RING_BUILD_NUMBER);
}

RB_ATTR_RO(is_validated_boot);
RB_ATTR_RO(is_unlocked);
RB_ATTR_RO(product_type_str);
RB_ATTR_RO(second_source_ver);
RB_ATTR_RO(sensor_type_str);
RB_ATTR_RO(major_rev_str);
RB_ATTR_RO(major_rev_num);
RB_ATTR_RO(minor_rev_num);
RB_ATTR_RO(alt_rev_code);
RB_ATTR_RO(product_year);
RB_ATTR_RO(manufacture_year);
RB_ATTR_RO(manufacture_week);
RB_ATTR_RO(build_number);

static const struct attribute *attrs[] = {
	RB_GET_ATTR(is_validated_boot),
	RB_GET_ATTR(is_unlocked),
	RB_GET_ATTR(product_type_str),
	RB_GET_ATTR(second_source_ver),
	RB_GET_ATTR(sensor_type_str),
	RB_GET_ATTR(major_rev_str),
	RB_GET_ATTR(major_rev_num),
	RB_GET_ATTR(minor_rev_num),
	RB_GET_ATTR(alt_rev_code),
	RB_GET_ATTR(product_year),
	RB_GET_ATTR(manufacture_year),
	RB_GET_ATTR(manufacture_week),
	RB_GET_ATTR(build_number),
	NULL
};

static int __init ringboot_init(void)
{
	int err = 0;
	int i;

	if (validated_kernel) {
		if (strcmp(validated_kernel, "true") == 0)
			is_validated_boot = true;
	}
	if (unlocked_kernel) {
		if ((strcmp(unlocked_kernel, "true") == 0) && is_validated_boot)
			is_unlocked = true;
	}
	if (boardid) {
		int i;

		for (i = 0; i < ARRAY_SIZE(BOARD_ID_PROD_ID_STRS); i++) {
			if (strncmp(&boardid[BOARD_ID_PROD_ID_INDEX],
				    BOARD_ID_PROD_ID_STRS[i],
				    BOARD_ID_PROD_ID_LEN) == 0) {
				product_type = i;
				break;
			}
		}

		second_source_ver =
			(boardid[BOARD_ID_2ND_SOURCE_INDEX] - '0') * 10 +
			(boardid[BOARD_ID_2ND_SOURCE_INDEX + 1] - '0');

		if (boardid[BOARD_ID_MAJOR_REV_INDEX] >= RB_MAJOR_REV_MIN &&
		    boardid[BOARD_ID_MAJOR_REV_INDEX] < RB_MAJOR_REV_MAX)
			major_rev = boardid[BOARD_ID_MAJOR_REV_INDEX];

		minor_rev = boardid[BOARD_ID_MINOR_REV_INDEX] - '0';

		product_year = 2000 +
				   (boardid[BOARD_ID_YEAR_INDEX] - '0') * 10 +
				   (boardid[BOARD_ID_YEAR_INDEX + 1] - '0');
	}
	if (serialno) {
		int i;
		int maxlen;
		const struct dsn_data *serial_data =
					(serialno[SERIAL_ID_VER_IDX] == 'B') ?
					 &b_serial : &g_serial;

		for (i = 0; i < serial_data->year.len; i++) {
			manufacture_year = (manufacture_year * 10) +
				(serialno[serial_data->year.idx + i] - '0');
		}
		manufacture_year += serial_data->base_year;

		for (i = 0; i < serial_data->week.len; i++) {
			manufacture_week = (manufacture_week * 10) +
				(serialno[serial_data->week.idx + i] - '0');
		}

		maxlen = serial_data->rev_code.len < ALT_REV_CODE_MAX_LEN ?
					serial_data->rev_code.len :
					ALT_REV_CODE_MAX_LEN - 1;
		for (i = 0; i < maxlen; i++) {
			alt_rev_code[i] =
				serialno[serial_data->rev_code.idx + i];
		}
	}

	/* Find the kobj associated with /sys/module/ringboot */
	ringboot_kobj = kset_find_obj(module_kset, DEVICE_NAME);
	if (!ringboot_kobj) {
		err = -ENOENT;
		goto out;
	}

	/* Add files for the parsed board id etc to sysfs. Manually
	 * add them to the same location as the module parameters added
	 * by module_param
	 */
	for (i = 0; attrs[i] && !err; i++) {
		err = sysfs_add_file_to_group(ringboot_kobj, attrs[i],
					      "parameters");
	}
	if (err) {
		while (--i >= 0) {
			sysfs_remove_file_from_group(ringboot_kobj, attrs[i],
						     "parameters");
		}
	}

out:
	return err;
}
subsys_initcall(ringboot_init);

static void __exit ringboot_exit(void)
{
	int i;

	if (ringboot_kobj) {
		for (i = 0; attrs[i]; i++) {
			sysfs_remove_file_from_group(ringboot_kobj, attrs[i],
						     "parameters");
		}
	}
	pr_info("%s: Exit\n", DEVICE_NAME);
}
module_exit(ringboot_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Richard Simmons <rssimmo@amazon.co.uk>");

/* Make these parameters directly available in sysfs */
module_param(boardid, charp, 0400);
MODULE_PARM_DESC(boardid, "The Board ID field");

module_param(serialno, charp, 0400);
MODULE_PARM_DESC(serialno, "The Device Serial Number (DSN)");

module_param(factory_test_port, int, 0400);
MODULE_PARM_DESC(factory_test_port, "Factory test TCP port");

module_param(backup_kernel, bool, 0400);
MODULE_PARM_DESC(backup_kernel, "Are we using the backup kernel partition");

module_param(rootfs_name, charp, 0400);
MODULE_PARM_DESC(rootfs_name, "Name of rootfs partition to mount");

module_param(rootfs_offset, charp, 0400);
MODULE_PARM_DESC(rootfs_offset, "Offset of validated rootfs partition");

/* These parameters are not directly available in sysfs to ensure we can force
 * them to false if not set.  They are parsed and added manually
 */
module_param(validated_kernel, charp, 0000);
MODULE_PARM_DESC(validated_kernel, "Indicates whether device went through secure boot cycle");

module_param(unlocked_kernel, charp, 0000);
MODULE_PARM_DESC(unlocked_kernel, "Indicates whether device is unlocked");
