ccid_get_atr (ccid_driver_t handle,
              unsigned char *atr, size_t maxatrlen, size_t *atrlen)
{
  int rc;
  int statusbits;
  unsigned char msg[100];
  unsigned char *tpdu;
  size_t msglen, tpdulen;
  unsigned char seqno;
  int use_crc = 0;
  unsigned int edc;
  int tried_iso = 0;
  int got_param;
  unsigned char param[7] = { /* For Protocol T=1 */
    0x11, /* bmFindexDindex */
    0x10, /* bmTCCKST1 */
    0x00, /* bGuardTimeT1 */
    0x4d, /* bmWaitingIntegersT1 */
    0x00, /* bClockStop */
    0x20, /* bIFSC */
    0x00  /* bNadValue */
  };

  /* First check whether a card is available.  */
  rc = ccid_slot_status (handle, &statusbits);
  if (rc)
    return rc;
  if (statusbits == 2)
    return CCID_DRIVER_ERR_NO_CARD;

  /* For an inactive and also for an active card, issue the PowerOn
     command to get the ATR.  */
 again:
  msg[0] = PC_to_RDR_IccPowerOn;
  msg[5] = 0; /* slot */
  msg[6] = seqno = handle->seqno++;
  /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */
  msg[7] = handle->auto_voltage ? 0 : 1;
  msg[8] = 0; /* RFU */
  msg[9] = 0; /* RFU */
  set_msg_len (msg, 0);
  msglen = 10;

  rc = bulk_out (handle, msg, msglen, 0);
  if (rc)
    return rc;
  rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
                seqno, 5000, 0);
  if (rc)
    return rc;
  if (!tried_iso && CCID_COMMAND_FAILED (msg) && CCID_ERROR_CODE (msg) == 0xbb
      && ((handle->id_vendor == VENDOR_CHERRY
           && handle->id_product == 0x0005)
          || (handle->id_vendor == VENDOR_GEMPC
              && handle->id_product == 0x4433)
          ))
    {
      tried_iso = 1;
      /* Try switching to ISO mode. */
      if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2,
                            NULL, 0, NULL))
        goto again;
    }
  else if (CCID_COMMAND_FAILED (msg))
    return CCID_DRIVER_ERR_CARD_IO_ERROR;


  handle->powered_off = 0;

  if (atr)
    {
      size_t n = msglen - 10;

      if (n > maxatrlen)
        n = maxatrlen;
      memcpy (atr, msg+10, n);
      *atrlen = n;
    }

  param[6] = handle->nonnull_nad? ((1 << 4) | 0): 0;
  rc = update_param_by_atr (param, msg+10, msglen - 10);
  if (rc < 0)
    {
      DEBUGOUT_1 ("update_param_by_atr failed: %d\n", rc);
      return CCID_DRIVER_ERR_CARD_IO_ERROR;
    }

  got_param = 0;

  if (handle->auto_param)
    {
      msg[0] = PC_to_RDR_GetParameters;
      msg[5] = 0; /* slot */
      msg[6] = seqno = handle->seqno++;
      msg[7] = 0; /* RFU */
      msg[8] = 0; /* RFU */
      msg[9] = 0; /* RFU */
      set_msg_len (msg, 0);
      msglen = 10;
      rc = bulk_out (handle, msg, msglen, 0);
      if (!rc)
        rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
                      seqno, 2000, 0);
      if (rc)
        DEBUGOUT ("GetParameters failed\n");
      else if (msglen == 17 && msg[9] == 1)
        got_param = 1;
    }
  else if (handle->auto_pps)
    ;
  else if (rc == 1)             /* It's negotiable, send PPS.  */
    {
      msg[0] = PC_to_RDR_XfrBlock;
      msg[5] = 0; /* slot */
      msg[6] = seqno = handle->seqno++;
      msg[7] = 0;
      msg[8] = 0;
      msg[9] = 0;
      msg[10] = 0xff;           /* PPSS */
      msg[11] = 0x11;           /* PPS0: PPS1, Protocol T=1 */
      msg[12] = param[0];       /* PPS1: Fi / Di */
      msg[13] = 0xff ^ 0x11 ^ param[0]; /* PCK */
      set_msg_len (msg, 4);
      msglen = 10 + 4;

      rc = bulk_out (handle, msg, msglen, 0);
      if (rc)
        return rc;

      rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
                    seqno, 5000, 0);
      if (rc)
        return rc;

      if (msglen != 10 + 4)
        {
          DEBUGOUT_1 ("Setting PPS failed: %zu\n", msglen);
          return CCID_DRIVER_ERR_CARD_IO_ERROR;
        }

      if (msg[10] != 0xff || msg[11] != 0x11 || msg[12] != param[0])
        {
          DEBUGOUT_1 ("Setting PPS failed: 0x%02x\n", param[0]);
          return CCID_DRIVER_ERR_CARD_IO_ERROR;
        }
    }

  /* Setup parameters to select T=1. */
  msg[0] = PC_to_RDR_SetParameters;
  msg[5] = 0; /* slot */
  msg[6] = seqno = handle->seqno++;
  msg[7] = 1; /* Select T=1. */
  msg[8] = 0; /* RFU */
  msg[9] = 0; /* RFU */

  if (!got_param)
    memcpy (&msg[10], param, 7);
  set_msg_len (msg, 7);
  msglen = 10 + 7;

  rc = bulk_out (handle, msg, msglen, 0);
  if (rc)
    return rc;
  rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
                seqno, 5000, 0);
  if (rc)
    DEBUGOUT ("SetParameters failed (ignored)\n");

  if (!rc && msglen > 15 && msg[15] >= 16 && msg[15] <= 254 )
    handle->ifsc = msg[15];
  else
    handle->ifsc = 128; /* Something went wrong, assume 128 bytes.  */

  if (handle->nonnull_nad && msglen > 16 && msg[16] == 0)
    {
      DEBUGOUT ("Use Null-NAD, clearing handle->nonnull_nad.\n");
      handle->nonnull_nad = 0;
    }

  handle->t1_ns = 0;
  handle->t1_nr = 0;

  /* Send an S-Block with our maximum IFSD to the CCID.  */
  if (!handle->apdu_level && !handle->auto_ifsd)
    {
      tpdu = msg+10;
      /* NAD: DAD=1, SAD=0 */
      tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0;
      tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */
      tpdu[2] = 1;
      tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32;
      tpdulen = 4;
      edc = compute_edc (tpdu, tpdulen, use_crc);
      if (use_crc)
        tpdu[tpdulen++] = (edc >> 8);
      tpdu[tpdulen++] = edc;

      msg[0] = PC_to_RDR_XfrBlock;
      msg[5] = 0; /* slot */
      msg[6] = seqno = handle->seqno++;
      msg[7] = 0;
      msg[8] = 0; /* RFU */
      msg[9] = 0; /* RFU */
      set_msg_len (msg, tpdulen);
      msglen = 10 + tpdulen;

      if (debug_level > 1)
        DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
                      ((msg[11] & 0xc0) == 0x80)? 'R' :
                                (msg[11] & 0x80)? 'S' : 'I',
                      ((msg[11] & 0x80)? !!(msg[11]& 0x10)
                                       : !!(msg[11] & 0x40)),
                    (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));

      rc = bulk_out (handle, msg, msglen, 0);
      if (rc)
        return rc;


      rc = bulk_in (handle, msg, sizeof msg, &msglen,
                    RDR_to_PC_DataBlock, seqno, 5000, 0);
      if (rc)
        return rc;

      tpdu = msg + 10;
      tpdulen = msglen - 10;

      if (tpdulen < 4)
        return CCID_DRIVER_ERR_ABORTED;

      if (debug_level > 1)
        DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
                    ((msg[11] & 0xc0) == 0x80)? 'R' :
                              (msg[11] & 0x80)? 'S' : 'I',
                    ((msg[11] & 0x80)? !!(msg[11]& 0x10)
                                     : !!(msg[11] & 0x40)),
                    ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
                    (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));

      if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1)
        {
          DEBUGOUT ("invalid response for S-block (Change-IFSD)\n");
          return -1;
        }
      DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]);
    }

  return 0;
}