8000 py/ringbuf: Add micropython.RingIO() interface for general use. · micropython/micropython@260821a · GitHub
[go: up one dir, main page]

Skip to content

Commit 260821a

Browse files
committed
py/ringbuf: Add micropython.RingIO() interface for general use.
Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
1 parent f1bdac3 commit 260821a

15 files changed

+373
-27
lines changed

docs/library/micropython.rst

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,71 @@ Functions
147147

148148
There is a finite queue to hold the scheduled functions and `schedule()`
149149
will raise a `RuntimeError` if the queue is full.
150+
151+
Classes
152+
-------
153+
154+
.. class:: RingIO(size)
155+
.. class:: RingIO(buffer)
156+
:noindex:
157+
158+
Provides a fixed-size ringbuffer for bytes with a stream interface. Can be
159+
considered like a fifo queue variant of `io.BytesIO`.
160+
161+
When created with integer size a suitable buffer will be allocated.
162+
Alternatively a `bytearray` or similar buffer protocol object can be provided
163+
to the constructor for in-place use.
164+
165+
The classic ringbuffer algorithm is used which allows for any size buffer
166+
to be used however one byte will be consumed for tracking. If initialised
167+
with an integer size this will be accounted for, for example ``RingIO(16)``
168+
will allocate a 17 byte buffer internally so it can hold 16 bytes of data.
169+
When passing in a pre-allocated buffer however one byte less than its
170+
original length will be available for storage, eg. ``RingIO(bytearray(16))``
171+
will only hold 15 bytes of data.
172+
173+
A RingIO instance can be IRQ / thread safe when used to pass data in a single
174+
direction eg. when written to in an IRQ and read from in a non-IRQ function
175+
(or vice versa). This does not hold if you try to eg. write to a single instance
176+
from both IRQ and non-IRQ code, this would often cause data corruption.
177+
178+
.. method:: RingIO.any()
179+
180+
Returns an integer counting the number of characters that can be read.
181+
182+
.. method:: RingIO.read([nbytes])
183+
184+
Read available characters. This is a non-blocking function. If ``nbytes``
185+
is specified then read at most that many bytes, otherwise read as much
186+
data as possible.
187+
188+
Return value: a bytes object containing the bytes read. Will be
189+
zero-length bytes object if no data is available.
190+
191+
.. method:: RingIO.readline([nbytes])
192+
193+
Read a line, ending in a newline character or return if one exists in
194+
the buffer, else return available bytes in buffer. If ``nbytes`` is
195+
specified then read at most that many bytes.
196+
197+
Return value: a bytes object containing the line read.
198+
199+
.. method:: RingIO.readinto(buf[, nbytes])
200+
201+
Read available bytes into the provided ``buf``. If ``nbytes`` is
202+
specified then read at most that many bytes. Otherwise, read at
203+
most ``len(buf)`` bytes.
204+
205+
Return value: Integer count of the number of bytes read into ``buf``.
206+
207+
.. method:: RingIO.write(buf)
208+
209+
Non-blocking write of bytes from ``buf`` into the ringbuffer, limited
210+
by the available space in the ringbuffer.
211+
212+
Return value: Integer count of bytes written.
213+
214+
.. method:: RingIO.close()
215+
216+
No-op provided as part of standard `stream` interface. Has no effect
217+
on data in the ringbuffer.

ports/unix/coverage.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,24 @@ static mp_obj_t extra_coverage(void) {
692692
ringbuf.iget = 0;
693693
ringbuf_put(&ringbuf, 0xaa);
694694
mp_printf(&mp_plat_print, "%d\n", ringbuf_get16(&ringbuf));
695+
696+
// ringbuf_put_bytes() / ringbuf_get_bytes() functions.
697+
ringbuf.iput = 0;
698+
ringbuf.iget = 0;
699+
uint8_t *put = (uint8_t *)"abc123";
700+
uint8_t get[7] = {0};
701+
mp_printf(&mp_plat_print, "%d\n", ringbuf_put_bytes(&ringbuf, put, 7));
702+
mp_printf(&mp_plat_print, "%d\n", ringbuf_get_bytes(&ringbuf, get, 7));
703+
mp_printf(&mp_plat_print, "%s\n", get);
704+
// Prefill ringbuffer
705+
for (size_t i = 0; i < (sizeof(buf) - 3); ++i) {
706+
ringbuf_put(&ringbuf, i);
707+
}
708+
// Should fail - too full.
709+
mp_printf(&mp_plat_print, "%d\n", ringbuf_put_bytes(&ringbuf, put, 7));
710+
// Should fail - buffer too big.
711+
uint8_t large[sizeof(buf) + 5] = {0};
712+
mp_printf(&mp_plat_print, "%d\n", ringbuf_put_bytes(&ringbuf, large, sizeof(large)));
695713
}
696714

697715
// pairheap

py/modmicropython.c

+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ static const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
200200
#if MICROPY_KBD_EXCEPTION
201201
{ MP_ROM_QSTR(MP_QSTR_kbd_intr), MP_ROM_PTR(&mp_micropython_kbd_intr_obj) },
202202
#endif
203+
#if MICROPY_PY_MICROPYTHON_RINGIO
204+
{ MP_ROM_QSTR(MP_QSTR_RingIO), MP_ROM_PTR(&mp_type_ringio) },
205+
#endif
203206
#if MICROPY_ENABLE_SCHEDULER
204207
{ MP_ROM_QSTR(MP_QSTR_schedule), MP_ROM_PTR(&mp_micropython_schedule_obj) },
205208
#endif

py/mpconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,11 @@ typedef double mp_float_t;
13061306
#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EVERYTHING)
13071307
#endif
13081308

1309+
// Support for micropython.RingIO()
1310+
#ifndef MICROPY_PY_MICROPYTHON_RINGIO
1311+
#define MICROPY_PY_MICROPYTHON_RINGIO (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
1312+
#endif
1313+
13091314
// Whether to provide "array" module. Note that large chunk of the
13101315
// underlying code is shared with "bytearray" builtin type, so to
13111316
// get real savings, it should be disabled too.

py/obj.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ extern const mp_obj_type_t mp_type_bound_meth;
843843
extern const mp_obj_type_t mp_type_property;
844844
extern const mp_obj_type_t mp_type_stringio;
845845
extern const mp_obj_type_t mp_type_bytesio;
846+
extern const mp_obj_type_t mp_type_ringio;
846847
extern const mp_obj_type_t mp_type_reversed;
847848
extern const mp_obj_type_t mp_type_polymorph_iter;
848849
#if MICROPY_ENABLE_FINALISER

py/objringio.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2024 Andrew Leech
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "ringbuf.h"
28+
#include "py/mpconfig.h"
29+
30+
#if MICROPY_PY_MICROPYTHON_RINGIO
31+
32+
#include "py/runtime.h"
33+
#include "py/stream.h"
34+
35+
typedef struct _micropython_ringio_obj_t {
36+
mp_obj_base_t base;
37+
ringbuf_t ringbuffer;
38+
} micropython_ringio_obj_t;
39+
40+
static mp_obj_t micropython_ringio_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
41+
mp_arg_check_num(n_args, n_kw, 1, 1, false);
42+
mp_int_t buff_size = -1;
43+
mp_buffer_info_t bufinfo = {NULL, 0, 0};
44+
45+
if (!mp_get_buffer(args[0], &bufinfo, MP_BUFFER_RW)) {
46+
buff_size = mp_obj_get_int(args[0]);
47+
}
48+
micropython_ringio_obj_t *self = mp_obj_malloc(micropython_ringio_obj_t, type);
49+
if (bufinfo.buf != NULL) {
50+
// buffer passed in, use it directly for ringbuffer.
51+
self->ringbuffer.buf = bufinfo.buf;
52+
self->ringbuffer.size = bufinfo.len;
53+
self->ringbuffer.iget = self->ringbuffer.iput = 0;
54+
} else {
55+
// Allocate new buffer, add one extra to buff_size as ringbuf consumes one byte for tracking.
56+
ringbuf_alloc(&(self->ringbuffer), buff_size + 1);
57+
}
58+
return MP_OBJ_FROM_PTR(self);
59+
}
60+
61+
static mp_uint_t micropython_ringio_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
62+
micropython_ringio_obj_t *self = MP_OBJ_TO_PTR(self_in);
63+
size = MIN(size, ringbuf_avail(&self->ringbuffer));
64+
ringbuf_memcpy_get_internal(&(self->ringbuffer), buf_in, size);
65+
*errcode = 0;
66+
return size;
67+
}
68+
69+
static mp_uint_t micropython_ringio_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
70+
micropython_ringio_obj_t *self = MP_OBJ_TO_PTR(self_in);
71+
size = MIN(size, ringbuf_free(&self->ringbuffer));
72+
ringbuf_memcpy_put_internal(&(self->ringbuffer), buf_in, size);
73+
*errcode = 0;
74+
return size;
75+
}
76+
77+
static mp_uint_t micropython_ringio_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
78+
micropython_ringio_obj_t *self = MP_OBJ_TO_PTR(self_in);
79+
switch (request) {
80+
case MP_STREAM_POLL: {
81+
mp_uint_t ret = 0;
82+
if ((arg & MP_STREAM_POLL_RD) && ringbuf_avail(&self->ringbuffer) > 0) {
83+
ret |= MP_STREAM_POLL_RD;
84+
}
85+
if ((arg & MP_STREAM_POLL_WR) && ringbuf_free(&self->ringbuffer) > 0) {
86+
ret |= MP_STREAM_POLL_WR;
87+
}
88+
return ret;
89+
}
90+
case MP_STREAM_CLOSE:
91+
return 0;
92+
}
93+
*errcode = MP_EINVAL;
94+
return MP_STREAM_ERROR;
95+
}
96+
97+
static mp_obj_t micropython_ringio_any(mp_obj_t self_in) {
98+
micropython_ringio_obj_t *self = MP_OBJ_TO_PTR(self_in);
99+
return MP_OBJ_NEW_SMALL_INT(ringbuf_avail(&self->ringbuffer));
100+
}
101+
static MP_DEFINE_CONST_FUN_OBJ_1(micropython_ringio_any_obj, micropython_ringio_any);
102+
103+
static const mp_rom_map_elem_t micropython_ringio_locals_dict_table[] = {
104+
{ MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&micropython_ringio_any_obj) },
105+
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
106+
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
107+
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
108+
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
109+
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
110+
111+
};
112+
static MP_DEFINE_CONST_DICT(micropython_ringio_locals_dict, micropython_ringio_locals_dict_table);
113+
114+
static const mp_stream_p_t ringio_stream_p = {
115+
.read = micropython_ringio_read,
116+
.write = micropython_ringio_write,
117+
.ioctl = micropython_ringio_ioctl,
118+
.is_text = false,
119+
};
120+
121+
MP_DEFINE_CONST_OBJ_TYPE(
122+
mp_type_ringio,
123+
MP_QSTR_RingIO,
124+
MP_TYPE_FLAG_NONE,
125+
make_new, micropython_ringio_make_new,
126+
protocol, &ringio_stream_p,
127+
locals_dict, &micropython_ringio_locals_dict
128+
);
129+
130+
#endif // MICROPY_PY_MICROPYTHON_RINGIO

py/py.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ set(MICROPY_SOURCE_PY
9595
${MICROPY_PY_DIR}/objproperty.c
9696
${MICROPY_PY_DIR}/objrange.c
9797
${MICROPY_PY_DIR}/objreversed.c
98+
${MICROPY_PY_DIR}/objringio.c
9899
${MICROPY_PY_DIR}/objset.c
99100
${MICROPY_PY_DIR}/objsingleton.c
100101
${MICROPY_PY_DIR}/objslice.c

py/py.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\
167167
objnamedtuple.o \
168168
objrange.o \
169169
objreversed.o \
170+
objringio.o \
170171
objset.o \
171172
objsingleton.o \
172173
objslice.o \

py/ringbuf.c

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
* THE SOFTWARE.
2525
*/
2626

27-
#include <string.h>
28-
2927
#include "ringbuf.h"
3028

3129
int ringbuf_get16(ringbuf_t *r) {
@@ -83,17 +81,7 @@ int ringbuf_get_bytes(ringbuf_t *r, uint8_t *data, size_t data_len) {
8381
if (ringbuf_avail(r) < data_len) {
8482
return (r->size <= data_len) ? -2 : -1;
8583
}
86-
uint32_t iget = r->iget;
87-
uint32_t iget_a = (iget + data_len) % r->size;
88-
uint8_t *datap = data;
89-
if (iget_a < iget) {
90-
// Copy part of the data from the space left at the end of the buffer
91-
memcpy(datap, r->buf + iget, r->size - iget);
92-
datap += (r->size - iget);
93-
iget = 0;
94-
}
95-
memcpy(datap, r->buf + iget, iget_a - iget);
96-
r->iget = iget_a;
84+
ringbuf_memcpy_get_internal(r, data, data_len);
9785
return 0;
9886
}
9987

@@ -105,16 +93,6 @@ int ringbuf_put_bytes(ringbuf_t *r, const uint8_t *data, size_t data_len) {
10593
if (ringbuf_free(r) < data_len) {
10694
return (r->size <= data_len) ? -2 : -1;
10795
}
108-
uint32_t iput = r->iput;
109-
uint32_t iput_a = (iput + data_len) % r->size;
110-
const uint8_t *datap = data;
111-
if (iput_a < iput) {
112-
// Copy part of the data to the end of the buffer
113-
memcpy(r->buf + iput, datap, r->size - iput);
114-
datap += (r->size - iput);
115-
iput = 0;
116-
}
117-
memcpy(r->buf + iput, datap, iput_a - iput);
118-
r->iput = iput_a;
96+
ringbuf_memcpy_put_internal(r, data, data_len);
11997
return 0;
12098
}

py/ringbuf.h

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@
2828

2929
#include <stddef.h>
3030
#include <stdint.h>
31+
#include <string.h>
3132

32-
#ifdef _MSC_VER
33-
#include "py/mpconfig.h" // For inline.
34-
#endif
33+
#include "py/mpconfig.h"
3534

3635
typedef struct _ringbuf_t {
3736
uint8_t *buf;
@@ -91,6 +90,38 @@ static inline size_t ringbuf_avail(ringbuf_t *r) {
9190
return (r->size + r->iput - r->iget) % r->size;
9291
}
9392

93+
static inline void ringbuf_memcpy_get_internal(ringbuf_t *r, uint8_t *data, size_t data_len) {
94+
// No bounds / space checking is performed here so ensure available size is checked before running this
95+
// otherwise data loss or buffer overflow can occur.
96+
uint32_t iget = r->iget;
97+
uint32_t iget_a = (iget + data_len) % r->size;
98+
uint8_t *datap = data;
99+
if (iget_a < iget) {
100+
// Copy part of the data from the space left at the end of the buffer
101+
memcpy(datap, r->buf + iget, r->size - iget);
102+
datap += (r->size - iget);
103+
iget = 0;
104+
}
105+
memcpy(datap, r->buf + iget, iget_a - iget);
106+
r->iget = iget_a;
107+
}
108+
109+
static inline void ringbuf_memcpy_put_internal(ringbuf_t *r, const uint8_t *data, size_t data_len) {
110+
// No bounds / space checking is performed here so ensure free size is checked before running this
111+
// otherwise data loss or buffer overflow can occur.
112+
uint32_t iput = r->iput;
113+
uint32_t iput_a = (iput + data_len) % r->size;
114+
const uint8_t *datap = data;
115+
if (iput_a < iput) {
116+
// Copy part of the data to the end of the buffer
117+
memcpy(r->buf + iput, datap, r->size - iput);
118+
datap += (r->size - iput);
119+
iput = 0;
120+
}
121+
memcpy(r->buf + iput, datap, iput_a - iput);
122+
r->iput = iput_a;
123+
}
124+
94125
// Note: big-endian. No-op if not enough room available for both bytes.
95126
int ringbuf_get16(ringbuf_t *r);
96127
int ringbuf_peek16(ringbuf_t *r);

0 commit comments

Comments
 (0)
0