aboutsummaryrefslogtreecommitdiff
path: root/libjaylink/emucom.c
blob: 0971da1015c4aa3a47fabc7d8bbbf1f472ab1812 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/*
 * This file is part of the libjaylink project.
 *
 * Copyright (C) 2015-2016 Marc Schink <jaylink-dev@marcschink.de>
 *
 * 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 2 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/>.
 */

#include <stdint.h>
#include <stdbool.h>

#include "libjaylink.h"
#include "libjaylink-internal.h"

/**
 * @file
 *
 * Emulator communication (EMUCOM).
 */

/** @cond PRIVATE */
#define CMD_EMUCOM			0xee

#define EMUCOM_CMD_READ			0x00
#define EMUCOM_CMD_WRITE		0x01

/** Bitmask for the error indication bit of an EMUCOM status code. */
#define EMUCOM_ERR			0x80000000

/** Error code indicating that the channel is not supported by the device. */
#define EMUCOM_ERR_NOT_SUPPORTED	0x80000001

/**
 * Error code indicating that the channel is not available for the requested
 * number of bytes to be read.
 *
 * The number of bytes available on this channel is encoded in the lower
 * 24 bits of the EMUCOM status code.
 *
 * @see EMUCOM_AVAILABLE_BYTES_MASK
 */
#define EMUCOM_ERR_NOT_AVAILABLE	0x81000000

/**
 * Bitmask to extract the number of available bytes on a channel from an EMUCOM
 * status code.
 */
#define EMUCOM_AVAILABLE_BYTES_MASK	0x00ffffff
/** @endcond */

/**
 * Read from an EMUCOM channel.
 *
 * @note This function must only be used if the device has the
 *       #JAYLINK_DEV_CAP_EMUCOM capability.
 *
 * @param[in,out] devh Device handle.
 * @param[in] channel Channel to read data from.
 * @param[out] buffer Buffer to store read data on success. Its content is
 *                    undefined on failure.
 * @param[in,out] length Number of bytes to read. On success, the value gets
 *                       updated with the actual number of bytes read. Unless
 *                       otherwise specified, the value is undefined on
 *                       failure.
 *
 * @retval JAYLINK_OK Success.
 * @retval JAYLINK_ERR_ARG Invalid arguments.
 * @retval JAYLINK_ERR_TIMEOUT A timeout occurred.
 * @retval JAYLINK_ERR_PROTO Protocol violation.
 * @retval JAYLINK_ERR_IO Input/output error.
 * @retval JAYLINK_ERR_DEV_NOT_SUPPORTED Channel is not supported by the
 *                                       device.
 * @retval JAYLINK_ERR_DEV_NOT_AVAILABLE Channel is not available for the
 *                                       requested amount of data. @p length is
 *                                       updated with the number of bytes
 *                                       available on this channel.
 * @retval JAYLINK_ERR_DEV Unspecified device error.
 * @retval JAYLINK_ERR Other error conditions.
 *
 * @since 0.1.0
 */
JAYLINK_API int jaylink_emucom_read(struct jaylink_device_handle *devh,
		uint32_t channel, uint8_t *buffer, uint32_t *length)
{
	int ret;
	struct jaylink_context *ctx;
	uint8_t buf[10];
	uint32_t tmp;

	if (!devh || !buffer || !length)
		return JAYLINK_ERR_ARG;

	ctx = devh->dev->ctx;
	ret = transport_start_write_read(devh, 10, 4, true);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_start_write_read() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	buf[0] = CMD_EMUCOM;
	buf[1] = EMUCOM_CMD_READ;

	buffer_set_u32(buf, channel, 2);
	buffer_set_u32(buf, *length, 6);

	ret = transport_write(devh, buf, 10);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_write() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	ret = transport_read(devh, buf, 4);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_read() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	tmp = buffer_get_u32(buf, 0);

	if (tmp == EMUCOM_ERR_NOT_SUPPORTED)
		return JAYLINK_ERR_DEV_NOT_SUPPORTED;

	if ((tmp & ~EMUCOM_AVAILABLE_BYTES_MASK) == EMUCOM_ERR_NOT_AVAILABLE) {
		*length = tmp & EMUCOM_AVAILABLE_BYTES_MASK;
		return JAYLINK_ERR_DEV_NOT_AVAILABLE;
	}

	if (tmp & EMUCOM_ERR) {
		log_err(ctx, "Failed to read from channel 0x%x: 0x%x",
			channel, tmp);
		return JAYLINK_ERR_DEV;
	}

	if (tmp > *length) {
		log_err(ctx, "Requested at most %u bytes but device "
			"returned %u bytes", *length, tmp);
		return JAYLINK_ERR_PROTO;
	}

	*length = tmp;

	if (!tmp)
		return JAYLINK_OK;

	ret = transport_start_read(devh, tmp);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_start_read() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	ret = transport_read(devh, buffer, tmp);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_read() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	return JAYLINK_OK;
}

/**
 * Write to an EMUCOM channel.
 *
 * @note This function must only be used if the device has the
 *       #JAYLINK_DEV_CAP_EMUCOM capability.
 *
 * @param[in,out] devh Device handle.
 * @param[in] channel Channel to write data to.
 * @param[in] buffer Buffer to write data from.
 * @param[in,out] length Number of bytes to write. On success, the value gets
 *                       updated with the actual number of bytes written. The
 *                       value is undefined on failure.
 *
 * @retval JAYLINK_OK Success.
 * @retval JAYLINK_ERR_ARG Invalid arguments.
 * @retval JAYLINK_ERR_TIMEOUT A timeout occurred.
 * @retval JAYLINK_ERR_PROTO Protocol violation.
 * @retval JAYLINK_ERR_IO Input/output error.
 * @retval JAYLINK_ERR_DEV_NOT_SUPPORTED Channel is not supported by the
 *                                       device.
 * @retval JAYLINK_ERR_DEV Unspecified device error.
 * @retval JAYLINK_ERR Other error conditions.
 *
 * @since 0.1.0
 */
JAYLINK_API int jaylink_emucom_write(struct jaylink_device_handle *devh,
		uint32_t channel, const uint8_t *buffer, uint32_t *length)
{
	int ret;
	struct jaylink_context *ctx;
	uint8_t buf[10];
	uint32_t tmp;

	if (!devh || !buffer || !length)
		return JAYLINK_ERR_ARG;

	if (!*length)
		return JAYLINK_ERR_ARG;

	ctx = devh->dev->ctx;
	ret = transport_start_write(devh, 10, true);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_start_write() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	buf[0] = CMD_EMUCOM;
	buf[1] = EMUCOM_CMD_WRITE;

	buffer_set_u32(buf, channel, 2);
	buffer_set_u32(buf, *length, 6);

	ret = transport_write(devh, buf, 10);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_write() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	ret = transport_start_write_read(devh, *length, 4, false);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_start_write_read() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	ret = transport_write(devh, buffer, *length);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_write() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	ret = transport_read(devh, buf, 4);

	if (ret != JAYLINK_OK) {
		log_err(ctx, "transport_read() failed: %s",
			jaylink_strerror(ret));
		return ret;
	}

	tmp = buffer_get_u32(buf, 0);

	if (tmp == EMUCOM_ERR_NOT_SUPPORTED)
		return JAYLINK_ERR_DEV_NOT_SUPPORTED;

	if (tmp & EMUCOM_ERR) {
		log_err(ctx, "Failed to write to channel 0x%x: 0x%x",
			channel, tmp);
		return JAYLINK_ERR_DEV;
	}

	if (tmp > *length) {
		log_err(ctx, "Only %u bytes were supposed to be written, but "
			"the device reported %u written bytes", *length, tmp);
		return JAYLINK_ERR_PROTO;
	}

	*length = tmp;

	return JAYLINK_OK;
}