Программирование драйвера для ридера смарт карт (Serial) под linux

По долгу службы пришлось мне программировать драйвер для ридеров,

делал его я под ubuntu, но для красной шляпы (RedHat)

Для этого нам понадобиться

NetBeans для c++, я использую последнюю версию 7.0

Скачать можно от сюда http://netbeans.org/downloads/index.html

Скелеты - исходники самого драйвера и работы с ком портом

PC/SC Driver Developers Kit(1.0.0)

CT-API Driver Skeleton(1.0.0)

Скачать с http://www.linuxnet.com/sourcedrivers.html

Настройка проекта

В NetBeans-е создаем проект C++ Application

даем имя ReaderDriver

рис.1

заходим в папку проекта и для удобства создаем в ней папку, куда распаковываем наши скелеты, на рис.2 показаны файлы которые нам пригодятся

рис.2

далее в проект добавляем файлы,

свойство проекта, С компилятор, Include Directories

рис.3

Пробуем построить проект, должно компилироваться

Перед тем как начать программировать расскажу как работает вся эта система, на примере ком порта.

Демон который отвечает за работу ридеров pcscd, через него идет все общение с картами и ридерами, он в свою очередь подключает все ридеры которые настроены в его конфигурации и опрашивает их с помощью драйвера.

Файлы настроек ридеров находятся /etc/reader.conf.d/, от туда берет настройки ридеров,

имя ридера, имя портя к которому подключен, имя драйвера, имя канала порта(не знаю как точно его назвать)

Мы попробуем написать универсальный драйвер который будет опрашивать все ком порты и скорости.

В файле ifdhandler.c находятся функции, с которыми работает демон.

Кратко опишу их

RESPONSECODE IFDHCreateChannel ( DWORD Lun, DWORD Channel )

Вызывается при инициализации, то есть когда ридер был обнаружен(USB) или при запуске демона

В нашем случае, в ней нужно найти ком порт и скорость, к которому подключен ридер

RESPONSECODE IFDHCloseChannel ( DWORD Lun )

Противоположна IFDHCreateChannel,

В нашем случае, будет вызываться при закрытии pcscd, в ней нужно отключить ком порт, и вытащить карточку из ридера(если он моторизированный)

RESPONSECODE IFDHGetCapabilities ( DWORD Lun, DWORD Tag,

PDWORD Length, PUCHAR Value )

В этой функции pcscd опрашивает настройки драйвера, ридера, карточки

В ней будем отдавать ATR карточки

RESPONSECODE IFDHSetCapabilities ( DWORD Lun, DWORD Tag,

DWORD Length, PUCHAR Value )

Противоположна IFDHGetCapabilities, мы её не будем использовать

RESPONSECODE IFDHSetProtocolParameters ( DWORD Lun, DWORD Protocol,

UCHAR Flags, UCHAR PTS1,

UCHAR PTS2, UCHAR PTS3)

В этой функции pcscd присылает номер протокола и по atr нам надо вычислить PTS1,2,3

но я это не использую т. к. работаю с одним видом карточек, по желанию можете прочитать поподробней о ней в инструкции

В ней будем проверять поддерживает ли ридер протокол

RESPONSECODE IFDHPowerICC ( DWORD Lun, DWORD Action,

PUCHAR Atr, PDWORD AtrLength )

В этой функции pcscd просит драйвер подключить питание к карточке, отключить или сделать ресет

RESPONSECODE IFDHTransmitToICC ( DWORD Lun, SCARD_IO_HEADER SendPci,

PUCHAR TxBuffer, DWORD TxLength,

PUCHAR RxBuffer, PDWORD RxLength,

PSCARD_IO_HEADER RecvPci )

С помощью этой функции, идет работа с карточкой, на карточку посылаются команды APDU

RESPONSECODE IFDHControl ( DWORD Lun, PUCHAR TxBuffer,

DWORD TxLength, PUCHAR RxBuffer,

PDWORD RxLength )

С помощью этой функции производится контроль над ридером

RESPONSECODE IFDHICCPresence( DWORD Lun )

Эта самая главная функция и она вызываться каждые пол секунды,

в ней происходит проверка вставлена карта или нет

Файл serial.c содержит функции для работы с ком портом

я его переделал под себя и убрал лишние комментарии и MacOS код

Добавляем в IO_InitializePort нужные параметры

int IO_InitializePort(int baud, int bits, char parity, const char* port)

{

HANDLE handle;

struct termios newtio;

handle = open(port, O_RDWR | O_NOCTTY); /* Try user input depending on port */

if (handle < 0) /* Problems with /dev/smartcard */

{

return FALSE;

}

memset(&newtio, 0, sizeof (newtio));

switch (baud)

{

case 1200: /* Baudrate 1200 */

newtio.c_cflag = B1200;

break;

case 2400: /* Baudrate 2400 */

newtio.c_cflag = B2400;

break;

case 4800: /* Baudrate 4800 */

newtio.c_cflag = B4800;

break;

case 9600: /* Baudrate 9600 */

newtio.c_cflag = B9600;

break;

case 19200: /* Baudrate 19200 */

newtio.c_cflag = B19200;

break;

case 38400: /* Baudrate 38400 */

newtio.c_cflag = B38400;

break;

default:

close(handle);

return FALSE;

}

switch (bits)

{

case 5: /* Five bits */

newtio.c_cflag |= CS5;

break;

case 6: /* Six bits */

newtio.c_cflag |= CS6;

break;

case 7: /* Seven bits */

newtio.c_cflag |= CS7;

break;

case 8: /* Eight bits */

newtio.c_cflag |= CS8;

break;

default:

close(handle);

return FALSE;

}

switch (parity)

{

case 'O': /* Odd Parity */

newtio.c_cflag |= PARODD | PARENB | INPCK;

break;

case 'E': /* Even Parity */

newtio.c_cflag &= (~PARODD);

newtio.c_cflag |= PARENB | INPCK;

break;

case 'N': /* No Parity */

break;

default:

close(handle);

return FALSE;

}

newtio.c_cflag |= CREAD | HUPCL | CLOCAL;

newtio.c_iflag &= ~(IGNPAR | PARMRK | INLCR | IGNCR | ICRNL);

newtio.c_iflag |= BRKINT;

newtio.c_lflag &= ~(ICANON | ECHO | ISTRIP);

newtio.c_oflag = 0;

newtio.c_lflag = 0;

newtio.c_cc[VMIN] = 1;

newtio.c_cc[VTIME] = 0;

if (tcflush(handle, TCIFLUSH) < 0) /* Flush the serial port */

{

close(handle);

return FALSE;

}

if (tcsetattr(handle, TCSANOW, &newtio) < 0) /* Set the parameters */

{

close(handle);

return FALSE;

}

ioport.handle = handle; /* Record the handle */

ioport.baud = baud; /* Record the baudrate */

ioport.bits = bits; /* Record the bits */

ioport.parity = parity; /* Record the parity */

ioport.blocktime = 1; /* Default Beginning Blocktime */

return TRUE;

}

IO_Read переделал для чтения одного байта

bool IO_Read(BYTE *response)

{

fd_set rfds;

struct timeval tv;

int rval;

HANDLE handle;

// int count = 0;

handle = ioport.handle;

tv.tv_sec = ioport.blocktime;

tv.tv_usec = 0;

FD_ZERO(&rfds);

FD_SET(handle, &rfds);

rval = select(handle + 1, &rfds, NULL, NULL, &tv);

if (FD_ISSET(handle, &rfds))

{

read(handle, response, 1);

} else

{

tcflush(handle, TCIFLUSH);

return FALSE;

}

return TRUE;

}

IO_Write оставил как есть

bool IO_Write(BYTE c)

{

HANDLE handle = ioport.handle;

tcflush(handle, TCIFLUSH); /* Flush the port */

if (write(handle, &c, 1) == 1) /* Write one byte */

return TRUE;

return FALSE;

}

Добавил IO_WriteCmd для записи буфера в ком порт

bool IO_WriteCmd(BYTE *cmd, int Size)

{

if (cmd == NULL) return FALSE;

int i;

for (i = 0; i < Size; i++)

if (!IO_Write(cmd[i]))

return FALSE;

return TRUE;

}

IO_ReadCmd использую для чтения по спецификации

bool IO_ReadCmd(BYTE *resp,int *Size)

{

BYTE tmp[270];

BYTE b, b1;

int size = 0;

*Size=0;

int i = 0;

if (IO_Read(&b))//Читаем первый байт

{

if (b == ACK)//Все ОК

{

IO_Write(ENQ);

if (IO_Read(&b))//Читаем начало сообщения

if (b != STX)

return FALSE; //Говно

if (IO_Read(&b) & IO_Read(&b1))//читаем размер

size = (int) b << 8 | b1;

else

return FALSE; //govno

for (i = 0; i < size; i++)//читаем данные

if (!IO_Read(&resp[i]))

return FALSE; //говно

*Size = size;

if (!IO_Read(&b) || b != ETX)// читаем конец сообщения

return FALSE;//ну пипец

IO_Read(&b);//так для вида crc

return TRUE;

}

else

{

*Size=0;

printf("Error responce cmd:");

while(IO_Read(&b)) printf("%02x ", b);

printf("\n");

return FALSE;

}

}

else return FALSE;

}

int IO_Close()

{

HANDLE handle = ioport.handle;

if (close(handle) == 0) /* Close the handle */

{

return TRUE;

} else

{

return FALSE; /* Done ! */

}

}

Функция GetCommandByte для составления команды для ридера

int GetCommandByte(BYTE *CMD, int sizein, BYTE *Res)

{

...

}

printb выводит на экран массив байтов, вспомогательная функция

void printb(BYTE *b, int s)

{

int i;

for (i = 0; i < s; i++)

printf("%02x ", b[i]);

printf("\n");

}

ExCmdResp преобразует, посылает и принемает команду в/из ридера

int ExCmdResp(BYTE *Cmd, int size, BYTE *Resp, int *respSize)

{

int ss=0;

BYTE resp[270];

ss = GetCommandByte(Cmd, size, resp);

//printb(resp,ss);

if(!IO_WriteCmd(resp,ss))

{

if(respSize)

*respSize = 0;

return -1;

}

if(!IO_ReadCmd(Resp,respSize))

{

if(respSize)

*respSize = 0;

return -1;

}

return 0;

}

Тоже что и ExCmdResp только не возвращает буфер

int ExecuteCommand(BYTE *Cmd, int size)

{

BYTE buff[270];

int sizeBuff;

int ret;

ret = ExCmdResp(Cmd, size, buff, &sizeBuff);

//printb(buff,sizeBuff);

if (sizeBuff > 0)

{

if (buff[2] == 0x59) ret = 0; //success

else

if (buff[sizeBuff - 3] == 0x45) ret = 1; //no card in reader

else

if (buff[sizeBuff - 3] == 0x57) ret = 2; // card is not in the right position where it can be operated

else

ret = -1; // failure

}

return ret;

}

Продолжение следует