Some Linux experiments with the ACR38 smartcards reader

Trying to read smartcards using only libusb. For fun and learning.

Nom :

01 avril 2007

Using libusb with the ACR38...

Connecting with libusb

!!! DRAFT !!!

The first thing to do is to find the device on the USB bus. This is described in various 'libusb tutorials' (see here to find one).


/*
** ACR38_get_dev()
** find the first ACR38 device
*/
struct usb_device *ACR38_get_dev()
{
struct usb_bus *busses, *bus;
struct usb_device *dev;

usb_init();
usb_find_busses();
usb_find_devices();

busses = usb_get_busses();
for (bus = busses; bus; bus = bus->next)
{
for (dev = bus->devices; dev; dev = dev->next)
{
if (dev->descriptor.idVendor == 0x072f && /* ACS */
dev->descriptor.idProduct == 0x9000) /* ACR38 */
{
printf("found ACR38\n");
break;
}
}
if (dev)
break;
}
return(dev);
}

An application now looks something like this :

main()
{
struct usb_device *dev;
struct usb_dev_handle* udp;

if ((dev = ACR38_get_dev()) == NULL)
exit(-1);
if ((udp = usb_open(dev)) == NULL)
{
error("usb_open");
exit(-1);
}
/*
** ... do something with libusb functions (using udp)
*/
if (usb_close(udp) < 0)
{
perror("usb_close");
exit(-1);
}
}

Now, one has to read the card manufacturer's Reference document, (REF_ACR38x_v1.9.pdf) to find out what to do next. By example, one can send simple commands to the card reader, or ask if there is a card in the reader... 'lsusb -v' gives the endpoints adresses, there is no need to write code to get it.

#define TIMEOUT 20*100 /*HZ*/

#define EP_IN 0x82 /* bulk in endpoint */
#define EP_OUT 0x02 /* bulk out endpoint */
#define EP_INT_IN 0x81 /* interrupt endpoint */
/*
** ACR38_get_acr_stat(udp)
** get information about the card reader
** GET_ACR_STAT 8.1.1 in REF_ACR38x_v1.9.pdf
*/
u_char *ACR38_get_acr_stat(struct usb_dev_handle* udp)
{
u_char buf_out[] = { 0x01, 0x01, 0x00, 0x00 };
static u_char buf_in[64];

if (usb_bulk_write(udp, EP_OUT, (char *)buf_out, 4, TIMEOUT) < 0)
{
perror("usb_bulk_write");
return(NULL);
}
if (usb_bulk_read(udp, EP_IN, (char *)buf_in, 20, TIMEOUT) < 0)
{
perror("usb_bulk_read");
return(NULL);
}
return(buf_in); /* 20 bytes to be interpreted elsewhere */
}

One has to have the permission to handle the USB device, and, when there is a card in the reader, ACR38_get_acr_stat() returns :

00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 (index)
01 00 00 10 41 43 52 33 38 2d 31 30 34 30 ff ff 00 06 0c 01 (returned)

Last byte is 0 when there is no card in the reader (and will be 3 when the card is powered).

It seems that using the next code crashes the reader... So, I won't use EP_INT_IN any more until I find an explanation. It doesn't seem to be used in the 'bel-eid-fr-linux.tar.gz' sources either...

/*
** ACR38_getstatus(udp)
** tells if there is a card in the reader
*/
u_char *ACR38_getstatus(struct usb_dev_handle *udp)
{
static u_char buf_in[8];

if (usb_interrupt_read(udp, EP_INT_IN, (char *)buf_in, 8, TIMEOUT) < 0)
{
perror("usb_interrup_read");
return(NULL);
}
return(buf_in); /* card status message 7.3 */
}

When it works, as documented, ACR38_getstatus() returns 01 c1 when there is a card in the reader and 01 c0 when there is none. But I can't do it twice, it seems that I have to unplug/replug the device to get it working again... Reads return an error 'Resource temporarily unavailable'... Why? That's the question!...

This still is experimental.