New version of pcsc-lite: 2.5.0

I have just released a new version of pcsc-lite 2.5.0.

pcsc-lite is a Free Software implementation of the PC/SC (also known as WinSCard) API for Unix systems. It provides an API for using smart cards and smart card readers.

The major change in this version is: no more limited to 16 smart card readers.

Changes:

2.5.0: Ludovic Rousseau

27 May 2026

  • Do not limit to 16 readers only

  • Remove support of autotools

  • Fix a crash when rescanning serial configs

  • Fix a memory leak in Polkit

  • tokenparser: avoid a crash with corrupted Info.plist files

  • Some other minor improvements

New version of libccid: 1.8.0

I just released version 1.8.0 of libccid the Free Software CCID class smart card reader driver.

The major changes in this version are:

  • no more limited to 16 smart card readers

  • ./configure script is no more provided. Only meson configuration is provided.

Changes:

1.8.0 - 27 May 2026, Ludovic Rousseau

  • Add support of

    • GLSolutions NM61 PC/SC

    • Identiv uTrust FIDO2 Security Key

    • Kensington VeriMark NFC+ USB-C Security Key

    • MARX CryptoTech LP Tokey 3 FIDO

    • mCore Contact-Reader

    • mCore Contactless-Reader

    • mCore DualSlot-Reader

    • Pol Henarejos Pico Fido

    • Pol Henarejos Pico HSM

    • Pol Henarejos Pico OpenPGP

    • Richmond Technologies CO. LLC AEGIS PRO4 Smart Card Reader

    • SCR Prime

  • Remove the limitation to 16 readers

  • udev: Update rules file to comply with systemd documentation (and fix permissions issue)

  • meson: install serial config file in correct dir

  • Remove support of autotools

  • Fix crash in multi slots readers code

  • Fix some race conditions

  • Some other minor improvements

Laptops and smart card readers

Which smart card reader is present in a particular laptop model? It can be difficult to find out the exact model of smart card reader included in a laptop before buying it.

However, thanks to the Hardware for Linux project, you can submit your own hardware configuration and also consult the configurations of many laptops and desktops to find out what hardware is included.

The website also provides aggregated results for a specific categories such as CPU Vendors.

One interesting result is the Chipcard Vendor aggregation.

Chipard Vendors market shares

The results do not represent exact market shares. They only include systems that:

However, they should reflect reality.

Chipcard Vendor Alcor Micro (26.2%) Broadcom (55.6%) O2 Micro (6.5%) Upek (4.7%) Lenovo (3.4%) Yubico.com (0.8%) Gemalto (was ... (0.6%) SCM Microsystems (0.5%) Advanced Card... (0.4%) Aktiv (0.2%) Others (1.0%)
Chipcard Vendors

#

Vendor

Percent

1

Broadcom

55.61%

2

Alcor Micro

26.17%

3

O2 Micro

6.53%

4

Upek

4.69%

5

Lenovo

3.45%

6

Yubico.com

0.83%

7

Gemalto (was Gemplus)

0.6%

8

SCM Microsystems

0.51%

9

Advanced Card Systems

0.37%

10

Activ

0.23%

An important point to note is that the first five on the list should be correct, as they correspond to the manufacturers of embedded smart card readers. However, the next five in the list are often (if not always) external smart card readers that are connected via a USB port and not integrated into the laptop. For instance, I am not aware of any Yubico readers that are embedded in laptops. These readers are listed on https://linux-hardware.org/ because they were plugged in when the user ran the hw-probe program to scan and report the hardware configuration.

Which laptop manufacturers use which smart card reader?

You can click on a specific slice of the Chipcard Vendor pie chart to see which laptop manuifacturers uses this smart card reader brand.

Broadcom

Notebook Vendors Dell (100.0%)

All Broadcom readers are used by Dell.

Alcor Micro

Notebook Vendors Lenovo (82.4%) Hewlett-Packard (8.6%) Fujitsu (6.5%) Acer (0.7%) ASUSTek (0.4%) Dell (0.4%) Durabook (0.2%) Getac (0.2%) Google (0.2%) Notebook (0.2%) Others (0.4%)

#

Vendor

Percent

1

Lenovo

82.4%

2

Hewlett-Packard

8.6%

3

Fujitsu

6.5%

4

Acer

0.7%

5

ASUSTek Computer

0.4%

O2 Micro

Notebook Vendors Fujitsu (37.3%) Toshiba (19.7%) Fujitsu Siemens (2.1%) Dell (40.8%)

#

Vendor

Percent

1

Dell

40.8%

2

Fujitsu

37.3%

3

Toshiba

19.7%

4

Fujitsu Siemens

2.1%

Upek

Notebook Vendors Lenovo (100.0%)

All of Upek readers are used by Lenovo.

It looks like the Upek devices are "TouchChip Fingerprint Coprocessor" and not an "Integrated Smart Card Reader". I don't know why they are listed as "Chipcard Vendor". Perhaps it is a bug or limitation of https://linux-hardware.org/.

Lenovo

Notebook Vendors Lenovo (100.0%)

Unsurprisingly, all of Lenovo readers are used by... Lenovo.

Corresponding CCID devices

It is possible to list the CCID devices of each manufacturer that I know of:

Conclusion

Only three main vendors of smart card readers are used in laptops: Broadcom, Alcor Micro and O2 Micro.

Do they produce good products? Are their products supported by my CCID driver? These are both very good questions, but for other blog articles. To be continued...

pcsc_scan improved user interface

Version 1.7.4 of pcsc-tools provides an enhanced version of the pcsc_scan tool.

Problem

The program waits for card and reader events and reports any smart card events. If no reader or card is inserted or removed, nothing happens.

Since version 1.6.0 (New version of pcsc-tools: 1.6.0) the program has included an animation (cycling \|/- characters) to indicate that it is waiting. However, this was not obvious to users who did not know how to stop the program or what to do.

Solution

Now when the program is waiting for an event it displays a message such as:

Waiting for the first reader...   \  (use Ctrl-C to exit)

or

Insert or remove a card or a reader... /  (use Ctrl-C to exit)

I hope this makes it clearer what the user is expected to do.

Demo

The animation was recorded and played using asciinema.

The sequence in the animation below correspons to:

  1. Connect a reader

  2. Insert a card

  3. Remove the card

  4. Disconnect the reader

  5. Exit pcsc_scan

Windows

The program is also available for Windows. You can download the Windows binary from the project page https://pcsc-tools.apdu.fr/#windows

https://pcsc-tools.apdu.fr/pcsc_scan_windows.gif

Conclusion

I hope you find pcsc_scan usefull for you. This tool is intended for initial debugging purposes. For example see Level 1 smart card support on GNU/Linux.

SCARD_CTL_CODE(3601): USB path of the reader

In version 1.7.1 of my CCID driver I added a new service with the Control Code SCARD_CTL_CODE(3601).

Problem

Some people use two (or more) readers. And each reader plays a specific role in their application. It is therefore important to identify each one.

While it is often possible to use the PC/SC reader name (see What is in a PC/SC reader name?), if you have two identical readers with no serial numbers, it becomes impossible to distinguish between them.

Solution

A USB device is connected to a USB bus with a specific topology. The lsusb --tree command, for example, can display the USB bus topology. The output looks like this:

$ lsusb -t
/:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/16p, 480M
    |__ Port 003: Dev 039, If 0, Class=Chip/SmartCard, Driver=usbfs, 12M

The smart card reader is connected to USB Port 003 on Bus 001. These numbers are fixed and linked to the computer's physical USB port. Disconnecting and reconnecting the reader to the same USB port will result in the same topology.

On GNU/Linux, the device number will change. For example, if I disconnect and reconnect the device, the reader's device number (Dev) will change from 039 to 040.

SCARD_CTL_CODE(3601)

This code is specific to my CCID driver. It comes after SCARD_CTL_CODE(3600) that is also specific/proprietary and used for MEP (see CCID driver and Multiple Enabled Profiles (MEP)).

Ideally, we would use a code defined by the PC/SC Workgroup but this group is essentially defunct and Microsoft is no longer a member. Microsoft stopped following the PC/SC Workgroup Specification a long time ago. Therefore, even if a name is defined by the PC/SC Workgroup, it will not be available on Windows.

API documentation

Use SCardControl() to send the control code SCARD_CTL_CODE(3601) to the reader. This code will be intercepted by the CCID driver. On return, you get a NUL-terminated string in the format <bus>-<port[.port[.port]]>:<config>.<interface> (e.g., 1-1.3.2:1.0) as found in /sys/bus/usb/devices/.

Source code

This example code is also included in the CCID driver's source code, in the examples/get_usb_path.py file.

The code also displays the SCARD_ATTR_CHANNEL_ID of the reader (see SCARD_ATTR_CHANNEL_ID and USB devices). This is an official PC/SC feature. It can be used when topology information is obtained from another source.

#! /usr/bin/env python3

"""
get_usb_path.py: get USB path of readers
Copyright (C) 2026  Ludovic Rousseau
"""

#   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 3 of the License, 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, see <http://www.gnu.org/licenses/>.

import struct
import smartcard
from smartcard.pcsc.PCSCPart10 import SCARD_CTL_CODE
from smartcard.scard import (
    SCARD_E_NOT_TRANSACTED,
    SCARD_E_INVALID_PARAMETER,
    SCARD_ATTR_CHANNEL_ID,
    SCARD_SHARE_DIRECT,
    SCARD_LEAVE_CARD,
)
from smartcard.util import toASCIIString


def get_usb_path(reader):
    """
    Display USB topology
    """
    print("Using:", reader)
    card_connection = reader.createConnection()
    card_connection.connect(mode=SCARD_SHARE_DIRECT, disposition=SCARD_LEAVE_CARD)

    # special control code
    ioctl_get_usb_path = SCARD_CTL_CODE(3601)
    try:
        res = card_connection.control(ioctl_get_usb_path)
    except smartcard.Exceptions.SmartcardException as ex:
        # SCARD_E_NOT_TRANSACTED returned by pcsc-lite
        # SCARD_E_INVALID_PARAMETER retruned by macOS
        if ex.hresult in [SCARD_E_NOT_TRANSACTED, SCARD_E_INVALID_PARAMETER]:
            print("Your driver does not (yet) support SCARD_CTL_CODE(3601)")
            return
        raise
    print("USB path:", toASCIIString(res))

    # get Channel ID
    attrib = card_connection.getAttrib(SCARD_ATTR_CHANNEL_ID)
    ddddcccc = struct.unpack("i", bytearray(attrib))[0]
    dddd = ddddcccc >> 16
    if dddd == 0x0020:
        bus = (ddddcccc & 0xFF00) >> 8
        addr = ddddcccc & 0xFF
        print(f" USB: bus: {bus}, addr: {addr}")
    print()


def main():
    """
    main
    """
    # for all the available readers
    for reader in smartcard.System.readers():
        get_usb_path(reader)


if __name__ == "__main__":
    main()

Output

The reader is connected directly to the computer

$ ./get_usb_path.py
Using: Gemalto PC Twin Reader (70D7E2EE) 01 00
USB path: 1-3:1.0
 USB: bus: 1, addr: 39

$ lsusb -t
/:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/16p, 480M
    |__ Port 003: Dev 039, If 0, Class=Chip/SmartCard, Driver=usbfs, 12M

The USB path is 1-3:1.0.

This corresponds to bus 1, port 3, configuration 1 and interface 0. You can ignore the configuration and interface details for non-composite USB devices.

Connected to another USB port of the computer

$ ./get_usb_path.py
Using: Gemalto PC Twin Reader (70D7E2EE) 01 00
USB path: 1-6:1.0
 USB: bus: 1, addr: 40

$ lsusb -t
/:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/16p, 480M
    |__ Port 006: Dev 040, If 0, Class=Chip/SmartCard, Driver=usbfs, 12M

Note:

  • the Port changed from 003 to 006

  • the Dev changed from 039 to 040

Connected to a hub

$ ./get_usb_path.py
Using: Gemalto PC Twin Reader (70D7E2EE) 01 00
USB path: 1-3.1:1.0
 USB: bus: 1, addr: 42

$ lsusb -t
/:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/16p, 480M
    |__ Port 003: Dev 041, If 0, Class=Hub, Driver=hub/4p, 480M
        |__ Port 001: Dev 042, If 0, Class=Chip/SmartCard, Driver=usbfs, 12M

Note:

  • Port 003 is now used by the USB hub (Class=Hub)

  • the reader uses Port 001 of the hub

  • the USB path has one extra level 1-3.1:1.0

Connected to another port of the hub

$ ./get_usb_path.py
Using: Gemalto PC Twin Reader (70D7E2EE) 01 00
USB path: 1-3.4:1.0
 USB: bus: 1, addr: 43

$ lsusb -t
/:  Bus 001.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/16p, 480M
    |__ Port 003: Dev 041, If 0, Class=Hub, Driver=hub/4p, 480M
        |__ Port 004: Dev 043, If 0, Class=Chip/SmartCard, Driver=usbfs, 12M

Note:

  • the reader is connected to Port 004 of the hub

  • the hub is still connected to Port 003

Conclusion

Not everyone will need or use this feature. However, it can be very important if you have a large number of readers.

Many Thanks to Diego de los Santos for the idea and implementation.

New version of libccid: 1.7.1

I just released version 1.7.1 of libccid the Free Software CCID class smart card reader driver.

Changes:

1.7.1 - 4 February 2026, Ludovic Rousseau

  • Add support of

    • ACS APG8201-B2

    • BUDGET E-ID BUD001

    • CHERRY Smart Board 1150

    • CryptnoxCR CryptnoxCR

    • Diebold Nixdorf PN7362au CCID

    • FT BioPass FIDO2 Pro

    • Nitrokey Nitrokey Passkey

  • Add SCARD_CTL_CODE(3601): USB path of the reader

  • Some other minor improvements

PCSC sample in Zig

I continue the list of PC/SC wrappers I started in 2010 with PC/SC sample in different languages. I am now presenting a new sample code in Zig.

pcsc-z

The wrapper is available at https://github.com/kofi-q/pcsc-z and is compatible for GNU/Linux, macOS and Windows.

The author is kofi-q.

The licence is MIT.

The API documentation can be found at https://kofi-q.github.io/pcsc-z/#pcsc.

Zig

From Wikipedia Zig article:

Zig is a system programming language designed to be a general-purpose improvement to the C programming language. It is free and open-source software, released under an MIT License.

Differences with C relate to control flow, function calls, library imports, variable declaration and Unicode support. The language makes no use of macros or preprocessor instructions. Features adopted from modern languages include the addition of compile time generic programming data types, allowing functions to work on a variety of data, along with a small set of new compiler directives to allow access to the information about those types using reflection. Zig requires manual memory management, but attempts to improve memory safety through option types and a unit testing framework. Features for low-level programming include packed structs, arbitrary-width integers and multiple pointer types.

Zig was designed by Andrew Kelly and first announced in 2016. Development is funded by the Zig Software Foundation (ZSF).

Installation

$ zig fetch --save=pcsc "git+https://github.com/kofi-q/pcsc-z.git"

I had to use an older version of the Zig-PC/SC wrapper because I used Zig 0.15.2 (stable version) instead of the development version 0.16.

$ zig fetch --save=pcsc "git+https://github.com/kofi-q/pcsc-z.git#zig-0.15"
info: resolved ref 'zig-0.15' to commit b347fd008c003ceb92041654a050c48d02468ce3
warning: overwriting existing dependency named 'pcsc'

Source code

To get started, you can use the zig init command to generate a minimal zig project.

$ zig init
info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options

You then need to update 2 files: src/main.zig and build.zig.

//! src/main.zig

const std = @import("std");
const pcsc = @import("pcsc");

pub fn main() !void {
    const client = try pcsc.Client.init(.SYSTEM);
    defer client.deinit() catch |err| std.debug.print(
        "Unable to release client: {t}",
        .{err},
    );

    // Connect to 1st reader
    var reader_names = try client.readerNames();
    if (reader_names.next()) |reader_name| {
        std.debug.print("Using: {s}\n", .{reader_name});

        // Connect to an inserted card:
        const card = try client.connect(reader_name, .SHARED, .ANY);
        defer card.disconnect(.LEAVE) catch |err| std.debug.print(
            "Unable to disconnect card: {t}\n",
            .{err},
        );

        std.debug.print("Card connected with protocol {f}\n", .{card.protocol});

        var buf_response: [pcsc.max_buffer_len]u8 = undefined;

        // SELECT command
        const select = [_]u8{ 0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00,
            0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01 };

        std.debug.print("Transmitting APDU: {x}\n", .{select});

        var response = try card.transmit(&select, &buf_response);
        std.debug.print("Received response: {x}\n", .{response});

        // TEST command
        const command = [_]u8{ 0x00, 0x00, 0x00, 0x00 };

        std.debug.print("Transmitting APDU: {x}\n", .{command});

        response = try card.transmit(&command, &buf_response);
        std.debug.print("Received response: {x}\n", .{response});

        // Truncate the 2 last bytes: status word
        const text = response[0..response.len-2];
        std.debug.print("Text: {s}\n", .{text});
    } else {
        std.debug.print("No reader found\n", .{});
    }
}
//! build.zig

const std = @import("std");

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const mode = b.standardOptimizeOption(.{});

    const pcsc_dep = b.dependency("pcsc", .{
        .optimize = mode,
        .target = target,
    });

    const pcsc_mod = pcsc_dep.module("pcsc");

    const exe = b.addExecutable(.{
        .name = "pcsc-demo",
        .root_module = b.createModule(.{
            .imports = &.{
                .{ .name = "pcsc", .module = pcsc_mod },
            },
            .optimize = mode,
            .root_source_file = b.path("src/main.zig"),
            .target = target,
        }),
    });

    const demo_run = b.addRunArtifact(exe);
    const demo_step = b.step("demo", "Run PCSC demo");
    demo_step.dependOn(&demo_run.step);
}

Output

$ zig build demo
Using: Gemalto USB SmartCard Reader
Card connected with protocol T=1
Transmitting APDU: 00a404000aa00000006203010c0601
Received response: 9000
Transmitting APDU: 00000000
Received response: 48656c6c6f20776f726c64219000
Text: Hello world!

Conclusion

Zig is a relatively new programming language. We can already find a PC/SC wrapper for it. That is great!

If you are working on a Free Software PC/SC wrapper that is not yet on my list please let me know.

pcsc-lite backward & forward compatible with itself

pcsc-lite 2.4.1 now provides a backward & forward compatible mechanism that supports both the current and previous internal communication protocols.

The communication protocol we are talking about here is the one between the client (libpcsclite) and the server (pcscd) sides. It is an internal-only protocol that is specific to pcsc-lite.

Protocol negotiation

To ensure that the client and server are using the same protocol, they negotiate the procotol they know. If they are different, the server will refuse the connection. In the application (client side), the SCardEstablishContext() function will return the error code SCARD_E_SERVICE_STOPPED.

Up to version 2.4.0, both the server and the client only supported one protocol version (version 4:5).

Sandboxes

Flatpack, Snap, AppImage and other sandbox technologies are now used to distribute applications.

In some/many cases, the server (pcscd daemon) running on the host comes from a different version of pcsc-lite than the client used in the sandbox. If the internal communication protocol differs, the communication is not possible.

I receive requests to solve this problem. See:

Protocol History

pcsc-lite version

Release Date

Protocol version

Compatible with

2.4.1

1 Jan 2026

4:5

4:4 & 4:5

2.4.0

19 Oct 2025

4:5

4:5

2.3.3

2 Apr 2025

2.3.2

26 Mar 2025

2.3.1

24 Dec 2024

2.3.0

3 Aug 2024

2.2.3

26 May 2024

4:4

4:4

...

1.8.24

12 Oct 2018

1.8.23

18 Dec 2017

4:3

4:3

...

1.8.9

16 Oct 2013

1.8.8

16 Jan 2012

4:2

4:2

...

1.6.5

12 Oct 2010

New protocol negotiation

From pcsc-lite 2.4.1 onwards, the protocol negotiation is smarter.

The daemon side will accept the current version and also the previous one. Versions 4:5 and 4:4 are accepted. It is straightforward, as the only change with version 4:5 is the addition of a new command (command CMD_GET_READER_EVENTS introduced in commit 53f57ed700bcd0bc47d970dc674ba3fd5ee5b387). Old clients will simply not use the new command.

The client side will also accept the current version and the previous one. These are versions 4:5 and 4:4. Using a recent client (protocol 4:5) with an old server (protocol 4:4) will have a small side effect. The SCardGetStatusChange() function will not include the number of reader events (see Improved SCardGetStatusChange() for "\\?PnP?\Notification" special reader).

Applications using pcsc-lite 2.4.1 can use a daemon as old as pcsc-lite version 1.8.24, released in October 2018.

A pcscd daemon from pcsc-lite 2.4.1 can handle clients as old as the same pcsc-lite version 1.8.24 from October 2018.

Supporting the (older) protocol version 4:3 may be possible but would require more efforts and be more complex. Contact me if you require this.

Conclusion

This change should help mix PC/SC servers and clients from more different versions.

This should prevent people from using ugly hacks.

New version of pcsc-lite: 2.4.1

I have just released a new version of pcsc-lite 2.4.1.

pcsc-lite is a Free Software implementation of the PC/SC (also known as WinSCard) API for Unix systems. It provides an API for using smart cards and smart card readers.

This version offers backwards compatibility for both the client and server components. It should now be easier to mix and match different versions of pcscd and libpcsclite (and using different internal protocol versions).

Changes:

2.4.1: Ludovic Rousseau

1 January 2026

  • Add backward version support on the client side

  • Add backward version support on the server side

  • hotplug libudev: rescan the USB bus with pcscd --hotplug

  • fix a value in pcscd.service systemd file

  • meson: install systemd files even if libsystemd is not used

  • Some other minor improvements