/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <nos/transport.h> #include <errno.h> #include <stdarg.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <application.h> /* Note: evaluates expressions multiple times */ #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define VERBOSE_LOG 0 #define DEBUG_LOG 0 #ifdef ANDROID /* Logging for Android */ #define LOG_TAG "libnos_transport" #include <sys/types.h> #include <log/log.h> #define NLOGE(...) ALOGE(__VA_ARGS__) #define NLOGV(...) ALOGV(__VA_ARGS__) #define NLOGD(...) ALOGD(__VA_ARGS__) extern int usleep (uint32_t usec); #else /* Logging for other platforms */ #include <stdio.h> #define NLOGE(...) do { fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); } while (0) #define NLOGV(...) do { if (VERBOSE_LOG) { \ printf(__VA_ARGS__); printf("\n"); } } while (0) #define NLOGD(...) do { if (DEBUG_LOG) { \ printf(__VA_ARGS__); printf("\n"); } } while (0) #endif /* * If Citadel is rebooting it will take a while to become responsive again. We * expect a reboot to take around 100ms but we'll keep trying for 300ms to leave * plenty of margin. */ #define RETRY_COUNT 60 #define RETRY_WAIT_TIME_US 5000 static int nos_device_read(const struct nos_device *dev, uint32_t command, uint8_t *buf, uint32_t len) { int retries = RETRY_COUNT; while (retries--) { int err = dev->ops.read(dev->ctx, command, buf, len); if (err == -EAGAIN) { /* Linux driver returns EAGAIN error if Citadel chip is asleep. * Give to the chip a little bit of time to awake and retry reading * status again. */ usleep(RETRY_WAIT_TIME_US); continue; } if (err) { NLOGE("Failed to read: %s", strerror(-err)); } return -err; } return ETIMEDOUT; } static int nos_device_write(const struct nos_device *dev, uint32_t command, uint8_t *buf, uint32_t len) { int retries = RETRY_COUNT; while (retries--) { int err = dev->ops.write(dev->ctx, command, buf, len); if (err == -EAGAIN) { /* Linux driver returns EAGAIN error if Citadel chip is asleep. * Give to the chip a little bit of time to awake and retry reading * status again. */ usleep(RETRY_WAIT_TIME_US); continue; } if (err) { NLOGE("Failed to write: %s", strerror(-err)); } return -err; } return ETIMEDOUT; } /* status is non-zero on error */ static int get_status(const struct nos_device *dev, uint8_t app_id, uint32_t *status, uint16_t *ulen) { uint8_t buf[6]; uint32_t command = CMD_ID(app_id) | CMD_IS_READ | CMD_TRANSPORT; if (0 != nos_device_read(dev, command, buf, sizeof(buf))) { NLOGE("Failed to read device status"); return -1; } /* The read operation is successful */ *status = *(uint32_t *)buf; *ulen = *(uint16_t *)(buf + 4); return 0; } static int clear_status(const struct nos_device *dev, uint8_t app_id) { uint32_t command = CMD_ID(app_id) | CMD_TRANSPORT; if (0 != nos_device_write(dev, command, 0, 0)) { NLOGE("Failed to clear device status"); return -1; } return 0; } uint32_t nos_call_application(const struct nos_device *dev, uint8_t app_id, uint16_t params, const uint8_t *args, uint32_t arg_len, uint8_t *reply, uint32_t *reply_len) { uint32_t command; uint8_t buf[MAX_DEVICE_TRANSFER]; uint32_t status; uint16_t ulen; uint32_t poll_count = 0; if (get_status(dev, app_id, &status, &ulen) != 0) { return APP_ERROR_IO; } NLOGV("%d: query status 0x%08x ulen 0x%04x", __LINE__, status, ulen); /* It's not idle, but we're the only ones telling it what to do, so it * should be. */ if (status != APP_STATUS_IDLE) { /* Try clearing the status */ NLOGV("clearing previous status"); if (clear_status(dev, app_id) != 0) { return APP_ERROR_IO; } /* Check again */ if (get_status(dev, app_id, &status, &ulen) != 0) { return APP_ERROR_IO; } NLOGV("%d: query status 0x%08x ulen 0x%04x",__LINE__, status, ulen); /* It's ignoring us and is still not ready, so it's broken */ if (status != APP_STATUS_IDLE) { NLOGE("Device is not responding"); return APP_ERROR_IO; } } /* Send args data */ command = CMD_ID(app_id) | CMD_TRANSPORT | CMD_IS_DATA; do { /* * We can't send more than the device can take. For * Citadel using the TPM Wait protocol on SPS, this is * a constant. For other buses, it may not be. * * For each Write, Citadel requires that we send the length of * what we're about to send in the params field. */ ulen = MIN(arg_len, MAX_DEVICE_TRANSFER); CMD_SET_PARAM(command, ulen); if (args && ulen) memcpy(buf, args, ulen); NLOGV("Write command 0x%08x, bytes 0x%x", command, ulen); if (0 != nos_device_write(dev, command, buf, ulen)) { NLOGE("Failed to send datagram to device"); return APP_ERROR_IO; } /* Additional data needs the additional flag set */ command |= CMD_MORE_TO_COME; if (args) args += ulen; if (arg_len) arg_len -= ulen; } while (arg_len); /* See if we had any errors while sending the args */ if (get_status(dev, app_id, &status, &ulen) != 0) { return APP_ERROR_IO; } NLOGV("%d: query status 0x%08x ulen 0x%04x", __LINE__, status, ulen); if (status & APP_STATUS_DONE) /* Yep, problems. It should still be idle. */ goto reply; /* Now tell the app to do whatever */ command = CMD_ID(app_id) | CMD_PARAM(params); NLOGV("Write command 0x%08x", command); if (0 != nos_device_write(dev, command, 0, 0)) { NLOGE("Failed to send command datagram to device"); return APP_ERROR_IO; } /* Poll the app status until it's done */ do { if (get_status(dev, app_id, &status, &ulen) != 0) { return APP_ERROR_IO; } NLOGD("%d: poll status 0x%08x ulen 0x%04x", __LINE__, status, ulen); poll_count++; } while (!(status & APP_STATUS_DONE)); NLOGV("polled %d times, status 0x%08x ulen 0x%04x", poll_count, status, ulen); reply: /* Read any result only if there's a place with room to put it */ if (reply && reply_len && *reply_len) { uint16_t left = MIN(*reply_len, ulen); uint16_t gimme, got; command = CMD_ID(app_id) | CMD_IS_READ | CMD_TRANSPORT | CMD_IS_DATA; got = 0 ; while (left) { /* * We can't read more than the device can send. For * Citadel using the TPM Wait protocol on SPS, this is * a constant. For other buses, it may not be. */ gimme = MIN(left, MAX_DEVICE_TRANSFER); NLOGV("Read command 0x%08x, bytes 0x%x", command, gimme); if (0 != nos_device_read(dev, command, buf, gimme)) { NLOGE("Failed to receive datagram from device"); return APP_ERROR_IO; } memcpy(reply, buf, gimme); reply += gimme; left -= gimme; got += gimme; } /* got it all */ *reply_len = got; } /* Clear the reply manually for the next caller */ command = CMD_ID(app_id) | CMD_TRANSPORT; if (0 != nos_device_write(dev, command, 0, 0)) { NLOGE("Failed to clear the reply"); return APP_ERROR_IO; } return APP_STATUS_CODE(status); }