/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2020 Joyent, Inc.
 * Copyright 2020 Oxide Computer Company
 */

#ifndef _SYS_DDI_UFM_H
#define	_SYS_DDI_UFM_H

#ifdef __cplusplus
extern "C" {
#endif

#ifdef _KERNEL
#include <sys/cred.h>
#include <sys/dditypes.h>
#include <sys/nvpair.h>
#include <sys/param.h>
#else
#include <sys/nvpair.h>
#include <sys/param.h>
#include <sys/types.h>
#endif /* _KERNEL */

#define	DDI_UFM_DEV		"/dev/ufm"
#define	DDI_UFM_CURRENT_VERSION	1
#define	DDI_UFM_VERSION_ONE	1

#define	UFM_IOC			('u' << 24) | ('f' << 16) | ('m' << 8)
#define	UFM_IOC_GETCAPS		(UFM_IOC | 1)
#define	UFM_IOC_REPORTSZ	(UFM_IOC | 2)
#define	UFM_IOC_REPORT		(UFM_IOC | 3)
#define	UFM_IOC_READIMG		(UFM_IOC | 4)
#define	UFM_IOC_MAX		UFM_IOC_REPORT

/*
 * Bitfield enumerating the DDI UFM capabilities supported by this device
 * instance.  Currently there is only a single capability of being able to
 * report UFM information.  When support for new capabilties are added to the
 * DDI UFM subsystem, it should be reflected in this enum and the implementation
 * of the UFM_IOC_GETCAPS should be extended appropriately.
 */
typedef enum {
	DDI_UFM_CAP_REPORT	= 1 << 0,
	DDI_UFM_CAP_READIMG	= 1 << 1
} ddi_ufm_cap_t;

/*
 * This struct defines the input/output data for the UFM_IOC_GETCAPS ioctl.
 * Callers should specify the ufmg_version and ufmg_devpath fields.  On success
 * the ufmg_caps field will be filled in with a value indicating the supported
 * UFM capabilities of the device specified in ufmg_devpath.
 */
typedef struct ufm_ioc_getcaps {
	uint_t		ufmg_version;	/* DDI_UFM_VERSION */
	uint_t		ufmg_caps;	/* UFM Caps */
	char		ufmg_devpath[MAXPATHLEN];
} ufm_ioc_getcaps_t;

/*
 * This struct defines the input/output data for the UFM_IOC_REPORTSZ ioctl.
 * Callers should specify the ufbz_version and ufbz_devpath fields.  On success
 * the ufmg_size field will be filled in with the amount of space (in bytes)
 * required to hold the UFM data for this device instance.  This should be used
 * to allocate a sufficiently size buffer for the UFM_IOC_REPORT ioctl.
 */
typedef struct ufm_ioc_bufsz {
	uint_t		ufbz_version;	/* DDI_UFM_VERSION */
	size_t		ufbz_size;	/* sz of buf to be returned by ioctl */
	char		ufbz_devpath[MAXPATHLEN];
} ufm_ioc_bufsz_t;

#ifdef _KERNEL
typedef struct ufm_ioc_bufsz32 {
	uint_t		ufbz_version;
	size32_t	ufbz_size;
	char		ufbz_devpath[MAXPATHLEN];
} ufm_ioc_bufsz32_t;
#endif	/* _KERNEL */

/*
 * This struct defines the input/output data for the UFM_IOC_REPORT ioctl.
 * Callers should specify the ufmr_version, ufmr_bufsz and ufmr_devpath fields.
 * On success, the ufmr_buf field will point to a packed nvlist containing the
 * UFM data for the specified device instance.  The value of ufmr_bufsz will be
 * updated to reflect the actual size of data copied out.
 */
typedef struct ufm_ioc_report {
	uint_t		ufmr_version;	/* DDI_UFM_VERSION */
	size_t		ufmr_bufsz;	/* size of caller-supplied buffer */
	caddr_t		ufmr_buf;	/* buf to hold packed output nvl */
	char		ufmr_devpath[MAXPATHLEN];
} ufm_ioc_report_t;

#ifdef _KERNEL
typedef struct ufm_ioc_report32 {
	uint_t		ufmr_version;
	size32_t	ufmr_bufsz;
	caddr32_t	ufmr_buf;
	char		ufmr_devpath[MAXPATHLEN];
} ufm_ioc_report32_t;
#endif	/* _KERNEL */

/*
 * This struct defines the input/output data for the UFM_IOC_READ ioctl, which
 * reads the firmware image from a given slot.
 */
typedef struct ufm_ioc_readimg {
	uint_t		ufri_version;
	uint_t		ufri_imageno;
	uint_t		ufri_slotno;
	uint64_t	ufri_offset;
	uint64_t	ufri_len;
	uint64_t	ufri_nread;
	void		*ufri_buf;
	char		ufri_devpath[MAXPATHLEN];
} ufm_ioc_readimg_t;

#ifdef _KERNEL
#pragma pack(4)
typedef struct ufm_ioc_readimg32 {
	uint_t		ufri_version;
	uint_t		ufri_imageno;
	uint_t		ufri_slotno;
	uint64_t	ufri_offset;
	uint64_t	ufri_len;
	uint64_t	ufri_nread;
	caddr32_t	ufri_buf;
	char		ufri_devpath[MAXPATHLEN];
} ufm_ioc_readimg32_t;
#pragma pack()
#endif	/* _KERNEL */

/*
 * The UFM_IOC_REPORT ioctl return UFM image and slot data in the form of a
 * packed nvlist.  The nvlist contains and array of nvlists (one-per-image).
 * Each image nvlist contains will contain a string nvpair containing a
 * description of the image and an optional nvlist nvpair containing
 * miscellaneous image information.
 */
#define	DDI_UFM_NV_IMAGES		"ufm-images"
#define	DDI_UFM_NV_IMAGE_DESC		"ufm-image-description"
#define	DDI_UFM_NV_IMAGE_MISC		"ufm-image-misc"

/*
 * Each image nvlist also contains an array of nvlists representing the slots.
 */
#define	DDI_UFM_NV_IMAGE_SLOTS		"ufm-image-slots"

/*
 * Each slot nvlist has the following:
 *
 *  o A string nvpair describing the firmware image version
 *  o A uint32 nvpair describing the slot attributes (see ddi_ufm_attr_t
 *    below).
 *  o An optional nvlist nvpar may be present containing additional
 *    miscellaneous slot data.
 *  o An optional uint64 slot length that indicates the size of the image in
 *    that slot. Note htis is the size of the image, not the size of the slot.
 */
#define	DDI_UFM_NV_SLOT_VERSION		"ufm-slot-version"
#define	DDI_UFM_NV_SLOT_IMGSIZE		"ufm-slot-imgsize"

typedef enum {
	DDI_UFM_ATTR_READABLE	= 1 << 0,
	DDI_UFM_ATTR_WRITEABLE	= 1 << 1,
	DDI_UFM_ATTR_ACTIVE	= 1 << 2,
	DDI_UFM_ATTR_EMPTY	= 1 << 3
} ddi_ufm_attr_t;

#define	DDI_UFM_ATTR_MAX	DDI_UFM_ATTR_READABLE | \
				DDI_UFM_ATTR_WRITEABLE | \
				DDI_UFM_ATTR_ACTIVE | \
				DDI_UFM_ATTR_EMPTY

#define	DDI_UFM_NV_SLOT_ATTR		"ufm-slot-attributes"

#define	DDI_UFM_NV_SLOT_MISC		"ufm-slot-misc"

#ifdef _KERNEL
/* opaque structures */
typedef struct ddi_ufm_handle ddi_ufm_handle_t;
typedef struct ddi_ufm_image ddi_ufm_image_t;
typedef struct ddi_ufm_slot ddi_ufm_slot_t;

/*
 * DDI UFM Operations vector
 */
typedef struct ddi_ufm_ops {
	int (*ddi_ufm_op_nimages)(ddi_ufm_handle_t *, void *, uint_t *);
	int (*ddi_ufm_op_fill_image)(ddi_ufm_handle_t *, void *, uint_t,
	    ddi_ufm_image_t *);
	int (*ddi_ufm_op_fill_slot)(ddi_ufm_handle_t *, void *, uint_t, uint_t,
	    ddi_ufm_slot_t *);
	int (*ddi_ufm_op_getcaps)(ddi_ufm_handle_t *, void *, ddi_ufm_cap_t *);
	int (*ddi_ufm_op_readimg)(ddi_ufm_handle_t *, void *, uint_t, uint_t,
	    uint64_t, uint64_t, void *, uint64_t *);
} ddi_ufm_ops_t;

/*
 * During a device driver's attach(9E) entry point, a device driver should
 * register with the UFM subsystem by filling out a UFM operations vector
 * (see above) and then calling ddi_ufm_init(9F).  The driver may pass in a
 * value, usually a pointer to its soft state pointer, which it will then
 * receive when its subsequent entry points are called.
 */
int ddi_ufm_init(dev_info_t *, uint_t version, ddi_ufm_ops_t *,
    ddi_ufm_handle_t **, void *);

/*
 * Device drivers should call ddi_ufm_update(9F) after driver initialization is
 * complete and after calling ddi_ufm_init(9F), in order to indicate to the
 * UFM subsystem that the driver is in a state where it is ready to receive
 * calls to its UFM entry points.
 *
 * Additionally, whenever the driver detects a change in the state of a UFM, it
 * should call ddi_ufm_update(9F).  This will cause the UFM subsystem to
 * invalidate any cached state regarding this driver's UFM(s)
 */
void ddi_ufm_update(ddi_ufm_handle_t *);

/*
 * A device driver should call ddi_ufm_fini(9F) during its detach(9E) entry
 * point.  Upon return, the driver is gaurunteed that no further DDI UFM entry
 * points will be called and thus any related state can be safely torn down.
 *
 * After return, the UFM handle is no longer valid and should not be used in
 * any future ddi_ufm_* calls.
 */
void ddi_ufm_fini(ddi_ufm_handle_t *);

/*
 * These interfaces should only be called within the context of a
 * ddi_ufm_op_fill_image callback.
 */
void ddi_ufm_image_set_desc(ddi_ufm_image_t *, const char *);
void ddi_ufm_image_set_nslots(ddi_ufm_image_t *, uint_t);
void ddi_ufm_image_set_misc(ddi_ufm_image_t *, nvlist_t *);

/*
 * These interfaces should only be called within the context of a
 * ddi_ufm_op_fill_slot callback.
 */
void ddi_ufm_slot_set_version(ddi_ufm_slot_t *, const char *);
void ddi_ufm_slot_set_attrs(ddi_ufm_slot_t *, ddi_ufm_attr_t);
void ddi_ufm_slot_set_misc(ddi_ufm_slot_t *, nvlist_t *);
void ddi_ufm_slot_set_imgsize(ddi_ufm_slot_t *, uint64_t);
#endif /* _KERNEL */

#ifdef __cplusplus
}
#endif

#endif	/* _SYS_DDI_UFM_H */