/*
 * Gemplus GPR400 / GemPC400 software sample.
 * Copyright (c) 2002 Pierrick Hascoet <pierrick.hascoet@hydromel.net>
 *
 * 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  of  the License, or
 *
 * This program is distributed  in  the  hope  that it  will be useful,
 * but  WITHOUT ANY  WARRANTY;  without  even  the  implied warranty of
 *
 * 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.
 */
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#include "gpr400.h"

#define DRIVER_SIZE_MAX		0x6D3
#define DRIVER_HEADER_SIZE	0xA0

static int fd;

/* Put all the bytes to zero for a specified variable. */
void flush(unsigned char data[], int size) { 
	int i;

	for ( i = 0; i < size; i++ )
		data[i] = 0x00;
}

#ifdef DEBUG
/* Generate a sort of hexdump for a specified variable. */
void dump(unsigned char data[], int size) {
	int i,j;

	printf("+ dump (size: %d bytes)\n", size);
	for (i = 0; i < size; i += 16) {

		printf("%08x  ",i);
		for (j = 0; j < 16; j++ ) {
			printf("%02x ", data[i+j]);
		}
		
		printf(" |");
		for (j = 0; j < 16; j++) {
			if ( isprint(data[i+j]) != 0 )
				printf("%c",data[i+j]);
			else
				printf(".");
		}
		printf("|\n");
	}
	printf("\n");
}
#endif

/* read Gemplus ICC driver file. */
int read_driver_file(char *filename, struct gpr400_driver *icc_driver) {
	int fd, size;
	int fsize;			/* file size */
	int hsize = DRIVER_HEADER_SIZE;	/* driver header size */
	int dsize;			/* driver data size */
	struct stat fileInfo;

	if ( stat(filename, &fileInfo) != 0 ) {
		fprintf(stderr, "%s: %s\n", filename, sys_errlist[errno]);
		return(-errno);
	}

	fsize = fileInfo.st_size;
	dsize = fsize - hsize;
	icc_driver->size = dsize;

	printf("+ read_driver_file:\n\t");
	printf("   filename : %s\n\t", filename);
	printf("  file size : %d (0x%04x) bytes.\n\t", fsize, fsize);
	printf("  data size : %d (0x%04x) bytes.\n\t", dsize, dsize);
	printf("   ram size : %d (0x%04x) bytes.\n\n",
			GPR400_RAM_SIZE, GPR400_RAM_SIZE);

	/* read file informations to get file size. */
	if ( dsize > DRIVER_SIZE_MAX ) {
		fprintf(stderr, "Error: not enough space in GPR"
				"Ram to load ICC Driver file.\n");
		return(-EFBIG);
	}

	/* open the icc driver file. */
	if ( ( fd = open(filename, O_RDONLY) ) == -1 ) {
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
		return (-errno);
	}

	/* read header driver */
	size = read(fd, icc_driver->header, hsize);
#ifdef DEBUG
	printf("+ load header (size: %d)\n", size);
	dump(icc_driver->header, hsize);
#endif

	/* read icc driver file. */
	size = read(fd, icc_driver->ram->data, dsize);
	icc_driver->ram->data[dsize] = '\0';

#ifdef DEBUG
	printf("+ load data (size: %d)\n", size);
	dump(icc_driver->ram->data, size);
#endif

	/* close file descriptor. */
	close(fd);

	/* return size */
	return(size);
}

/* load an Icc driver file to the gpr400's ram */
int load_driver_file(int fd, char *filename) {
	int file_size, bytes_loaded, ret;
	struct gpr400_ram ram;			/* ram data structure */
	struct gpr400_driver ic_driver;		/* icc driver structure */

	/* inform ic_driver, we want to use ram. */
	ic_driver.ram = &ram;			

	/* clear all possible residual bytes. */
	flush(ram.data, GPR400_RAM_SIZE);
	flush(ic_driver.header, 160);

	/* Choose to load driver a the base address of the ram in the
	 * attribute space memory.
	 */
	ic_driver.adrh = 0x01;
	ic_driver.adrl = 0x00;
	ic_driver.dir = ( GPR400_ERASE_MEMORY | GPR400_LOAD_FROM_HOST );
	
	/* read the driver file */
	file_size = read_driver_file(filename, &ic_driver);
	if ( file_size < 0 )
		return(file_size);

	/* call ioctl */
	ret = ioctl(fd, GPR400_IOCTL_LOAD_MEM, &ic_driver);
	if ( ret != 0 ) {
		fprintf(stderr,"Error: GPR400_IOCTL_LOAD_MEM failed\n");
		return(ret);
	}

	/* return file size */
	return (file_size);
}

/* Ask for gpr_status:
 *	This can be improve to reduce time in kernel space by calling:
 *		o GPR400_IOCTL_FIRMWARE_STATUS
 *		o GPR400_IOCTL_CHECKSUM_STATUS
 *		o GPR400_IOCTL_SMARTCARD_STATUS
 */
int gpr_status() {
	int ret;
	struct gpr400_status readerStatus;

	ret = ioctl(fd,GPR400_IOCTL_STATUS, &readerStatus);
	if ( ret != 0 ) {
		fprintf(stderr,"Error: GPR400_STATUS ioctl failed\n");
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
		return(-errno);
	}
	printf("GPR status (firmware informations)\n");
	printf("\tversion: %02x\n",
		readerStatus.firmware.version);
	printf("\t  flash: %02x\n",
		readerStatus.firmware.flash_presence);
	printf("\tgen_reg: %02x\n",
		readerStatus.firmware.gen_reg);
	printf("\t    dir: %02x\n",
		readerStatus.firmware.dir);
	printf("\t   adrh: %02x\n",
		readerStatus.firmware.adrh);
	printf("\t   adrl: %02x\n",
		readerStatus.firmware.adrl);
	printf("\t manfid: %02x\n\n",
		readerStatus.firmware.manufacturer);

	printf("Checksum value\n");
	printf("\trom: %02x\n",
		readerStatus.checksum.rom);
	printf("\tram: %02x\n",
		readerStatus.checksum.ram);

	if ( readerStatus.firmware.flash_presence )
		printf("\tflash: %02x\n",
			readerStatus.checksum.flash);

	printf("\nSmartCard setting:\n");
	printf("\treg1: %02x\n",
			readerStatus.smartcard.gen_reg1);
	printf("\treg2: %02x\n",
			readerStatus.smartcard.gen_reg2);
	printf("\t clk: %02x\n\n",
			readerStatus.smartcard.clk_ctr_reg);
}

/* Read the gpr400 memory */
void read_memory() {
	int ret, i;
	struct gpr400_ram ram;
	struct gpr400_memory mem = {0};

	/* prepare to use only ram */
	mem.ram = &ram;

	flush(mem.ram->data, GPR400_RAM_SIZE);

	mem.dir = 0x00;
	mem.adrh = 0x00;
	mem.adrl = 0x00;
	mem.size = 0xFF; /* FIXME: size > 0xFF must be fixed in driver. */

	/* ioctl call */
	ret = ioctl(fd, GPR400_IOCTL_READ_MEM, &mem);
	if (ret != 0) {
		fprintf(stderr,"Error: GPR400_IOCTL_READ_MEMORY failed\n");
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
		return;
	}

#ifdef DEBUG
	printf("+ read memory:\n");
	dump(mem.ram->data, GPR400_RAM_SIZE);
#endif
}	

/* Activate driver sequence: must be used after a load sequence. */
int activate_driver() {
	int ret = -1;
	struct gpr400_activate_driver act;

	act.dir = 0x80;  /* Activate flag ! */
	act.adrh = 0x01; /* offset in ram : ADRH */ 
	act.adrl = 0x00; /* offset in ram : ADRL */ 

	/* call iocl */
	ret = ioctl(fd, GPR400_IOCTL_ACT_DRV, &act);
	if ( ret != 0 ) {
		fprintf(stderr,"Error: GPR400_IOCTL_ACT_DRV call failed\n");
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
	}
	return (ret);
}

/* select an iso driver */
int select_iso_driver() {
	int ret = -1;

	ret = ioctl(fd, GPR400_IOCTL_SELECT_ISO_MODE);
	if ( ret != 0 ) {
		fprintf(stderr,"Error: GPR400_IOCTL_SELECT_ISO_MODE"
				"call failed\n");
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
	}
	return (ret);
}

/* select a driver loaded in memory. */
int select_memory_driver() {
	int ret = -1;

	ret = ioctl(fd, GPR400_IOCTL_SELECT_MEM_MODE);
	if ( ret != 0 ) {
		fprintf(stderr,"Error: GPR400_SELECT_MEM_MODE call failed\n");
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
	}
	return (ret);
	
}

/* open a session with card */
int open_session() {
	int ret, i;
	struct gpr400_atr atr;

	flush(atr.data, 62);

	ret = ioctl(fd, GPR400_IOCTL_OPEN, &atr);
	if ( ret != 0 ) {
		fprintf(stderr,"Error: GPR400_IOCTL_OPEN call failed\n");
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
	} else {

		printf("Open session:\n");
		printf("\tstatus: %02x\n", atr.status);
		printf("\tlength: %02x\n", atr.len);
		printf("\t  data: ");
		for ( i=0; i < atr.len; i++)
			printf("%02x ", atr.data[i]);
		printf("\n\n");
	}
	
	return (ret);
}

#ifdef DEBUG
/* call the internal ram dump ioctl */
void dump_ram() {
	int ret = -1;
	struct gpr400_ram ram;

	flush(ram.data, 2016);

	ret = ioctl(fd,GPR400_IOCTL_DUMP_RAM, &ram);
	if ( ret != 0 ) {
		fprintf(stderr,"Error: GPR400_RAM ioctl failed\n");
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
	} else {
		printf("dump gpr400 internal ram:\n");
		dump(ram.data, 2016);
		printf("\n\n");
	}
}
#endif

int main() {
	int ret;
	char device[] = "/dev/gpr400";

	if ( (fd = open(device,O_RDWR)) < 0 ) {
		fprintf(stderr,"Error: %s\n", sys_errlist[errno]);
		exit(errno);
	}

	gpr_status();

#ifdef DEBUG
	dump_ram();
#endif

	/* Change the driver file to match your card */
	ret = load_driver_file(fd, "/tmp/Iccdrv03.gpr"); /* GPM256 */
	if ( ret < 0 ) exit(ret);

	activate_driver();

	select_memory_driver();

	gpr_status();

#ifdef DEBUG	
	dump_ram();
#endif

	open_session();

	gpr_status();

	close(fd);
}
