ccid: Introduce ccid_slot_ops + implement simulator/stub for it

This adds a new interface to the CCID implementation, on the card/slot
side.  The purpose of this interface (based on function pointers) is
to allow for different real hardware or virtual implementations.

Change-Id: I2c38aa69594a3b22bb5b5e256edfb48481e42793
diff --git a/ccid/Makefile b/ccid/Makefile
index cd8321f..2194180 100644
--- a/ccid/Makefile
+++ b/ccid/Makefile
@@ -1,6 +1,6 @@
 CFLAGS=-Wall -g
 
-ccid_functionfs: ccid_main_functionfs.o ccid_proto.o ccid_device.o
+ccid_functionfs: ccid_main_functionfs.o ccid_proto.o ccid_device.o ccid_slot_sim.o
 	$(CC) $(CFLAGS) -lasan -losmocore -ltalloc -laio -o $@ $^
 
 %.o: %.c
diff --git a/ccid/ccid_device.c b/ccid/ccid_device.c
index 87d99ce..d9e59b0 100644
--- a/ccid/ccid_device.c
+++ b/ccid/ccid_device.c
@@ -421,7 +421,7 @@
 	uint8_t seq = u->icc_power_off.hdr.bSeq;
 	struct msgb *resp;
 
-	/* FIXME */
+	cs->ci->slot_ops->set_power(cs, false);
 	resp = ccid_gen_slot_status(cs, seq, CCID_CMD_STATUS_OK, 0);
 	return ccid_slot_send_unbusy(cs, resp);
 }
@@ -434,7 +434,7 @@
 	uint8_t seq = u->xfr_block.hdr.bSeq;
 	struct msgb *resp;
 
-	/* FIXME */
+	/* FIXME: handle this asynchronously */
 	resp = ccid_gen_data_block(cs, seq, CCID_CMD_STATUS_OK, 0, NULL, 0);
 	return ccid_slot_send_unbusy(cs, resp);
 }
@@ -460,8 +460,11 @@
 	uint8_t seq = u->reset_parameters.hdr.bSeq;
 	struct msgb *resp;
 
-	/* FIXME: copy default parameters from somewhere */
+	/* copy default parameters from somewhere */
 	/* FIXME: T=1 */
+	cs->ci->slot_ops->set_params(cs, CCID_PROTOCOL_NUM_T0, cs->default_pars);
+	cs->pars = *cs->default_pars;
+
 	resp = ccid_gen_parameters_t0(cs, seq, CCID_CMD_STATUS_OK, 0);
 	return ccid_slot_send_unbusy(cs, resp);
 }
@@ -480,30 +483,29 @@
 	switch (spar->bProtocolNum) {
 	case CCID_PROTOCOL_NUM_T0:
 		rc = decode_ccid_pars_t0(&pars_dec, &spar->abProtocolData.t0);
-		if (rc < 0) {
-			LOGP(DCCID, LOGL_ERROR, "SetParameters: Unable to parse T0: %d\n", rc);
-			resp = ccid_gen_parameters_t0(cs, seq, CCID_CMD_STATUS_FAILED, -rc);
-			goto out;
-		}
-		/* FIXME: validate parameters; abort if they are not supported */
-		cs->pars = pars_dec;
-		resp = ccid_gen_parameters_t0(cs, seq, CCID_CMD_STATUS_OK, 0);
 		break;
 	case CCID_PROTOCOL_NUM_T1:
 		rc = decode_ccid_pars_t1(&pars_dec, &spar->abProtocolData.t1);
-		if (rc < 0) {
-			LOGP(DCCID, LOGL_ERROR, "SetParameters: Unable to parse T1: %d\n", rc);
-			resp = ccid_gen_parameters_t1(cs, seq, CCID_CMD_STATUS_FAILED, -rc);
-			goto out;
-		}
-		/* FIXME: validate parameters; abort if they are not supported */
-		cs->pars = pars_dec;
-		resp = ccid_gen_parameters_t1(cs, seq, CCID_CMD_STATUS_OK, 0);
 		break;
 	default:
 		LOGP(DCCID, LOGL_ERROR, "SetParameters: Invalid Protocol 0x%02x\n",spar->bProtocolNum);
 		resp = ccid_gen_parameters_t0(cs, seq, CCID_CMD_STATUS_FAILED, 0);
-		break;
+		goto out;
+	}
+
+	if (rc < 0) {
+		LOGP(DCCID, LOGL_ERROR, "SetParameters: Unable to parse: %d\n", rc);
+		resp = ccid_gen_parameters_t0(cs, seq, CCID_CMD_STATUS_FAILED, -rc);
+		goto out;
+	}
+
+	/* validate parameters; abort if they are not supported */
+	rc = cs->ci->slot_ops->set_params(cs, spar->bProtocolNum, &pars_dec);
+	if (rc < 0) {
+		resp = ccid_gen_parameters_t0(cs, seq, CCID_CMD_STATUS_FAILED, -rc);
+	} else {
+		cs->pars = pars_dec;
+		resp = ccid_gen_parameters_t0(cs, seq, CCID_CMD_STATUS_OK, 0);
 	}
 out:
 	return ccid_slot_send_unbusy(cs, resp);
@@ -529,7 +531,7 @@
 	uint8_t seq = u->icc_clock.hdr.bSeq;
 	struct msgb *resp;
 
-	/* FIXME: Actually Stop/Start the clock */
+	cs->ci->slot_ops->set_clock(cs, u->icc_clock.bClockCommand);
 	resp = ccid_gen_slot_status(cs, seq, CCID_CMD_STATUS_OK, 0);
 	return ccid_slot_send_unbusy(cs, resp);
 }
@@ -542,7 +544,7 @@
 	uint8_t seq = u->t0apdu.hdr.bSeq;
 	struct msgb *resp;
 
-	/* FIXME */
+	/* FIXME: Required for APDU level exchange */
 	//resp = ccid_gen_slot_status(cs, seq, CCID_CMD_STATUS_OK, 0);
 	resp = ccid_gen_slot_status(cs, seq, CCID_CMD_STATUS_FAILED, CCID_ERR_CMD_NOT_SUPPORTED);
 	return ccid_slot_send_unbusy(cs, resp);
@@ -608,10 +610,17 @@
 	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
 	const struct ccid_header *ch = (const struct ccid_header *) u;
 	uint8_t seq = u->set_rate_and_clock.hdr.bSeq;
+	uint32_t freq_hz = osmo_load32le(&u->set_rate_and_clock.dwClockFrequency);
+	uint32_t rate_bps = osmo_load32le(&u->set_rate_and_clock.dwDataRate);
 	struct msgb *resp;
+	int rc;
 
-	/* FIXME */
-	resp = ccid_gen_clock_and_rate(cs, seq, CCID_CMD_STATUS_OK, 0, 9600, 2500000);
+	/* FIXME: which rate to return in failure case? */
+	rc = cs->ci->slot_ops->set_rate_and_clock(cs, freq_hz, rate_bps);
+	if (rc < 0)
+		resp = ccid_gen_clock_and_rate(cs, seq, CCID_CMD_STATUS_FAILED, -rc, 9600, 2500000);
+	else
+		resp = ccid_gen_clock_and_rate(cs, seq, CCID_CMD_STATUS_OK, 0, rate_bps, freq_hz);
 	return ccid_slot_send_unbusy(cs, resp);
 }
 
@@ -660,6 +669,10 @@
 
 	/* TODO: enqueue into the per-slot specific input queue */
 
+	/* call pre-processing call-back function; allows reader to update state */
+	if (ci->slot_ops->pre_proc_cb)
+		ci->slot_ops->pre_proc_cb(cs, msg);
+
 	switch (ch->bMessageType) {
 	case PC_to_RDR_GetSlotStatus:
 		if (len != sizeof(u->get_slot_status))
@@ -751,8 +764,8 @@
 	return -1;
 }
 
-void ccid_instance_init(struct ccid_instance *ci, const struct ccid_ops *ops, const char *name,
-			void *priv)
+void ccid_instance_init(struct ccid_instance *ci, const struct ccid_ops *ops,
+			const struct ccid_slot_ops *slot_ops, const char *name, void *priv)
 {
 	int i;
 
@@ -761,7 +774,8 @@
 		cs->slot_nr = i;
 		cs->ci = ci;
 	}
-	ci->ops= ops;
+	ci->ops = ops;
+	ci->slot_ops = slot_ops;
 	ci->name = name;
 	ci->priv = priv;
 }
diff --git a/ccid/ccid_device.h b/ccid/ccid_device.h
index d0fcdc0..e7bb557 100644
--- a/ccid/ccid_device.h
+++ b/ccid/ccid_device.h
@@ -2,6 +2,8 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+#include "ccid_proto.h"
+
 enum {
 	DCCID,
 	DUSB,
@@ -55,9 +57,11 @@
 	bool cmd_busy;
 	/* decided CCID parameters */
 	struct ccid_pars_decoded pars;
+	/* default parameters; applied on ResetParameters */
+	const struct ccid_pars_decoded *default_pars;
 };
 
-/* CCID operations */
+/* CCID operations provided by USB transport layer */
 struct ccid_ops {
 	/* msgb ownership in below functions is transferred, i.e. whoever
 	 * provides the callback function must make sure to msgb_free() them
@@ -66,17 +70,33 @@
 	int (*send_int)(struct ccid_instance *ci, struct msgb *msg);
 };
 
+/* CCID operations provided by actual slot hardware */
+struct ccid_slot_ops {
+	/* called once on start-up for initialization */
+	int (*init)(struct ccid_slot *cs);
+	/* called before processing any command for a slot; used e.g. to
+	 * update the (power/clock/...) status from the hardware */
+	void (*pre_proc_cb)(struct ccid_slot *cs, struct msgb *msg);
+
+	void (*set_power)(struct ccid_slot *cs, bool enable);
+	void (*set_clock)(struct ccid_slot *cs, enum ccid_clock_command cmd);
+	int (*set_params)(struct ccid_slot *cs, enum ccid_protocol_num proto,
+			  const struct ccid_pars_decoded *pars_dec);
+	int (*set_rate_and_clock)(struct ccid_slot *cs, uint32_t freq_hz, uint32_t rate_bps);
+};
+
 /* An instance of CCID (i.e. a card reader device) */
 struct ccid_instance {
 	/* slots within the reader */
 	struct ccid_slot slot[NR_SLOTS];
 	/* set of function pointers implementing specific operations */
 	const struct ccid_ops *ops;
+	const struct ccid_slot_ops *slot_ops;
 	const char *name;
 	/* user-supplied opaque data */
 	void *priv;
 };
 
-void ccid_instance_init(struct ccid_instance *ci, const struct ccid_ops *ops, const char *name,
-			void *priv);
+void ccid_instance_init(struct ccid_instance *ci, const struct ccid_ops *ops,
+			const struct ccid_slot_ops *slot_ops, const char *name, void *priv);
 int ccid_handle_out(struct ccid_instance *ci, struct msgb *msg);
diff --git a/ccid/ccid_main_functionfs.c b/ccid/ccid_main_functionfs.c
index e32fbf7..06ab0f3 100644
--- a/ccid/ccid_main_functionfs.c
+++ b/ccid/ccid_main_functionfs.c
@@ -139,6 +139,7 @@
 #include <osmocom/core/logging.h>
 
 #include "ccid_device.h"
+#include "ccid_slot_sim.h"
 
 #ifndef FUNCTIONFS_SUPPORTS_POLL
 #include <libaio.h>
@@ -509,7 +510,7 @@
 
 	signal(SIGUSR1, &signal_handler);
 
-	ccid_instance_init(&ci, &c_ops, "", &ufh);
+	ccid_instance_init(&ci, &c_ops, &slotsim_slot_ops, "", &ufh);
 	ufh.ccid_handle = &ci;
 
 	if (argc < 2) {
diff --git a/ccid/ccid_slot_sim.c b/ccid/ccid_slot_sim.c
new file mode 100644
index 0000000..d7a5952
--- /dev/null
+++ b/ccid/ccid_slot_sim.c
@@ -0,0 +1,72 @@
+/* Simulated CCID card slot. This is used in absence of a real hardware back-end
+ * in order to test the CCID firmware codebase in a virtual environment */
+
+#include "ccid_device.h"
+
+static const struct ccid_pars_decoded slotsim_def_pars = {
+	.fi = 0,
+	.di = 0,
+	.clock_stop = CCID_CLOCK_STOP_NOTALLOWED,
+	.inverse_convention = false,
+	.t0 = {
+		.guard_time_etu = 0,
+		.waiting_integer = 0,
+	},
+	/* FIXME: T=1 */
+};
+
+static void slotsim_pre_proc_cb(struct ccid_slot *cs, struct msgb *msg)
+{
+	/* do nothing; real hardware would update the slot related state here */
+}
+
+static void slotsim_set_power(struct ccid_slot *cs, bool enable)
+{
+	if (enable) {
+		cs->icc_powered = true;
+		/* FIXME: What to do about ATR? */
+	} else {
+		cs->icc_powered = false;
+	}
+}
+
+static void slotsim_set_clock(struct ccid_slot *cs, enum ccid_clock_command cmd)
+{
+	/* FIXME */
+	switch (cmd) {
+	case CCID_CLOCK_CMD_STOP:
+		break;
+	case CCID_CLOCK_CMD_RESTART:
+		break;
+	default:
+		OSMO_ASSERT(0);
+	}
+}
+
+static int slotsim_set_params(struct ccid_slot *cs, enum ccid_protocol_num proto,
+				const struct ccid_pars_decoded *pars_dec)
+{
+	/* we always acknowledge all parameters */
+	return 0;
+}
+
+static int slotsim_set_rate_and_clock(struct ccid_slot *cs, uint32_t freq_hz, uint32_t rate_bps)
+{
+	/* we always acknowledge all rates/clocks */
+	return 0;
+}
+
+static int slotsim_init(struct ccid_slot *cs)
+{
+	cs->default_pars = &slotsim_def_pars;
+	return 0;
+}
+
+const struct ccid_slot_ops slotsim_slot_ops = {
+	.init = slotsim_init,
+	.pre_proc_cb = slotsim_pre_proc_cb,
+	.set_power = slotsim_set_power,
+	.set_clock = slotsim_set_clock,
+	.set_params = slotsim_set_params,
+	.set_rate_and_clock = slotsim_set_rate_and_clock,
+};
diff --git a/ccid/ccid_slot_sim.h b/ccid/ccid_slot_sim.h
new file mode 100644
index 0000000..6217756
--- /dev/null
+++ b/ccid/ccid_slot_sim.h
@@ -0,0 +1,3 @@
+#pragma once
+
+extern struct ccid_slot_ops slotsim_slot_ops;