/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Some devices have two luns with entirely different drivers for each.
 * Sometimes both drivers need private data, and at first sight
 * us->extra does not suffice.
 * One solution would be to make us->extra point at an array with
 * per_lun data. However, mdharm prefers a setup where the per_device
 * struct is saved at the entrance of the transport routine, and replaced
 * by the per_lun data, where things are restored again upon exit from
 * the transport routine. He promises that things are serialized so that
 * the transport routine can not be active for two luns simultaneously.
 * Yuk. - aeb
 */

#include <linux/config.h>
#include "transport.h"
#include "usb.h"
#include "debug.h"
#include "twoluns.h"

#define NLUNS 2

struct per_lun_data {
	void *data;
	extra_data_destructor kill;
};

static void
destroy_2luns(void *extra) {
	struct per_lun_data *p = (struct per_lun_data *) extra;
	int i;

	if (!p)
		return;
	for (i=0; i<NLUNS; i++) {
		if (p[i].data) {
			if (p[i].kill)
				p[i].kill(p[i].data);
			kfree(p[i].data);
		}
	}
}

/*
 * Save per device data, set per lun data.
 * The caller has checked that lun is valid.
 */
static void
set_per_lun_data(struct us_data *us, int lun, struct per_lun_data *sv) {
	struct per_lun_data *p;

	if (us->extra == NULL) {
		void *ptr = kmalloc(NLUNS * sizeof(struct per_lun_data),
				    GFP_NOIO);
		if (ptr == NULL)
			return;		/* out of memory */
		memset(ptr, 0, NLUNS * sizeof(struct per_lun_data));
		us->extra = ptr;
		us->extra_destructor = destroy_2luns;
	}

	sv->data = us->extra;
	sv->kill = us->extra_destructor;
	us->extra_destructor = 0;

	p = (struct per_lun_data *) sv->data;
	us->extra = p[lun].data;
}

static void
set_per_device_data(struct us_data *us, int lun, struct per_lun_data *sv) {
	struct per_lun_data *p = (struct per_lun_data *) sv->data;

	p[lun].data = us->extra;
	p[lun].kill = us->extra_destructor;
	us->extra = sv->data;
	us->extra_destructor = sv->kill;
}

/*
 * Below a few examples of this situation.
 */
static int
twolun_transport(Scsi_Cmnd *srb, struct us_data *us, trans_cmnd *t) {
	struct per_lun_data sv_data;
	unsigned int lun;
	int rc;

	if(srb == NULL)
		return USB_STOR_TRANSPORT_ERROR;

	lun = srb->lun;
	if (lun > 1 || t[lun] == NULL) {
		US_DEBUGP("twoluns: Invalid LUN %d\n", lun);
		return USB_STOR_TRANSPORT_ERROR;
	}
	US_DEBUGP("twoluns: LUN=%d\n", lun);

	set_per_lun_data(us, lun, &sv_data);

	rc = (t[lun])(srb, us);

	set_per_device_data(us, lun, &sv_data);

	return rc;
}

/*
 * Microtech DPCM-USB CompactFlash/SmartMedia reader
 *
 * First release: mdharm
 *
 * Current development and maintenance by:
 *   (c) 2000 Brian Webb (webbb@earthlink.net)
 *
 * This device contains both a CompactFlash card reader, which
 * uses the Control/Bulk w/o Interrupt protocol and a SmartMedia
 * card reader that uses the same protocol as the SDDR09.
 */

#include "sddr09.h"

#ifndef CONFIG_USB_STORAGE_SDDR09
#define sddr09_transport NULL
#endif

static trans_cmnd dpcm_t[2] = {
	usb_stor_CB_transport,
	sddr09_transport,
};

int dpcm_transport(Scsi_Cmnd *srb, struct us_data *us) {
	return twolun_transport(srb, us, dpcm_t);
}

/*
 * Next the 07c4:a109 dual SM/CF reader from Acomdata or Apacer
 */
#include "apacer.h"
#include "datafab.h"

#ifndef CONFIG_USB_STORAGE_ACOMDATA
#define apacer_sm_transport NULL
#endif
#ifndef CONFIG_USB_STORAGE_DATAFAB
#define datafab_transport NULL
#endif

static trans_cmnd apacer_t[2] = {
        apacer_sm_transport,
        datafab_transport,
};

int
apacer_transport(Scsi_Cmnd *srb, struct us_data *us) {
	return twolun_transport(srb, us, apacer_t);
}
