/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2021 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <string.h>

#include "log.h"
#include "ezusb.h"
#include "ihex.h"
#include "timing.h"
#include "core.h"

#define EZUSB_REQUEST_TIMEOUT 1000
#define EZUSB_CPUCS_DELAY 10

int
ezusbWriteData (UsbDevice *device, EzusbAction action, IhexAddress address, const unsigned char *data, size_t length) {
  ssize_t result = usbControlWrite(
    device, EZUSB_REQUEST_RECIPIENT, EZUSB_REQUEST_TYPE,
    action, address, EZUSB_REQUEST_INDEX,
    data, length, EZUSB_REQUEST_TIMEOUT
  );

  if (result != -1) {
    size_t count = result;
    if (count == length) return 1;

    logMessage(LOG_ERR,
      "firmware write length mismatch:"
      " Address:%04"PRIX16
      " Expect:%"PRIsize
      " Actual:%"PRIsize,
      address, length, count
    );
  }

  return 0;
}

int
ezusbReadData (UsbDevice *device, EzusbAction action, IhexAddress address, unsigned char *buffer, size_t size) {
  ssize_t result = usbControlRead(
    device, EZUSB_REQUEST_RECIPIENT, EZUSB_REQUEST_TYPE,
    action, address, EZUSB_REQUEST_INDEX,
    buffer, size, EZUSB_REQUEST_TIMEOUT
  );

  if (result != -1) {
    size_t count = result;
    if (count == size) return 1;

    logMessage(LOG_ERR,
      "firmware read length mismatch:"
      " Address:%04"PRIX16
      " Expect:%"PRIsize
      " Actual:%"PRIsize,
      address, size, count
    );
  }

  return 0;
}

int
ezusbVerifyData (UsbDevice *device, EzusbAction action, IhexAddress address, const unsigned char *data, size_t length) {
  unsigned char buffer[length];

  {
    int ok = ezusbReadData(
      device, action, address, buffer, length
    );

    if (!ok) return 0;
  }

  if (memcmp(buffer, data, length) != 0) {
    logMessage(LOG_ERR,
      "firmware data verification mismatch:"
      " Address:%04"PRIX16,
      address
    );

    logBytes(LOG_DEBUG, "expect", data, length);
    logBytes(LOG_DEBUG, "actual", buffer, length);
    return 0;
  }

  return 1;
}

int
ezusbWriteCPUCS (UsbDevice *device, uint8_t state) {
  int ok = ezusbWriteData(
    device, EZUSB_ACTION_RW_INTERNAL,
    EZUSB_CPUCS_ADDRESS, &state, sizeof(state)
  );

  if (ok) approximateDelay(EZUSB_CPUCS_DELAY);
  return ok;
}

int
ezusbStopCPU (UsbDevice *device) {
  return ezusbWriteCPUCS(device, EZUSB_CPUCS_STOP);
}

int
ezusbResetCPU (UsbDevice *device) {
  return ezusbWriteCPUCS(device, EZUSB_CPUCS_RESET);
}

int
ezusbProcessBlob (const char *name, IhexRecordHandler *handler, void *data) {
  int ok = 0;
  char *path = ihexMakePath(opt_driversDirectory, name);

  if (path) {
    if (ihexProcessFile(path, handler, data)) {
      ok = 1;
    }

    free(path);
  }

  return ok;
}

typedef struct {
  UsbDevice *device;
  EzusbAction action;
} EzusbRecordProcessingData;

static int
ezusbInstallData (const IhexParsedRecord *record, void *data) {
  const EzusbRecordProcessingData *rpd = data;

  UsbDevice *const device = rpd->device;
  const EzusbAction action = rpd->action;

  const IhexAddress address = record->address;
  const IhexByte *const bytes = record->data;
  const IhexCount count = record->count;

  if (!ezusbWriteData(device, action, address, bytes, count)) return 0;
  if (!ezusbVerifyData(device, action, address, bytes, count)) return 0;

  return 1;
}

int
ezusbInstallBlob (UsbDevice *device, const char *name, EzusbAction action) {
  EzusbRecordProcessingData rpd = {
    .device = device,
    .action = action
  };

  return ezusbProcessBlob(name, ezusbInstallData, &rpd);
}
