/*	$NetBSD: umass_isdata.c,v 1.42.4.1 2022/12/30 14:39:10 martin Exp $	*/

/*
 * TODO:
 *  get ATA registers on any kind of error
 *  implement more commands (what is needed)
 */

/*
 * Copyright (c) 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Lennart Augustsson (lennart@augustsson.net) at
 * Carlstedt Research & Technology.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: umass_isdata.c,v 1.42.4.1 2022/12/30 14:39:10 martin Exp $");

#ifdef _KERNEL_OPT
#include "opt_usb.h"
#endif

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/disklabel.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/systm.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>

#include <dev/usb/umassvar.h>
#include <dev/usb/umass_isdata.h>

int umass_wd_attach(struct umass_softc *);

#include <dev/ata/atareg.h>
#include <dev/ata/atavar.h>

/*
 * XXX This driver likely doesn't work after ATA NCQ changes.
 * XXX Need to confirm if the ata_channel kludge works
 */

/* XXX move this */
struct isd200_config {
	uByte EventNotification;
	uByte ExternalClock;
	uByte ATAInitTimeout;
	uByte ATAMisc1;
#define ATATiming		0x0f
#define ATAPIReset		0x10
#define MasterSlaveSelection	0x20
#define ATAPICommandBlockSize	0xc0
	uByte ATAMajorCommand;
	uByte ATAMinorCommand;
	uByte ATAMisc2;
#define LastLUNIdentifier	0x07
#define DescriptOverride	0x08
#define ATA3StateSuspend	0x10
#define SkipDeviceBoot		0x20
#define ConfigDescriptor2	0x40
#define InitStatus		0x80
	uByte ATAMisc3;
#define SRSTEnable		0x01
};

struct uisdata_softc {
	struct umassbus_softc	base;

	struct ata_drive_datas	sc_drv_data;
	struct ata_channel 	sc_channel;
	struct isd200_config	sc_isd_config;
	u_long			sc_skip;
};

#define CH2SELF(chnl_softc)	((void *)chnl_softc->atabus)

#undef DPRINTF
#undef DPRINTFN
#ifdef UISDATA_DEBUG
#define DPRINTF(x)	if (uisdatadebug) printf x
#define DPRINTFN(n,x)	if (uisdatadebug>(n)) printf x
int	uisdatadebug = 0;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif

void uisdata_bio(struct ata_drive_datas *, struct ata_xfer *);
void uisdata_bio1(struct ata_drive_datas *, struct ata_xfer *);
void uisdata_reset_drive(struct ata_drive_datas *, int, uint32_t *);
void uisdata_reset_channel(struct ata_channel *, int);
void uisdata_exec_command(struct ata_drive_datas *, struct ata_xfer *);
int  uisdata_get_params(struct ata_drive_datas *, uint8_t, struct ataparams *);
int  uisdata_addref(struct ata_drive_datas *);
void uisdata_delref(struct ata_drive_datas *);
void uisdata_kill_pending(struct ata_drive_datas *);

void uisdata_bio_cb(struct umass_softc *, void *, int, int);
void uisdata_exec_cb(struct umass_softc *, void *, int, int);
int  uwdprint(void *, const char *);

const struct ata_bustype uisdata_bustype = {
	SCSIPI_BUSTYPE_ATA,
	uisdata_bio,
	uisdata_reset_drive,
	uisdata_reset_channel,
	uisdata_exec_command,
	uisdata_get_params,
	uisdata_addref,
	uisdata_delref,
	uisdata_kill_pending,
	NULL,
};

struct ata_cmd {
	uint8_t ac_signature0;
	uint8_t ac_signature1;

	uint8_t ac_action_select;
#define AC_ReadRegisterAccess		0x01
#define AC_NoDeviceSelectionBit		0x02
#define AC_NoBSYPollBit			0x04
#define AC_IgnorePhaseErrorBit		0x08
#define AC_IgnoreDeviceErrorBit		0x10

	uint8_t ac_register_select;
#define AC_SelectAlternateStatus	0x01 /* R */
#define AC_SelectDeviceControl		0x01 /* W */
#define AC_SelectError			0x02 /* R */
#define AC_SelectFeatures		0x02 /* W */
#define AC_SelectSectorCount		0x04 /* RW */
#define AC_SelectSectorNumber		0x08 /* RW */
#define AC_SelectCylinderLow		0x10 /* RW */
#define AC_SelectCylinderHigh		0x20 /* RW */
#define AC_SelectDeviceHead		0x40 /* RW */
#define AC_SelectStatus			0x80 /* R */
#define AC_SelectCommand		0x80 /* W */

	uint8_t ac_transfer_blocksize;

	uint8_t ac_alternate_status;
#define ac_device_control ac_alternate_status
	uint8_t ac_error;
#define ac_features ac_error

	uint8_t ac_sector_count;
	uint8_t ac_sector_number;
	uint8_t ac_cylinder_low;
	uint8_t ac_cylinder_high;
	uint8_t ac_device_head;

	uint8_t ac_status;
#define ac_command ac_status

	uint8_t ac_reserved[3];
};

#define ATA_DELAY 10000 /* 10s for a drive I/O */

int
umass_isdata_attach(struct umass_softc *sc)
{
	usb_device_request_t req;
	usbd_status err;
	struct ata_device adev;
	struct uisdata_softc *scbus;
	struct isd200_config *cf;

	scbus = kmem_zalloc(sizeof(*scbus), KM_SLEEP);
	sc->bus = &scbus->base;
	cf = &scbus->sc_isd_config;

	req.bmRequestType = UT_READ_VENDOR_DEVICE;
	req.bRequest = 0x02;
	USETW(req.wValue, 0);
	USETW(req.wIndex, 2);
	USETW(req.wLength, sizeof(*cf));

	err = usbd_do_request(sc->sc_udev, &req, cf);
	if (err) {
		sc->bus = NULL;
		kmem_free(scbus, sizeof(*scbus));
		return EIO;
	}
	DPRINTF(("umass_wd_attach info:\n  EventNotification=0x%02x "
		 "ExternalClock=0x%02x ATAInitTimeout=0x%02x\n"
		 "  ATAMisc1=0x%02x ATAMajorCommand=0x%02x "
		 "ATAMinorCommand=0x%02x\n"
		 "  ATAMisc2=0x%02x ATAMisc3=0x%02x\n",
		 cf->EventNotification, cf->ExternalClock, cf->ATAInitTimeout,
		 cf->ATAMisc1, cf->ATAMajorCommand, cf->ATAMinorCommand,
		 cf->ATAMisc2, cf->ATAMisc3));

	memset(&adev, 0, sizeof(struct ata_device));
	adev.adev_bustype = &uisdata_bustype;
	adev.adev_channel = 1;	/* XXX */
	adev.adev_drv_data = &scbus->sc_drv_data;

	/* Fake ATA channel so wd(4) ata_{get,free}_xfer() work */
	ata_channel_init(&scbus->sc_channel);
	scbus->sc_channel.atabus = (device_t)scbus;

	scbus->sc_drv_data.drive_type = ATA_DRIVET_ATA;
	scbus->sc_drv_data.chnl_softc = &scbus->sc_channel;

	scbus->base.sc_child = config_found(sc->sc_dev, &adev, uwdprint);

	return 0;
}

void
umass_isdata_detach(struct umass_softc *sc)
{
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;

	ata_channel_destroy(&scbus->sc_channel);

	kmem_free(scbus, sizeof(*scbus));
	sc->bus = NULL;
}

void
uisdata_bio_cb(struct umass_softc *sc, void *priv, int residue, int status)
{
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;
	struct ata_xfer *xfer = priv;
	struct ata_bio *ata_bio = &xfer->c_bio;
	int s;

	DPRINTF(("%s: residue=%d status=%d\n", __func__, residue, status));

	s = splbio();
	if (status != STATUS_CMD_OK)
		ata_bio->error = ERR_DF; /* ??? */
	else
		ata_bio->error = NOERROR;
	ata_bio->flags |= ATA_ITSDONE;

	ata_bio->blkdone += ata_bio->nblks;
	ata_bio->blkno += ata_bio->nblks;
	ata_bio->bcount -= ata_bio->nbytes;
	scbus->sc_skip += ata_bio->nbytes;
	if (residue != 0) {
		ata_bio->bcount += residue;
	} else if (ata_bio->bcount > 0) {
		DPRINTF(("%s: continue\n", __func__));
		uisdata_bio1(&scbus->sc_drv_data, xfer); /*XXX save drv*/
		splx(s);
		return;
	}

	if (ata_bio->flags & ATA_POLL) {
		DPRINTF(("%s: wakeup %p\n", __func__, ata_bio));
		wakeup(ata_bio);
	} else {
		(*scbus->sc_drv_data.drv_done)(scbus->sc_drv_data.drv_softc,
		    xfer);
	}
	splx(s);
}

void
uisdata_bio(struct ata_drive_datas *drv, struct ata_xfer *xfer)
{
	struct umass_softc *sc = CH2SELF(drv->chnl_softc);
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;

	scbus->sc_skip = 0;
	uisdata_bio1(drv, xfer);
}

void
uisdata_bio1(struct ata_drive_datas *drv, struct ata_xfer *xfer)
{
	struct umass_softc *sc = CH2SELF(drv->chnl_softc);
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;
	struct isd200_config *cf = &scbus->sc_isd_config;
	struct ata_bio *ata_bio = &xfer->c_bio;
	struct ata_cmd ata;
	uint16_t cyl;
	uint8_t head, sect;
	int dir;
	long nbytes;
	u_int nblks;

	DPRINTF(("%s\n", __func__));
	/* XXX */

	if (ata_bio->flags & ATA_POLL) {
		printf("%s: ATA_POLL not supported\n", __func__);
		ata_bio->error = TIMEOUT;
		ata_bio->flags |= ATA_ITSDONE;
	}

	if (ata_bio->flags & ATA_LBA) {
		sect = (ata_bio->blkno >> 0) & 0xff;
		cyl = (ata_bio->blkno >> 8) & 0xffff;
		head = (ata_bio->blkno >> 24) & 0x0f;
		head |= WDSD_LBA;
	} else {
		int blkno = ata_bio->blkno;
		sect = blkno % drv->lp->d_nsectors;
		sect++;    /* Sectors begin with 1, not 0. */
		blkno /= drv->lp->d_nsectors;
		head = blkno % drv->lp->d_ntracks;
		blkno /= drv->lp->d_ntracks;
		cyl = blkno;
		head |= WDSD_CHS;
	}

	nbytes = ata_bio->bcount;
	if (ata_bio->flags & ATA_SINGLE)
		nblks = 1;
	else
		nblks = uimin(drv->multi, nbytes / drv->lp->d_secsize);
	nbytes = nblks * drv->lp->d_secsize;
	ata_bio->nblks = nblks;
	ata_bio->nbytes = nbytes;

	memset(&ata, 0, sizeof(ata));
	ata.ac_signature0 = cf->ATAMajorCommand;
	ata.ac_signature1 = cf->ATAMinorCommand;
	ata.ac_transfer_blocksize = 1;
	ata.ac_sector_count = nblks;
	ata.ac_sector_number = sect;
	ata.ac_cylinder_high = cyl >> 8;
	ata.ac_cylinder_low = cyl;
	ata.ac_device_head = head;
	ata.ac_register_select = AC_SelectSectorCount | AC_SelectSectorNumber |
	    AC_SelectCylinderLow | AC_SelectCylinderHigh | AC_SelectDeviceHead |
	    AC_SelectCommand;

	dir = DIR_NONE;
	if (ata_bio->bcount != 0) {
		if (ata_bio->flags & ATA_READ)
			dir = DIR_IN;
		else
			dir = DIR_OUT;
	}

	if (ata_bio->flags & ATA_READ) {
		ata.ac_command = WDCC_READ;
	} else {
		ata.ac_command = WDCC_WRITE;
	}
	DPRINTF(("%s: bno=%" PRId64 " LBA=%d cyl=%d head=%d sect=%d "
		 "count=%d multi=%d\n",
		 __func__, ata_bio->blkno,
		 (ata_bio->flags & ATA_LBA) != 0, cyl, head, sect,
		 ata.ac_sector_count, drv->multi));
	DPRINTF(("    data=%p bcount=%ld, drive=%d\n", ata_bio->databuf,
		 ata_bio->bcount, drv->drive));
	sc->sc_methods->wire_xfer(sc, drv->drive, &ata, sizeof(ata),
				  ata_bio->databuf + scbus->sc_skip, nbytes,
				  dir, ATA_DELAY, 0, uisdata_bio_cb, xfer);

	while (ata_bio->flags & ATA_POLL) {
		DPRINTF(("%s: tsleep %p\n", __func__, ata_bio));
		if (tsleep(ata_bio, PZERO, "uisdatabl", 0)) {
			ata_bio->error = TIMEOUT;
			ata_bio->flags |= ATA_ITSDONE;
		}
	}
}

void
uisdata_reset_drive(struct ata_drive_datas *drv, int flags, uint32_t *sigp)
{
	DPRINTFN(-1,("%s\n", __func__));
	KASSERT(sigp == NULL);
	/* XXX what? */
}

void
uisdata_reset_channel(struct ata_channel *chp, int flags)
{
	DPRINTFN(-1,("%s\n", __func__));
	/* XXX what? */
}

void
uisdata_exec_cb(struct umass_softc *sc, void *priv,
    int residue, int status)
{
	struct ata_command *cmd = priv;

	DPRINTF(("%s: status=%d\n", __func__, status));
	if (status != STATUS_CMD_OK)
		cmd->flags |= AT_DF; /* XXX */
	cmd->flags |= AT_DONE;
	if (cmd->flags & (AT_READ | AT_WRITE))
		cmd->flags |= AT_XFDONE;
	if (cmd->flags & (AT_POLL | AT_WAIT)) {
		DPRINTF(("%s: wakeup %p\n", __func__, cmd));
		wakeup(cmd);
	}
}

void
uisdata_exec_command(struct ata_drive_datas *drv, struct ata_xfer *xfer)
{
	struct umass_softc *sc = CH2SELF(drv->chnl_softc);
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;
	struct isd200_config *cf = &scbus->sc_isd_config;
	int dir;
	struct ata_command *cmd = &xfer->c_ata_c;
	struct ata_cmd ata;

	DPRINTF(("%s\n", __func__));
	DPRINTF(("  r_command=0x%02x timeout=%d flags=0x%x bcount=%d\n",
		 cmd->r_command, cmd->timeout, cmd->flags, cmd->bcount));

	dir = DIR_NONE;
	if (cmd->bcount != 0) {
		if (cmd->flags & AT_READ)
			dir = DIR_IN;
		else
			dir = DIR_OUT;
	}

	if (cmd->bcount > UMASS_MAX_TRANSFER_SIZE) {
		printf("uisdata_exec_command: large datalen %d\n", cmd->bcount);
		cmd->flags |= AT_ERROR;
		return;
	}

	memset(&ata, 0, sizeof(ata));
	ata.ac_signature0 = cf->ATAMajorCommand;
	ata.ac_signature1 = cf->ATAMinorCommand;
	ata.ac_transfer_blocksize = 1;

	switch (cmd->r_command) {
	case WDCC_IDENTIFY:
		ata.ac_register_select |= AC_SelectCommand;
		ata.ac_command = WDCC_IDENTIFY;
		break;
	default:
		printf("uisdata_exec_command: bad command 0x%02x\n",
		       cmd->r_command);
		cmd->flags |= AT_ERROR;
		return;
	}

	DPRINTF(("%s: execute ATA command 0x%02x, drive=%d\n", __func__,
		 ata.ac_command, drv->drive));
	sc->sc_methods->wire_xfer(sc, drv->drive, &ata,
				  sizeof(ata), cmd->data, cmd->bcount, dir,
				  cmd->timeout, 0, uisdata_exec_cb, cmd);
	if (cmd->flags & (AT_POLL | AT_WAIT)) {
#if 0
		if (cmd->flags & AT_POLL)
			printf("%s: AT_POLL not supported\n", __func__);
#endif
		DPRINTF(("%s: tsleep %p\n", __func__, cmd));
		if (tsleep(cmd, PZERO, "uisdataex", 0)) {
			cmd->flags |= AT_ERROR;
			return;
		}
	}
}

int
uisdata_addref(struct ata_drive_datas *drv)
{
	DPRINTF(("%s\n", __func__));
	/* Nothing to do */
	return 0;
}

void
uisdata_delref(struct ata_drive_datas *drv)
{
	DPRINTF(("%s\n", __func__));
	/* Nothing to do */
}

void
uisdata_kill_pending(struct ata_drive_datas *drv)
{
	struct ata_channel *chp = drv->chnl_softc;
	struct umass_softc *sc = CH2SELF(chp);
	struct uisdata_softc *scbus = (struct uisdata_softc *)sc->bus;
	struct ata_xfer *xfer = ata_queue_get_active_xfer(chp);
	struct ata_bio *ata_bio = &xfer->c_bio;

	DPRINTFN(-1,("%s\n", __func__));

	if (xfer == NULL)
		return;

	ata_bio->flags |= ATA_ITSDONE;
	ata_bio->error = ERR_NODEV;
	ata_bio->r_error = WDCE_ABRT;
	(*scbus->sc_drv_data.drv_done)(scbus->sc_drv_data.drv_softc, xfer);
}

int
uisdata_get_params(struct ata_drive_datas *drvp, uint8_t flags,
		struct ataparams *prms)
{
	char tb[DEV_BSIZE];
	struct ata_xfer *xfer;
	int rv;

#if BYTE_ORDER == LITTLE_ENDIAN
	int i;
	uint16_t *p;
#endif

	DPRINTF(("%s\n", __func__));

	memset(tb, 0, DEV_BSIZE);
	memset(prms, 0, sizeof(struct ataparams));

	xfer = ata_get_xfer(drvp->chnl_softc, false);
	if (!xfer) {
		rv = CMD_AGAIN;
		goto out;
	}

	xfer->c_ata_c.r_command = WDCC_IDENTIFY;
	xfer->c_ata_c.timeout = 1000; /* 1s */
	xfer->c_ata_c.flags = AT_READ | flags;
	xfer->c_ata_c.data = tb;
	xfer->c_ata_c.bcount = DEV_BSIZE;
	uisdata_exec_command(drvp, xfer);
	ata_wait_cmd(drvp->chnl_softc, xfer);
	if (xfer->c_ata_c.flags & (AT_ERROR | AT_TIMEOU | AT_DF)) {
		DPRINTF(("uisdata_get_parms: ata_c.flags=0x%x\n",
			 xfer->c_ata_c.flags));
		rv = CMD_ERR;
		goto out;
	}

	/* Read in parameter block. */
	memcpy(prms, tb, sizeof(struct ataparams));
#if BYTE_ORDER == LITTLE_ENDIAN
	/* XXX copied from ata.c */
	/*
	 * Shuffle string byte order.
	 * ATAPI Mitsumi and NEC drives don't need this.
	 */
	if (prms->atap_config != WDC_CFG_CFA_MAGIC &&
	    (prms->atap_config & WDC_CFG_ATAPI) &&
	    ((prms->atap_model[0] == 'N' &&
		prms->atap_model[1] == 'E') ||
	     (prms->atap_model[0] == 'F' &&
		 prms->atap_model[1] == 'X'))) {
		rv = 0;
		goto out;
	}
	for (i = 0; i < sizeof(prms->atap_model); i += 2) {
		p = (u_short *)(prms->atap_model + i);
		*p = ntohs(*p);
	}
	for (i = 0; i < sizeof(prms->atap_serial); i += 2) {
		p = (u_short *)(prms->atap_serial + i);
		*p = ntohs(*p);
	}
	for (i = 0; i < sizeof(prms->atap_revision); i += 2) {
		p = (u_short *)(prms->atap_revision + i);
		*p = ntohs(*p);
	}
#endif
	rv = CMD_OK;

out:
	ata_free_xfer(drvp->chnl_softc, xfer);
	return rv;
}


/* XXX join with wdc.c routine? */
int
uwdprint(void *aux, const char *pnp)
{
	//struct ata_device *adev = aux;
	if (pnp)
		aprint_normal("wd at %s", pnp);
#if 0
	aprint_normal(" channel %d drive %d", adev->adev_channel,
	    adev->adev_drv_data->drive);
#endif
	return UNCONF;
}


#if 0

int umass_wd_attach(struct umass_softc *);

#if NWD > 0
	case UMASS_CPROTO_ISD_ATA:
		return umass_wd_attach(sc);
#endif

#endif
