summaryrefslogtreecommitdiffstats
path: root/common/serdev.c
blob: 4a6dbefe6133ae8f407a780749ea5e9dd0ab7158 (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

#include <common.h>
#include <serdev.h>

static void serdev_device_poller(void *context)
{
	struct serdev_device *serdev = context;
	struct console_device *cdev = to_console_device(serdev);
	unsigned char *buf = serdev->buf;
	int ret, len;

	/*
	 * Since this callback is a part of poller infrastructure we
	 * want to use _non_interruptible version of the function
	 * below to prevent recursion from happening (regular
	 * console_drain will call is_timeout, which might end up
	 * calling this function again).
	 */
	len = console_drain_non_interruptible(cdev, serdev->fifo, buf,
					      PAGE_SIZE,
					      serdev->polling_window);
	while (len > 0) {
		ret = serdev->receive_buf(serdev, buf, len);
		len -= ret;
		buf += ret;
	}

	if (serdev->polling_interval) {
		/*
		 * Re-schedule ourselves in 'serdev->polling_interval'
		 * nanoseconds
		 */
		poller_call_async(&serdev->poller,
				  serdev->polling_interval,
				  serdev_device_poller,
				  serdev);
	} else {
		poller_async_cancel(&serdev->poller);
	}
}

static int serdev_device_set_polling_interval(struct param_d *param, void *serdev)
{
	/*
	 * We execute poller ever time polling_interval changes to get
	 * any unprocessed immediate Rx data as well as to propagate
	 * polling_interval chagnes to outstanging async pollers.
	 */
	serdev_device_poller(serdev);
	return 0;
}

int serdev_device_open(struct serdev_device *serdev)
{
	struct console_device *cdev = to_console_device(serdev);
	struct param_d *p;
	int ret;

	if (!cdev->putc || !cdev->getc)
		return -EINVAL;

	if (!serdev->polling_window)
		return -EINVAL;

	serdev->buf = xzalloc(PAGE_SIZE);
	serdev->fifo = kfifo_alloc(PAGE_SIZE);
	if (!serdev->fifo)
		return -ENOMEM;

	ret = poller_async_register(&serdev->poller);
	if (ret)
		return ret;

	ret = console_open(cdev);
	if (ret)
		return ret;

	p = dev_add_param_uint64(serdev->dev, "polling_interval",
				 serdev_device_set_polling_interval, NULL,
				 &serdev->polling_interval, "%llu", serdev);
	if (IS_ERR(p))
		return PTR_ERR(p);

	return 0;
}

unsigned int serdev_device_set_baudrate(struct serdev_device *serdev,
					unsigned int speed)
{
	struct console_device *cdev = to_console_device(serdev);

	if (console_set_baudrate(cdev, speed) < 0)
		return 0;

	return console_get_baudrate(cdev);
}

int serdev_device_write(struct serdev_device *serdev, const unsigned char *buf,
			size_t count, unsigned long timeout)
{
	struct console_device *cdev = to_console_device(serdev);

	while (count--)
		cdev->putc(cdev, *buf++);
	/*
	 * Poll Rx once right after we just send some data in case our
	 * serdev device implements command/response type of a
	 * protocol and we need to start draining input as soon as
	 * possible.
	 */
	serdev_device_poller(serdev);
	return 0;
}

/*
 * NOTE: Code below is given primarily as an example of serdev API
 * usage. It may or may not be as useful or work as well as the
 * functions above.
 */

struct serdev_device_reader {
	unsigned char *buf;
	size_t len;
	size_t capacity;
};

static int serdev_device_reader_receive_buf(struct serdev_device *serdev,
					    const unsigned char *buf,
					    size_t size)
{
	struct device_d *dev = serdev->dev;
	struct serdev_device_reader *r = dev->priv;
	const size_t room = min(r->capacity - r->len, size);

	memcpy(r->buf + r->len, buf, room);
	r->len += room;
	/*
	 * It's important we return 'size' even if we didn't trully
	 * consume all of the data, since otherwise serdev will keep
	 * re-executing us until we do. Given the logic above that
	 * would mean infinite loop.
	 */
	return size;
}

/**
 * serdev_device_reader_open - Open a reader serdev device
 *
 * @serdev:	Underlying serdev device
 * @capacity:	Storage capacity of the reader
 *
 * This function is inteded for creating of reader serdev devices that
 * can be used in conjunction with serdev_device_read() to perform
 * trivial fixed length reads from a serdev device.
 */
int serdev_device_reader_open(struct serdev_device *serdev, size_t capacity)
{
	struct serdev_device_reader *r;

	if (serdev->receive_buf)
		return -EINVAL;

	r = xzalloc(sizeof(*r));
	r->capacity = capacity;
	r->buf = xzalloc(capacity);

	serdev->receive_buf = serdev_device_reader_receive_buf;
	serdev->dev->priv   = r;

	return 0;
}

/**
 * serdev_device_read - Read data from serdev device
 *
 * @serdev:	Serdev device to read from (must be a serdev reader)
 * @buf:	Buffer to read data into
 * @count:	Number of bytes to read
 * @timeout:	Read operation timeout
 *
 */
int serdev_device_read(struct serdev_device *serdev, unsigned char *buf,
		       size_t count, unsigned long timeout)
{
	struct device_d *dev = serdev->dev;
	struct serdev_device_reader *r = dev->priv;
	int ret;

	uint64_t start = get_time_ns();

	if (serdev->receive_buf != serdev_device_reader_receive_buf)
		return -EINVAL;

	/*
	 * is_timeout will implicitly poll serdev via poller
	 * infrastructure
	 */
	while (r->len < count) {
		if (is_timeout(start, timeout))
			return -ETIMEDOUT;
	}

	memcpy(buf, r->buf, count);
	ret = (r->len == count) ? 0 : -EMSGSIZE;
	r->len = 0;

	return ret;
}