aboutsummaryrefslogtreecommitdiff
path: root/lib/link/seven/scsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/link/seven/scsi.c')
-rw-r--r--lib/link/seven/scsi.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/lib/link/seven/scsi.c b/lib/link/seven/scsi.c
new file mode 100644
index 0000000..1951590
--- /dev/null
+++ b/lib/link/seven/scsi.c
@@ -0,0 +1,325 @@
+/* ****************************************************************************
+ * link/seven/scsi.c -- use SCSI to exchange data.
+ * Copyright (C) 2016-2017 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
+ *
+ * This file is part of libcasio.
+ * libcasio is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3.0 of the License,
+ * or (at your option) any later version.
+ *
+ * libcasio 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libcasio; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * These functions are the one used behind the others to send a packet.
+ * ************************************************************************* */
+#include "data.h"
+
+/* The fx-CG* calculators (fx-CG10/20, known as the Prizm, and the fx-CG50,
+ * known as the Graph 90+E in France) uses some vendor-specific commands
+ * to exchange bytes over an SCSI connexion. This stream makes that
+ * accessible through a simple stream.
+ *
+ * To gather more information, here's the page dealing with that on Cemetech:
+ * https://www.cemetech.net/prizm/USB_Communication
+ *
+ * Multi-byte integers are expressed in big endian.
+ *
+ * To receive data, we must probe first if there is data available.
+ * In order to do that, we use the 0xC0 command which returns the following
+ * data:
+ *
+ * D0 00 00 00 00 00 00 SS SS 00 AA AA 00 00 00 00
+ *
+ * Where `SS SS` represents the amount of available data and `AA AA`
+ * is the activity status of the Prizm (`10 00` if busy).
+ *
+ * TODO: The initial `D0 00` probably links to the `CAL00D0` we receive
+ * in Protocol 7.00… or not?
+ *
+ * Then, if data is available, we want to receive it, so we send the
+ * following command:
+ *
+ * C1 00 00 00 00 00 SS SS
+ *
+ * Where `SS SS` is the amount of bytes we want to receive. The Prizm will
+ * answer with the raw data.
+ *
+ * To send data, we first have to probe (each time!) to check if the
+ * calculator is busy or not (by checking the activity field). Then, if it
+ * is not busy, we send the following command:
+ *
+ * C2 00 00 00 00 00 SS SS
+ *
+ * Where `SS SS` is the quantity of data we are about to send next. After
+ * this command is sent, we send our raw data. */
+
+/* ---
+ * Cookie definition.
+ * --- */
+
+/* The cookie contains the following data:
+ * - the original stream to use for SCSI requests;
+ * - the buffer (with size). */
+
+#define COOKIE_BUFFER_SIZE MAX_PACKET_SIZE
+#define reset_cookie(COOKIE) \
+ (COOKIE)->off = 0; \
+ (COOKIE)->left = 0; \
+ (COOKIE)->ptr = (casio_uint8_t *)&(COOKIE)[1]
+
+typedef struct {
+ casio_stream_t *stream;
+ size_t size, off, left;
+ casio_uint8_t *ptr;
+} seven_scsi_cookie_t;
+
+/* ---
+ * Read and write from the stream.
+ * --- */
+
+CASIO_LOCAL int seven_scsi_read(seven_scsi_cookie_t *cookie,
+ unsigned char *buffer, size_t size)
+{
+ casio_scsi_t scsi; int err;
+
+ /* Empty what's already in the buffer. */
+
+ if (cookie->left) {
+ if (cookie->left >= size) {
+ memcpy(buffer, cookie->ptr, size);
+ cookie->ptr += size;
+ cookie->left -= size;
+ return (0);
+ }
+
+ memcpy(buffer, cookie->ptr, cookie->left);
+ buffer += cookie->left;
+ size -= cookie->left;
+ reset_cookie(cookie);
+ }
+
+ do {
+ casio_uint8_t *to;
+ size_t avail;
+ /* casio_uint16_t activity; */
+
+ /* Polling loop. */
+
+ while (1) {
+ casio_uint8_t poll_command[16] = {0xC0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ casio_uint8_t poll_data[16];
+
+ /* Send the polling command, extract the data. */
+
+ scsi.casio_scsi_cmd = poll_command;
+ scsi.casio_scsi_cmd_len = 16;
+ scsi.casio_scsi_direction = CASIO_SCSI_DIREC_FROM_DEV;
+ scsi.casio_scsi_data = poll_data;
+ scsi.casio_scsi_data_len = 16;
+
+ if ((err = casio_scsi_request(cookie->stream, &scsi)))
+ return (err);
+
+ avail = (poll_data[7] << 8) | poll_data[8];
+ /* activity = (poll_data[10] << 8) | poll_data[11]; */
+
+ /* Check if there are some bytes to get. */
+
+ if (!avail) {
+ /* FIXME: delay and check the timeout!!! */
+
+ continue;
+ }
+
+ break;
+ }
+
+ /* Decide which buffer to use and what size to read.
+ * We want to get the most bytes and directly into the buffer
+ * if possible.
+ * We could also check that `avail < 0x10000` because we need to
+ * express it later, but it is imposed by the source format. */
+
+ if (avail > size && size <= cookie->size) {
+ to = cookie->ptr;
+ if (avail > cookie->size)
+ avail = cookie->size;
+ } else {
+ to = buffer;
+ if (avail > size)
+ avail = size;
+ }
+
+ /* Actually get the data. */
+
+ {
+ casio_uint8_t recv_command[16] = {0xC1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ recv_command[6] = (avail >> 8) & 0xFF;
+ recv_command[7] = avail & 0xFF;
+
+ scsi.casio_scsi_cmd = recv_command;
+ scsi.casio_scsi_cmd_len = 16;
+ scsi.casio_scsi_direction = CASIO_SCSI_DIREC_FROM_DEV;
+ scsi.casio_scsi_data = cookie->ptr;
+ scsi.casio_scsi_data_len = avail;
+
+ if ((err = casio_scsi_request(cookie->stream, &scsi)))
+ return (err);
+ }
+
+ if (to == buffer) {
+ buffer += avail;
+ size -= avail;
+ } else {
+ cookie->left = avail;
+
+ memcpy(buffer, cookie->ptr, size);
+ cookie->ptr += size;
+ cookie->left -= size;
+ size = 0;
+ }
+ } while (size);
+
+ return (0);
+}
+
+CASIO_LOCAL int seven_scsi_write(seven_scsi_cookie_t *cookie,
+ unsigned char const *buffer, size_t size)
+{
+ casio_scsi_t scsi;
+ int err;
+
+ do {
+ casio_uint16_t activity;
+
+ /* Polling loop. */
+
+ while (1) {
+ casio_uint8_t poll_command[16] = {0xC0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ casio_uint8_t poll_data[16];
+
+ /* Poll to check the activity. */
+
+ scsi.casio_scsi_cmd = poll_command;
+ scsi.casio_scsi_cmd_len = 16;
+ scsi.casio_scsi_direction = CASIO_SCSI_DIREC_FROM_DEV;
+ scsi.casio_scsi_data = poll_data;
+ scsi.casio_scsi_data_len = 16;
+
+ if ((err = casio_scsi_request(cookie->stream, &scsi)))
+ return (err);
+
+ activity = (poll_data[10] << 8) | poll_data[11];
+
+ if (activity == 0x100) {
+ /* The calculator is busy.
+ * FIXME: delay and check the timeout!! */
+
+ continue;
+ }
+
+ break;
+ }
+
+ /* Actually send some of the data. */
+
+ {
+ casio_uint16_t to_send = (size > 0xFFFF) ? 0xFFFF : size;
+ casio_uint8_t send_command[16] = {0xC2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ send_command[6] = (to_send >> 8) & 0xFF;
+ send_command[7] = to_send & 0xFF;
+
+ scsi.casio_scsi_cmd = send_command;
+ scsi.casio_scsi_cmd_len = 16;
+ scsi.casio_scsi_direction = CASIO_SCSI_DIREC_TO_DEV;
+ scsi.casio_scsi_data = (casio_uint8_t *)buffer;
+ scsi.casio_scsi_data_len = to_send;
+
+ if ((err = casio_scsi_request(cookie->stream, &scsi)))
+ return (err);
+
+ buffer += to_send;
+ size -= to_send;
+ }
+ } while (size);
+
+ return (0);
+}
+
+/* ---
+ * SCSI requests.
+ * --- */
+
+CASIO_LOCAL int seven_scsi_request(seven_scsi_cookie_t *cookie,
+ casio_scsi_t *request)
+{
+ /* Actually, just transmit the request to the source stream. */
+
+ return (casio_scsi_request(cookie->stream, request));
+}
+
+/* ---
+ * Manage the stream.
+ * --- */
+
+CASIO_LOCAL int seven_scsi_close(seven_scsi_cookie_t *cookie)
+{
+ casio_close(cookie->stream);
+ free(cookie);
+ return (0);
+}
+
+CASIO_LOCAL casio_streamfuncs_t seven_scsi_funcs = {
+ (casio_stream_close_t *)seven_scsi_close,
+ NULL,
+ (casio_stream_read_t *)seven_scsi_read,
+ (casio_stream_write_t *)seven_scsi_write,
+ NULL,
+ NULL,
+ (casio_stream_scsi_t *)seven_scsi_request
+};
+
+int CASIO_EXPORT casio_open_seven_scsi(casio_stream_t **streamp,
+ casio_stream_t *original)
+{
+ seven_scsi_cookie_t *cookie = NULL;
+
+ /* Check if the original stream supports SCSI. */
+
+ if (~casio_get_openmode(original) & CASIO_OPENMODE_SCSI) {
+ /* We shall not close the original stream because the caller
+ * might want to use it if this stream hasn't been opened. */
+
+ return (casio_error_op);
+ }
+
+ /* Allocate and prepare the cookie. */
+
+ cookie = casio_alloc(1, sizeof(seven_scsi_cookie_t) + COOKIE_BUFFER_SIZE);
+ if (!cookie) {
+ casio_close(original);
+ return (casio_error_alloc);
+ }
+
+ cookie->stream = original;
+ cookie->size = COOKIE_BUFFER_SIZE;
+ reset_cookie(cookie);
+
+ /* Create the stream. */
+
+ return (casio_open_stream(streamp,
+ CASIO_OPENMODE_READ | CASIO_OPENMODE_WRITE | CASIO_OPENMODE_SCSI,
+ cookie, &seven_scsi_funcs, 0));
+}