diff options
Diffstat (limited to 'extmod')
-rw-r--r-- | extmod/fsusermount.c | 10 | ||||
-rw-r--r-- | extmod/machine_i2c.c | 464 | ||||
-rw-r--r-- | extmod/machine_i2c.h | 34 | ||||
-rw-r--r-- | extmod/modframebuf.c | 205 | ||||
-rw-r--r-- | extmod/modlwip.c | 130 | ||||
-rw-r--r-- | extmod/moduos_dupterm.c | 5 | ||||
-rw-r--r-- | extmod/modussl.c | 55 | ||||
-rw-r--r-- | extmod/modwebrepl.c | 330 | ||||
-rw-r--r-- | extmod/modwebsocket.c | 66 |
9 files changed, 1251 insertions, 48 deletions
diff --git a/extmod/fsusermount.c b/extmod/fsusermount.c index d86e57b4b8..8f789ca033 100644 --- a/extmod/fsusermount.c +++ b/extmod/fsusermount.c @@ -81,8 +81,7 @@ fs_user_mount_t *fatfs_mount_mkfs(mp_uint_t n_args, const mp_obj_t *pos_args, mp } // create new object - fs_user_mount_t *vfs; - MP_STATE_PORT(fs_user_mount)[i] = vfs = m_new_obj(fs_user_mount_t); + fs_user_mount_t *vfs = m_new_obj(fs_user_mount_t); vfs->str = mnt_str; vfs->len = mnt_len; vfs->flags = FSUSER_FREE_OBJ; @@ -108,6 +107,11 @@ fs_user_mount_t *fatfs_mount_mkfs(mp_uint_t n_args, const mp_obj_t *pos_args, mp vfs->writeblocks[0] = MP_OBJ_NULL; } + // Register the vfs object so that it can be found by the FatFS driver using + // ff_get_ldnumber. We don't register it any earlier than this point in case there + // is an exception, in which case there would remain a partially mounted device. + MP_STATE_PORT(fs_user_mount)[i] = vfs; + // mount the block device (if mkfs, only pre-mount) FRESULT res = f_mount(&vfs->fatfs, vfs->str, !mkfs); // check the result @@ -120,6 +124,7 @@ mkfs: res = f_mkfs(vfs->str, 1, 0); if (res != FR_OK) { mkfs_error: + MP_STATE_PORT(fs_user_mount)[i] = NULL; nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't mkfs")); } if (mkfs) { @@ -132,6 +137,7 @@ mkfs_error: return NULL; } } else { + MP_STATE_PORT(fs_user_mount)[i] = NULL; nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "can't mount")); } diff --git a/extmod/machine_i2c.c b/extmod/machine_i2c.c new file mode 100644 index 0000000000..e3bdb36925 --- /dev/null +++ b/extmod/machine_i2c.c @@ -0,0 +1,464 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +#include "py/mphal.h" +#include "py/runtime.h" +#include "extmod/machine_i2c.h" + +#if MICROPY_PY_MACHINE_I2C + +typedef struct _machine_i2c_obj_t { + mp_obj_base_t base; + uint32_t us_delay; + mp_hal_pin_obj_t scl; + mp_hal_pin_obj_t sda; +} machine_i2c_obj_t; + +STATIC void mp_hal_i2c_delay(machine_i2c_obj_t *self) { + // We need to use an accurate delay to get acceptable I2C + // speeds (eg 1us should be not much more than 1us). + mp_hal_delay_us_fast(self->us_delay); +} + +STATIC void mp_hal_i2c_scl_low(machine_i2c_obj_t *self) { + mp_hal_pin_low(self->scl); +} + +STATIC void mp_hal_i2c_scl_release(machine_i2c_obj_t *self) { + mp_hal_pin_od_high(self->scl); +} + +STATIC void mp_hal_i2c_sda_low(machine_i2c_obj_t *self) { + mp_hal_pin_low(self->sda); +} + +STATIC void mp_hal_i2c_sda_release(machine_i2c_obj_t *self) { + mp_hal_pin_od_high(self->sda); +} + +STATIC int mp_hal_i2c_sda_read(machine_i2c_obj_t *self) { + return mp_hal_pin_read(self->sda); +} + +STATIC void mp_hal_i2c_start(machine_i2c_obj_t *self) { + mp_hal_i2c_sda_release(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_release(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_sda_low(self); + mp_hal_i2c_delay(self); +} + +STATIC void mp_hal_i2c_stop(machine_i2c_obj_t *self) { + mp_hal_i2c_delay(self); + mp_hal_i2c_sda_low(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_release(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_sda_release(self); + mp_hal_i2c_delay(self); +} + +STATIC void mp_hal_i2c_init(machine_i2c_obj_t *self, uint32_t freq) { + self->us_delay = 500000 / freq; + if (self->us_delay == 0) { + self->us_delay = 1; + } + mp_hal_pin_config_od(self->scl); + mp_hal_pin_config_od(self->sda); + mp_hal_i2c_stop(self); +} + +STATIC int mp_hal_i2c_write_byte(machine_i2c_obj_t *self, uint8_t val) { + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_low(self); + + for (int i = 7; i >= 0; i--) { + if ((val >> i) & 1) { + mp_hal_i2c_sda_release(self); + } else { + mp_hal_i2c_sda_low(self); + } + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_release(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_low(self); + } + + mp_hal_i2c_sda_release(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_release(self); + mp_hal_i2c_delay(self); + + int ret = mp_hal_i2c_sda_read(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_low(self); + + return !ret; +} + +STATIC void mp_hal_i2c_write(machine_i2c_obj_t *self, uint8_t addr, uint8_t *data, size_t len) { + mp_hal_i2c_start(self); + if (!mp_hal_i2c_write_byte(self, addr << 1)) { + goto er; + } + while (len--) { + if (!mp_hal_i2c_write_byte(self, *data++)) { + goto er; + } + } + mp_hal_i2c_stop(self); + return; + +er: + mp_hal_i2c_stop(self); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "I2C bus error")); +} + +STATIC int mp_hal_i2c_read_byte(machine_i2c_obj_t *self, uint8_t *val, int nack) { + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_low(self); + mp_hal_i2c_delay(self); + + uint8_t data = 0; + for (int i = 7; i >= 0; i--) { + mp_hal_i2c_scl_release(self); + mp_hal_i2c_delay(self); + data = (data << 1) | mp_hal_i2c_sda_read(self); + mp_hal_i2c_scl_low(self); + mp_hal_i2c_delay(self); + } + *val = data; + + // send ack/nack bit + if (!nack) { + mp_hal_i2c_sda_low(self); + } + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_release(self); + mp_hal_i2c_delay(self); + mp_hal_i2c_scl_low(self); + mp_hal_i2c_sda_release(self); + + return 1; // success +} + +STATIC void mp_hal_i2c_read(machine_i2c_obj_t *self, uint8_t addr, uint8_t *data, size_t len) { + mp_hal_i2c_start(self); + if (!mp_hal_i2c_write_byte(self, (addr << 1) | 1)) { + goto er; + } + while (len--) { + if (!mp_hal_i2c_read_byte(self, data++, len == 0)) { + goto er; + } + } + mp_hal_i2c_stop(self); + return; + +er: + mp_hal_i2c_stop(self); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "I2C bus error")); +} + +STATIC void mp_hal_i2c_write_mem(machine_i2c_obj_t *self, uint8_t addr, uint16_t memaddr, const uint8_t *src, size_t len) { + // start the I2C transaction + mp_hal_i2c_start(self); + + // write the slave address and the memory address within the slave + if (!mp_hal_i2c_write_byte(self, addr << 1)) { + goto er; + } + if (!mp_hal_i2c_write_byte(self, memaddr)) { + goto er; + } + + // write the buffer to the I2C memory + while (len--) { + if (!mp_hal_i2c_write_byte(self, *src++)) { + goto er; + } + } + + // finish the I2C transaction + mp_hal_i2c_stop(self); + return; + +er: + mp_hal_i2c_stop(self); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "I2C bus error")); +} + +STATIC void mp_hal_i2c_read_mem(machine_i2c_obj_t *self, uint8_t addr, uint16_t memaddr, uint8_t *dest, size_t len) { + // start the I2C transaction + mp_hal_i2c_start(self); + + // write the slave address and the memory address within the slave + if (!mp_hal_i2c_write_byte(self, addr << 1)) { + goto er; + } + if (!mp_hal_i2c_write_byte(self, memaddr)) { + goto er; + } + + // i2c_read will do a repeated start, and then read the I2C memory + mp_hal_i2c_read(self, addr, dest, len); + return; + +er: + mp_hal_i2c_stop(self); + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "I2C bus error")); +} + +/******************************************************************************/ +// MicroPython bindings for I2C + +STATIC void machine_i2c_obj_init_helper(machine_i2c_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_scl, ARG_sda, ARG_freq }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); + self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); + mp_hal_i2c_init(self, args[ARG_freq].u_int); +} + +STATIC mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, MP_OBJ_FUN_ARGS_MAX, true); + machine_i2c_obj_t *self = m_new_obj(machine_i2c_obj_t); + self->base.type = &machine_i2c_type; + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + machine_i2c_obj_init_helper(self, n_args, args, &kw_args); + return (mp_obj_t)self; +} + +STATIC mp_obj_t machine_i2c_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + machine_i2c_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_init_obj, 1, machine_i2c_obj_init); + +STATIC mp_obj_t machine_i2c_scan(mp_obj_t self_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_obj_t list = mp_obj_new_list(0, NULL); + // 7-bit addresses 0b0000xxx and 0b1111xxx are reserved + for (int addr = 0x08; addr < 0x78; ++addr) { + mp_hal_i2c_start(self); + int ack = mp_hal_i2c_write_byte(self, (addr << 1) | 1); + if (ack) { + mp_obj_list_append(list, MP_OBJ_NEW_SMALL_INT(addr)); + } + mp_hal_i2c_stop(self); + } + return list; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_i2c_scan_obj, machine_i2c_scan); + +STATIC mp_obj_t machine_i2c_start(mp_obj_t self_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_hal_i2c_start(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_i2c_start_obj, machine_i2c_start); + +STATIC mp_obj_t machine_i2c_stop(mp_obj_t self_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_hal_i2c_stop(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_i2c_stop_obj, machine_i2c_stop); + +STATIC mp_obj_t machine_i2c_readinto(mp_obj_t self_in, mp_obj_t buf_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // get the buffer to read into + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE); + + // do the read + uint8_t *dest = bufinfo.buf; + while (bufinfo.len--) { + if (!mp_hal_i2c_read_byte(self, dest++, bufinfo.len == 0)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "I2C bus error")); + } + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2c_readinto_obj, machine_i2c_readinto); + +STATIC mp_obj_t machine_i2c_write(mp_obj_t self_in, mp_obj_t buf_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // get the buffer to write from + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + + // do the write + uint8_t *src = bufinfo.buf; + while (bufinfo.len--) { + if (!mp_hal_i2c_write_byte(self, *src++)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "I2C bus error")); + } + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(machine_i2c_write_obj, machine_i2c_write); + +STATIC mp_obj_t machine_i2c_readfrom(mp_obj_t self_in, mp_obj_t addr_in, mp_obj_t nbytes_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + vstr_t vstr; + vstr_init_len(&vstr, mp_obj_get_int(nbytes_in)); + mp_hal_i2c_read(self, mp_obj_get_int(addr_in), (uint8_t*)vstr.buf, vstr.len); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_3(machine_i2c_readfrom_obj, machine_i2c_readfrom); + +STATIC mp_obj_t machine_i2c_readfrom_into(mp_obj_t self_in, mp_obj_t addr_in, mp_obj_t buf_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE); + mp_hal_i2c_read(self, mp_obj_get_int(addr_in), (uint8_t*)bufinfo.buf, bufinfo.len); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(machine_i2c_readfrom_into_obj, machine_i2c_readfrom_into); + +STATIC mp_obj_t machine_i2c_writeto(mp_obj_t self_in, mp_obj_t addr_in, mp_obj_t buf_in) { + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + mp_hal_i2c_write(self, mp_obj_get_int(addr_in), bufinfo.buf, bufinfo.len); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_i2c_writeto_obj, machine_i2c_writeto); + +STATIC mp_obj_t machine_i2c_readfrom_mem(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_addr, ARG_memaddr, ARG_n, ARG_addrsize }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_memaddr, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_n, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + //{ MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, TODO + }; + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // create the buffer to store data into + vstr_t vstr; + vstr_init_len(&vstr, args[ARG_n].u_int); + + // do the transfer + mp_hal_i2c_read_mem(self, args[ARG_addr].u_int, args[ARG_memaddr].u_int, (uint8_t*)vstr.buf, vstr.len); + return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr); +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_readfrom_mem_obj, 1, machine_i2c_readfrom_mem); + +STATIC mp_obj_t machine_i2c_readfrom_mem_into(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_addr, ARG_memaddr, ARG_buf, ARG_addrsize }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_memaddr, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + //{ MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, TODO + }; + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // get the buffer to store data into + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_WRITE); + + // do the transfer + mp_hal_i2c_read_mem(self, args[ARG_addr].u_int, args[ARG_memaddr].u_int, bufinfo.buf, bufinfo.len); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_readfrom_mem_into_obj, 1, machine_i2c_readfrom_mem_into); + +STATIC mp_obj_t machine_i2c_writeto_mem(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_addr, ARG_memaddr, ARG_buf, ARG_addrsize }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_addr, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_memaddr, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + //{ MP_QSTR_addrsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, TODO + }; + machine_i2c_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // get the buffer to write the data from + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_READ); + + // do the transfer + mp_hal_i2c_write_mem(self, args[ARG_addr].u_int, args[ARG_memaddr].u_int, bufinfo.buf, bufinfo.len); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_writeto_mem_obj, 1, machine_i2c_writeto_mem); + +STATIC const mp_rom_map_elem_t machine_i2c_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2c_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&machine_i2c_scan_obj) }, + + // primitive I2C operations + { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&machine_i2c_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&machine_i2c_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&machine_i2c_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&machine_i2c_write_obj) }, + + // standard bus operations + { MP_ROM_QSTR(MP_QSTR_readfrom), MP_ROM_PTR(&machine_i2c_readfrom_obj) }, + { MP_ROM_QSTR(MP_QSTR_readfrom_into), MP_ROM_PTR(&machine_i2c_readfrom_into_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeto), MP_ROM_PTR(&machine_i2c_writeto_obj) }, + + // memory operations + { MP_ROM_QSTR(MP_QSTR_readfrom_mem), MP_ROM_PTR(&machine_i2c_readfrom_mem_obj) }, + { MP_ROM_QSTR(MP_QSTR_readfrom_mem_into), MP_ROM_PTR(&machine_i2c_readfrom_mem_into_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeto_mem), MP_ROM_PTR(&machine_i2c_writeto_mem_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(machine_i2c_locals_dict, machine_i2c_locals_dict_table); + +const mp_obj_type_t machine_i2c_type = { + { &mp_type_type }, + .name = MP_QSTR_I2C, + .make_new = machine_i2c_make_new, + .locals_dict = (mp_obj_dict_t*)&machine_i2c_locals_dict, +}; + +#endif // MICROPY_PY_MACHINE_I2C diff --git a/extmod/machine_i2c.h b/extmod/machine_i2c.h new file mode 100644 index 0000000000..03fa6422ad --- /dev/null +++ b/extmod/machine_i2c.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __MICROPY_INCLUDED_EXTMOD_MACHINE_I2C_H__ +#define __MICROPY_INCLUDED_EXTMOD_MACHINE_I2C_H__ + +#include "py/obj.h" + +extern const mp_obj_type_t machine_i2c_type; + +#endif // __MICROPY_INCLUDED_EXTMOD_MACHINE_I2C_H__ diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c new file mode 100644 index 0000000000..d0ef238075 --- /dev/null +++ b/extmod/modframebuf.c @@ -0,0 +1,205 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdio.h> +#include <string.h> + +#include "py/nlr.h" +#include "py/obj.h" +#include "py/runtime.h" + +#if MICROPY_PY_FRAMEBUF + +#include "font_petme128_8x8.h" + +// 1-bit frame buffer, each byte is a column of 8 pixels +typedef struct _mp_obj_framebuf1_t { + mp_obj_base_t base; + uint8_t *buf; + uint16_t width, height, stride; +} mp_obj_framebuf1_t; + +STATIC mp_obj_t framebuf1_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 3, 4, false); + + mp_obj_framebuf1_t *o = m_new_obj(mp_obj_framebuf1_t); + o->base.type = type; + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[0], &bufinfo, MP_BUFFER_WRITE); + o->buf = bufinfo.buf; + + o->width = mp_obj_get_int(args[1]); + o->height = mp_obj_get_int(args[2]); + o->stride = o->width; + if (n_args >= 4) { + o->stride = mp_obj_get_int(args[3]); + } + + return MP_OBJ_FROM_PTR(o); +} + +STATIC mp_obj_t framebuf1_fill(mp_obj_t self_in, mp_obj_t col_in) { + mp_obj_framebuf1_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t col = mp_obj_get_int(col_in); + if (col) { + col = 0xff; + } + for (int y = 0; y < self->height / 8; ++y) { + memset(self->buf + y * self->stride, col, self->width); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(framebuf1_fill_obj, framebuf1_fill); + +STATIC mp_obj_t framebuf1_pixel(size_t n_args, const mp_obj_t *args) { + mp_obj_framebuf1_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + if (0 <= x && x < self->width && 0 <= y && y < self->height) { + int index = (y / 8) * self->stride + x; + if (n_args == 3) { + // get + return MP_OBJ_NEW_SMALL_INT(self->buf[index] >> (y & 7)); + } else { + // set + if (mp_obj_get_int(args[3])) { + self->buf[index] |= (1 << (y & 7)); + } else { + self->buf[index] &= ~(1 << (y & 7)); + } + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf1_pixel_obj, 3, 4, framebuf1_pixel); + +STATIC mp_obj_t framebuf1_scroll(mp_obj_t self_in, mp_obj_t xstep_in, mp_obj_t ystep_in) { + mp_obj_framebuf1_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t xstep = mp_obj_get_int(xstep_in); + mp_int_t ystep = mp_obj_get_int(ystep_in); + if (xstep == 0 && ystep > 0) { + for (int y = self->height / 8; y > 0;) { + --y; + for (int x = 0; x < self->width; ++x) { + int prev = 0; + if (y > 0) { + prev = (self->buf[(y - 1) * self->stride + x] >> (8 - ystep)) & ((1 << ystep) - 1); + } + self->buf[y * self->stride + x] = (self->buf[y * self->stride + x] << ystep) | prev; + } + } + } else if (xstep == 0 && ystep < 0) { + for (int y = 0; y < self->height / 8; ++y) { + for (int x = 0; x < self->width; ++x) { + int prev = 0; + if (y + 1 < self->height / 8) { + prev = self->buf[(y + 1) * self->stride + x] << (8 + ystep); + } + self->buf[y * self->stride + x] = (self->buf[y * self->stride + x] >> -ystep) | prev; + } + } + } + // TODO xstep!=0 + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(framebuf1_scroll_obj, framebuf1_scroll); + +STATIC mp_obj_t framebuf1_text(size_t n_args, const mp_obj_t *args) { + // extract arguments + mp_obj_framebuf1_t *self = MP_OBJ_TO_PTR(args[0]); + const char *str = mp_obj_str_get_str(args[1]); + mp_int_t x0 = mp_obj_get_int(args[2]); + mp_int_t y0 = mp_obj_get_int(args[3]); + mp_int_t col = 1; + if (n_args >= 5) { + col = mp_obj_get_int(args[4]); + } + + // loop over chars + for (; *str; ++str) { + // get char and make sure its in range of font + int chr = *(uint8_t*)str; + if (chr < 32 || chr > 127) { + chr = 127; + } + // get char data + const uint8_t *chr_data = &font_petme128_8x8[(chr - 32) * 8]; + // loop over char data + for (int j = 0; j < 8; j++, x0++) { + if (0 <= x0 && x0 < self->width) { // clip x + uint vline_data = chr_data[j]; // each byte is a column of 8 pixels, LSB at top + for (int y = y0; vline_data; vline_data >>= 1, y++) { // scan over vertical column + if (vline_data & 1) { // only draw if pixel set + if (0 <= y && y < self->height) { // clip y + uint byte_pos = x0 + self->stride * ((uint)y >> 3); + if (col == 0) { + // clear pixel + self->buf[byte_pos] &= ~(1 << (y & 7)); + } else { + // set pixel + self->buf[byte_pos] |= 1 << (y & 7); + } + } + } + } + } + } + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf1_text_obj, 4, 5, framebuf1_text); + +STATIC const mp_rom_map_elem_t framebuf1_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_fill), MP_ROM_PTR(&framebuf1_fill_obj) }, + { MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&framebuf1_pixel_obj) }, + { MP_ROM_QSTR(MP_QSTR_scroll), MP_ROM_PTR(&framebuf1_scroll_obj) }, + { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&framebuf1_text_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(framebuf1_locals_dict, framebuf1_locals_dict_table); + +STATIC const mp_obj_type_t mp_type_framebuf1 = { + { &mp_type_type }, + .name = MP_QSTR_FrameBuffer1, + .make_new = framebuf1_make_new, + .locals_dict = (mp_obj_t)&framebuf1_locals_dict, +}; + +STATIC const mp_rom_map_elem_t framebuf_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_framebuf) }, + { MP_ROM_QSTR(MP_QSTR_FrameBuffer1), MP_ROM_PTR(&mp_type_framebuf1) }, +}; + +STATIC MP_DEFINE_CONST_DICT(framebuf_module_globals, framebuf_module_globals_table); + +const mp_obj_module_t mp_module_framebuf = { + .base = { &mp_type_module }, + .name = MP_QSTR_framebuf, + .globals = (mp_obj_dict_t*)&framebuf_module_globals, +}; + +#endif // MICROPY_PY_FRAMEBUF diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 1ebcd8923c..090e1005a8 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -43,6 +43,13 @@ #include "lwip/udp.h" //#include "lwip/raw.h" #include "lwip/dns.h" +#include "lwip/tcp_impl.h" + +#if 0 // print debugging info +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_printf(...) (void)0 +#endif // For compatibilily with older lwIP versions. #ifndef ip_set_option @@ -228,6 +235,7 @@ typedef struct _lwip_socket_obj_t { struct pbuf *pbuf; struct tcp_pcb *connection; } incoming; + mp_obj_t callback; byte peer[4]; mp_uint_t peer_port; mp_uint_t timeout; @@ -255,6 +263,12 @@ static inline void poll_sockets(void) { /*******************************************************************************/ // Callback functions for the lwIP raw API. +static inline void exec_user_callback(lwip_socket_obj_t *socket) { + if (socket->callback != MP_OBJ_NULL) { + mp_call_function_1_protected(socket->callback, socket); + } +} + // Callback for incoming UDP packets. We simply stash the packet and the source address, // in case we need it for recvfrom. STATIC void _lwip_udp_incoming(void *arg, struct udp_pcb *upcb, struct pbuf *p, ip_addr_t *addr, u16_t port) { @@ -303,11 +317,13 @@ STATIC err_t _lwip_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { tcp_recv(newpcb, _lwip_tcp_recv_unaccepted); if (socket->incoming.connection != NULL) { + DEBUG_printf("_lwip_tcp_accept: Tried to queue >1 pcb waiting for accept\n"); // We need to handle this better. This single-level structure makes the // backlog setting kind of pointless. FIXME return ERR_BUF; } else { socket->incoming.connection = newpcb; + exec_user_callback(socket); return ERR_OK; } } @@ -318,13 +334,18 @@ STATIC err_t _lwip_tcp_recv(void *arg, struct tcp_pcb *tcpb, struct pbuf *p, err if (p == NULL) { // Other side has closed connection. + DEBUG_printf("_lwip_tcp_recv[%p]: other side closed connection\n", socket); socket->state = STATE_PEER_CLOSED; + exec_user_callback(socket); return ERR_OK; } else if (socket->incoming.pbuf != NULL) { // No room in the inn, let LWIP know it's still responsible for delivery later return ERR_BUF; } socket->incoming.pbuf = p; + + exec_user_callback(socket); + return ERR_OK; } @@ -359,7 +380,10 @@ STATIC mp_uint_t lwip_udp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui pbuf_free(p); - if (err != ERR_OK) { + // udp_sendto can return 1 on occasion for ESP8266 port. It's not known why + // but it seems that the send actually goes through without error in this case. + // So we treat such cases as a success until further investigation. + if (err != ERR_OK && err != 1) { *_errno = error_lookup_table[-err]; return -1; } @@ -401,15 +425,27 @@ STATIC mp_uint_t lwip_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ return (mp_uint_t) result; } +// For use in stream virtual methods +#define STREAM_ERROR_CHECK(socket) \ + if (socket->state < 0) { \ + *_errno = error_lookup_table[-socket->state]; \ + return MP_STREAM_ERROR; \ + } \ + assert(socket->pcb.tcp); + + // Helper function for send/sendto to handle TCP packets STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_uint_t len, int *_errno) { + // Check for any pending errors + STREAM_ERROR_CHECK(socket); + u16_t available = tcp_sndbuf(socket->pcb.tcp); if (available == 0) { // Non-blocking socket if (socket->timeout == 0) { *_errno = EAGAIN; - return -1; + return MP_STREAM_ERROR; } mp_uint_t start = mp_hal_ticks_ms(); @@ -422,15 +458,13 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui while (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16) { if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = ETIMEDOUT; - return -1; + return MP_STREAM_ERROR; } poll_sockets(); } - if (socket->state < 0) { - *_errno = error_lookup_table[-socket->state]; - return -1; - } + // While we waited, something could happen + STREAM_ERROR_CHECK(socket); } u16_t write_len = MIN(available, len); @@ -439,7 +473,7 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui if (err != ERR_OK) { *_errno = error_lookup_table[-err]; - return -1; + return MP_STREAM_ERROR; } return write_len; @@ -447,10 +481,16 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui // Helper function for recv/recvfrom to handle TCP packets STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, int *_errno) { + // Check for any pending errors + STREAM_ERROR_CHECK(socket); + if (socket->incoming.pbuf == NULL) { // Non-blocking socket if (socket->timeout == 0) { + if (socket->state == STATE_PEER_CLOSED) { + return 0; + } *_errno = EAGAIN; return -1; } @@ -463,6 +503,7 @@ STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } poll_sockets(); } + if (socket->state == STATE_PEER_CLOSED) { if (socket->incoming.pbuf == NULL) { // socket closed and no data left in buffer @@ -475,6 +516,8 @@ STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } } + assert(socket->pcb.tcp != NULL); + struct pbuf *p = socket->incoming.pbuf; if (socket->leftover_count == 0) { @@ -515,6 +558,7 @@ STATIC mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, mp_uint_t n_args socket->base.type = (mp_obj_t)&lwip_socket_type; socket->domain = MOD_NETWORK_AF_INET; socket->type = MOD_NETWORK_SOCK_STREAM; + socket->callback = MP_OBJ_NULL; if (n_args >= 1) { socket->domain = mp_obj_get_int(args[0]); if (n_args >= 2) { @@ -569,6 +613,7 @@ STATIC mp_obj_t lwip_socket_close(mp_obj_t self_in) { socket_is_listener = true; } if (tcp_close(socket->pcb.tcp) != ERR_OK) { + DEBUG_printf("lwip_close: had to call tcp_abort()\n"); tcp_abort(socket->pcb.tcp); } break; @@ -689,6 +734,7 @@ STATIC mp_obj_t lwip_socket_accept(mp_obj_t self_in) { socket2->timeout = socket->timeout; socket2->state = STATE_CONNECTED; socket2->leftover_count = 0; + socket2->callback = MP_OBJ_NULL; tcp_arg(socket2->pcb.tcp, (void*)socket2); tcp_err(socket2->pcb.tcp, _lwip_tcp_error); tcp_recv(socket2->pcb.tcp, _lwip_tcp_recv); @@ -918,13 +964,55 @@ STATIC mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_recvfrom_obj, lwip_socket_recvfrom); +STATIC mp_obj_t lwip_socket_sendall(mp_obj_t self_in, mp_obj_t buf_in) { + lwip_socket_obj_t *socket = self_in; + lwip_socket_check_connected(socket); + + int _errno; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + + mp_uint_t ret = 0; + switch (socket->type) { + case MOD_NETWORK_SOCK_STREAM: { + if (socket->timeout == 0) { + // Behavior of sendall() for non-blocking sockets isn't explicitly specified. + // But it's specified that "On error, an exception is raised, there is no + // way to determine how much data, if any, was successfully sent." Then, the + // most useful behavior is: check whether we will be able to send all of input + // data without EAGAIN, and if won't be, raise it without sending any. + if (bufinfo.len > tcp_sndbuf(socket->pcb.tcp)) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(EAGAIN))); + } + } + // TODO: In CPython3.5, socket timeout should apply to the + // entire sendall() operation, not to individual send() chunks. + while (bufinfo.len != 0) { + ret = lwip_tcp_send(socket, bufinfo.buf, bufinfo.len, &_errno); + if (ret == -1) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(_errno))); + } + bufinfo.len -= ret; + bufinfo.buf = (char*)bufinfo.buf + ret; + } + break; + } + case MOD_NETWORK_SOCK_DGRAM: + mp_not_implemented(""); + break; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_sendall_obj, lwip_socket_sendall); + STATIC mp_obj_t lwip_socket_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) { lwip_socket_obj_t *socket = self_in; mp_uint_t timeout; if (timeout_in == mp_const_none) { timeout = -1; } else { - #if MICROPY_PY_BUILTIN_FLOAT + #if MICROPY_PY_BUILTINS_FLOAT timeout = 1000 * mp_obj_get_float(timeout_in); #else timeout = 1000 * mp_obj_get_int(timeout_in); @@ -950,8 +1038,20 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_setblocking_obj, lwip_socket_setblo STATIC mp_obj_t lwip_socket_setsockopt(mp_uint_t n_args, const mp_obj_t *args) { (void)n_args; // always 4 lwip_socket_obj_t *socket = args[0]; + + int opt = mp_obj_get_int(args[2]); + if (opt == 20) { + if (args[3] == mp_const_none) { + socket->callback = MP_OBJ_NULL; + } else { + socket->callback = args[3]; + } + return mp_const_none; + } + + // Integer options mp_int_t val = mp_obj_get_int(args[3]); - switch (mp_obj_get_int(args[2])) { + switch (opt) { case SOF_REUSEADDR: // Options are common for UDP and TCP pcb's. if (val) { @@ -1010,6 +1110,7 @@ STATIC const mp_map_elem_t lwip_socket_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_recv), (mp_obj_t)&lwip_socket_recv_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_sendto), (mp_obj_t)&lwip_socket_sendto_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_recvfrom), (mp_obj_t)&lwip_socket_recvfrom_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sendall), (mp_obj_t)&lwip_socket_sendall_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_settimeout), (mp_obj_t)&lwip_socket_settimeout_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_setblocking), (mp_obj_t)&lwip_socket_setblocking_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_setsockopt), (mp_obj_t)&lwip_socket_setsockopt_obj }, @@ -1143,6 +1244,14 @@ STATIC mp_obj_t lwip_getaddrinfo(mp_obj_t host_in, mp_obj_t port_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_getaddrinfo_obj, lwip_getaddrinfo); +// Debug functions + +STATIC mp_obj_t lwip_print_pcbs() { + tcp_debug_print_pcbs(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(lwip_print_pcbs_obj, lwip_print_pcbs); + #ifdef MICROPY_PY_LWIP STATIC const mp_map_elem_t mp_module_lwip_globals_table[] = { @@ -1150,6 +1259,7 @@ STATIC const mp_map_elem_t mp_module_lwip_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_reset), (mp_obj_t)&mod_lwip_reset_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_callback), (mp_obj_t)&mod_lwip_callback_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_getaddrinfo), (mp_obj_t)&lwip_getaddrinfo_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_print_pcbs), (mp_obj_t)&lwip_print_pcbs_obj }, // objects { MP_OBJ_NEW_QSTR(MP_QSTR_socket), (mp_obj_t)&lwip_socket_type }, #ifdef MICROPY_PY_LWIP_SLIP diff --git a/extmod/moduos_dupterm.c b/extmod/moduos_dupterm.c index 99022df37a..41b8b3c81e 100644 --- a/extmod/moduos_dupterm.c +++ b/extmod/moduos_dupterm.c @@ -44,12 +44,9 @@ void mp_uos_dupterm_tx_strn(const char *str, size_t len) { mp_call_method_n_kw(1, 0, write_m); nlr_pop(); } else { - // Temporarily disable dupterm to avoid infinite recursion - mp_obj_t save_term = MP_STATE_PORT(term_obj); MP_STATE_PORT(term_obj) = NULL; - mp_printf(&mp_plat_print, "dupterm: "); + mp_printf(&mp_plat_print, "dupterm: Exception in write() method, deactivating: "); mp_obj_print_exception(&mp_plat_print, nlr.ret_val); - MP_STATE_PORT(term_obj) = save_term; } } } diff --git a/extmod/modussl.c b/extmod/modussl.c index 5e18faa579..51f4fead81 100644 --- a/extmod/modussl.c +++ b/extmod/modussl.c @@ -26,6 +26,7 @@ #include <stdio.h> #include <string.h> +#include <errno.h> #include "py/nlr.h" #include "py/runtime.h" @@ -46,7 +47,7 @@ typedef struct _mp_obj_ssl_socket_t { STATIC const mp_obj_type_t ussl_socket_type; -STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock) { +STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, bool server_side) { mp_obj_ssl_socket_t *o = m_new_obj(mp_obj_ssl_socket_t); o->base.type = &ussl_socket_type; o->buf = NULL; @@ -54,21 +55,22 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock) { o->sock = sock; uint32_t options = SSL_SERVER_VERIFY_LATER; - if ((o->ssl_ctx = ssl_ctx_new(options, SSL_DEFAULT_CLNT_SESS)) == NULL) - { - fprintf(stderr, "Error: Client context is invalid\n"); - assert(0); + if ((o->ssl_ctx = ssl_ctx_new(options, SSL_DEFAULT_CLNT_SESS)) == NULL) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(EINVAL))); } - o->ssl_sock = ssl_client_new(o->ssl_ctx, (long)sock, NULL, 0); - - int res; - /* check the return status */ - if ((res = ssl_handshake_status(o->ssl_sock)) != SSL_OK) - { - printf("ssl_handshake_status: %d\n", res); - ssl_display_error(res); - assert(0); + if (server_side) { + o->ssl_sock = ssl_server_new(o->ssl_ctx, (long)sock); + } else { + o->ssl_sock = ssl_client_new(o->ssl_ctx, (long)sock, NULL, 0); + + int res; + /* check the return status */ + if ((res = ssl_handshake_status(o->ssl_sock)) != SSL_OK) { + printf("ssl_handshake_status: %d\n", res); + ssl_display_error(res); + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(EIO))); + } } return o; @@ -85,6 +87,11 @@ STATIC mp_uint_t socket_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errc while (o->bytes_left == 0) { mp_int_t r = ssl_read(o->ssl_sock, &o->buf); + if (r == SSL_OK) { + // SSL_OK from ssl_read() means "everything is ok, but there's + // not user data yet. So, we just keep reading. + continue; + } if (r < 0) { if (r == SSL_CLOSE_NOTIFY || r == SSL_ERROR_CONN_LOST) { // EOF @@ -153,14 +160,24 @@ STATIC const mp_obj_type_t ussl_socket_type = { .locals_dict = (mp_obj_t)&ussl_socket_locals_dict, }; -STATIC mp_obj_t mod_ssl_wrap_socket(mp_uint_t n_args, const mp_obj_t *args) { +STATIC mp_obj_t mod_ssl_wrap_socket(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { // TODO: Implement more args - assert(n_args == 1); - mp_obj_t sock = args[0]; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_server_side, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + // TODO: Check that sock implements stream protocol - return socket_new(sock); + mp_obj_t sock = pos_args[0]; + + struct { + mp_arg_val_t server_side; + } args; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t*)&args); + + return socket_new(sock, args.server_side.u_bool); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_ssl_wrap_socket_obj, 1, 6, mod_ssl_wrap_socket); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_ssl_wrap_socket_obj, 1, mod_ssl_wrap_socket); STATIC const mp_map_elem_t mp_module_ssl_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_ussl) }, diff --git a/extmod/modwebrepl.c b/extmod/modwebrepl.c new file mode 100644 index 0000000000..c160abea2f --- /dev/null +++ b/extmod/modwebrepl.c @@ -0,0 +1,330 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Paul Sokolovsky + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include "py/nlr.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "py/stream.h" +#include "py/builtin.h" +#ifdef MICROPY_PY_WEBREPL_DELAY +#include "py/mphal.h" +#endif +#include "extmod/modwebsocket.h" + +#if MICROPY_PY_WEBREPL + +#if 0 // print debugging info +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_printf(...) (void)0 +#endif + +struct webrepl_file { + char sig[2]; + char type; + char flags; + uint64_t offset; + uint32_t size; + uint16_t fname_len; + char fname[64]; +} __attribute__((packed)); + +enum { PUT_FILE = 1, GET_FILE, LIST_DIR }; +enum { STATE_PASSWD, STATE_NORMAL }; + +typedef struct _mp_obj_webrepl_t { + mp_obj_base_t base; + mp_obj_t sock; + byte state; + byte hdr_to_recv; + uint32_t data_to_recv; + struct webrepl_file hdr; + mp_obj_t cur_file; +} mp_obj_webrepl_t; + +// These get passed to functions which aren't force-l32, so can't be const +STATIC char passwd_prompt[] = "Password: "; +STATIC char connected_prompt[] = "\r\nWebREPL connected\r\n>>> "; +STATIC char denied_prompt[] = "\r\nAccess denied\r\n"; + +STATIC char webrepl_passwd[10]; + +static inline void close_meth(mp_obj_t stream) { + mp_obj_t dest[2]; + mp_load_method(stream, MP_QSTR_close, dest); + mp_call_method_n_kw(0, 0, dest); +} + +STATIC void write_webrepl(mp_obj_t websock, const void *buf, size_t len) { + const mp_stream_p_t *sock_stream = mp_get_stream_raise(websock, MP_STREAM_OP_WRITE | MP_STREAM_OP_IOCTL); + int err; + int old_opts = sock_stream->ioctl(websock, MP_STREAM_SET_DATA_OPTS, FRAME_BIN, &err); + sock_stream->write(websock, buf, len, &err); + sock_stream->ioctl(websock, MP_STREAM_SET_DATA_OPTS, old_opts, &err); +} + +#define SSTR(s) s, sizeof(s) - 1 +STATIC void write_webrepl_str(mp_obj_t websock, const char *str, int sz) { + int err; + const mp_stream_p_t *sock_stream = mp_get_stream_raise(websock, MP_STREAM_OP_WRITE | MP_STREAM_OP_IOCTL); + sock_stream->write(websock, str, sz, &err); +} + +STATIC void write_webrepl_resp(mp_obj_t websock, uint16_t code) { + char buf[4] = {'W', 'B', code & 0xff, code >> 8}; + write_webrepl(websock, buf, sizeof(buf)); +} + +STATIC mp_obj_t webrepl_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 2, false); + mp_get_stream_raise(args[0], MP_STREAM_OP_READ | MP_STREAM_OP_WRITE | MP_STREAM_OP_IOCTL); + DEBUG_printf("sizeof(struct webrepl_file) = %lu\n", sizeof(struct webrepl_file)); + mp_obj_webrepl_t *o = m_new_obj(mp_obj_webrepl_t); + o->base.type = type; + o->sock = args[0]; + o->hdr_to_recv = sizeof(struct webrepl_file); + o->data_to_recv = 0; + o->state = STATE_PASSWD; + write_webrepl_str(args[0], SSTR(passwd_prompt)); + return o; +} + +STATIC void handle_op(mp_obj_webrepl_t *self) { + mp_obj_t open_args[2] = { + mp_obj_new_str(self->hdr.fname, strlen(self->hdr.fname), false), + MP_OBJ_NEW_QSTR(MP_QSTR_rb) + }; + + if (self->hdr.type == PUT_FILE) { + open_args[1] = MP_OBJ_NEW_QSTR(MP_QSTR_wb); + } + + self->cur_file = mp_builtin_open(2, open_args, (mp_map_t*)&mp_const_empty_map); + const mp_stream_p_t *file_stream = + mp_get_stream_raise(self->cur_file, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE | MP_STREAM_OP_IOCTL); + + #if 0 + struct mp_stream_seek_t seek = { .offset = self->hdr.offset, .whence = 0 }; + int err; + mp_uint_t res = file_stream->ioctl(self->cur_file, MP_STREAM_SEEK, (uintptr_t)&seek, &err); + assert(res != MP_STREAM_ERROR); + #endif + + write_webrepl_resp(self->sock, 0); + + if (self->hdr.type == PUT_FILE) { + self->data_to_recv = self->hdr.size; + } else if (self->hdr.type == GET_FILE) { + byte readbuf[2 + 256]; + int err; + // TODO: It's not ideal that we block connection while sending file + // and don't process any input. + while (1) { + mp_uint_t out_sz = file_stream->read(self->cur_file, readbuf + 2, sizeof(readbuf) - 2, &err); + assert(out_sz != MP_STREAM_ERROR); + readbuf[0] = out_sz; + readbuf[1] = out_sz >> 8; + DEBUG_printf("webrepl: Sending %d bytes of file\n", out_sz); + write_webrepl(self->sock, readbuf, 2 + out_sz); + if (out_sz == 0) { + break; + } + } + + write_webrepl_resp(self->sock, 0); + self->hdr_to_recv = sizeof(struct webrepl_file); + } +} + +STATIC mp_uint_t _webrepl_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode); + +STATIC mp_uint_t webrepl_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { + mp_uint_t out_sz; + do { + out_sz = _webrepl_read(self_in, buf, size, errcode); + } while (out_sz == -2); + return out_sz; +} + +STATIC mp_uint_t _webrepl_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { + // We know that os.dupterm always calls with size = 1 + assert(size == 1); + mp_obj_webrepl_t *self = self_in; + const mp_stream_p_t *sock_stream = mp_get_stream_raise(self->sock, MP_STREAM_OP_READ); + mp_uint_t out_sz = sock_stream->read(self->sock, buf, size, errcode); + //DEBUG_printf("webrepl: Read %d initial bytes from websocket\n", out_sz); + if (out_sz == 0 || out_sz == MP_STREAM_ERROR) { + return out_sz; + } + + if (self->state == STATE_PASSWD) { + char c = *(char*)buf; + if (c == '\r' || c == '\n') { + self->hdr.fname[self->data_to_recv] = 0; + DEBUG_printf("webrepl: entered password: %s\n", self->hdr.fname); + + if (strcmp(self->hdr.fname, webrepl_passwd) != 0) { + write_webrepl_str(self->sock, SSTR(denied_prompt)); + return 0; + } + + self->state = STATE_NORMAL; + self->data_to_recv = 0; + write_webrepl_str(self->sock, SSTR(connected_prompt)); + } else if (self->data_to_recv < 10) { + self->hdr.fname[self->data_to_recv++] = c; + } + return -2; + } + + // If last read data belonged to text record (== REPL) + int err; + if (sock_stream->ioctl(self->sock, MP_STREAM_GET_DATA_OPTS, 0, &err) == 1) { + return out_sz; + } + + DEBUG_printf("webrepl: received bin data, hdr_to_recv: %d, data_to_recv=%d\n", self->hdr_to_recv, self->data_to_recv); + + if (self->hdr_to_recv != 0) { + char *p = (char*)&self->hdr + sizeof(self->hdr) - self->hdr_to_recv; + *p++ = *(char*)buf; + if (--self->hdr_to_recv != 0) { + mp_uint_t hdr_sz = sock_stream->read(self->sock, p, self->hdr_to_recv, errcode); + if (hdr_sz == MP_STREAM_ERROR) { + return hdr_sz; + } + self->hdr_to_recv -= hdr_sz; + if (self->hdr_to_recv != 0) { + return -2; + } + } + + DEBUG_printf("webrepl: op: %d, file: %s, chunk @%x, sz=%d\n", self->hdr.type, self->hdr.fname, (uint32_t)self->hdr.offset, self->hdr.size); + + handle_op(self); + + return -2; + } + + if (self->data_to_recv != 0) { + static byte filebuf[512]; + filebuf[0] = *(byte*)buf; + mp_uint_t buf_sz = 1; + if (--self->data_to_recv != 0) { + size_t to_read = MIN(sizeof(filebuf) - 1, self->data_to_recv); + mp_uint_t sz = sock_stream->read(self->sock, filebuf + 1, to_read, errcode); + if (sz == MP_STREAM_ERROR) { + return sz; + } + self->data_to_recv -= sz; + buf_sz += sz; + } + + DEBUG_printf("webrepl: Writing %lu bytes to file\n", buf_sz); + int err; + mp_uint_t res = mp_stream_writeall(self->cur_file, filebuf, buf_sz, &err); + if(res == MP_STREAM_ERROR) { + assert(0); + } + + if (self->data_to_recv == 0) { + close_meth(self->cur_file); + self->hdr_to_recv = sizeof(struct webrepl_file); + DEBUG_printf("webrepl: Finished writing file\n"); + write_webrepl_resp(self->sock, 0); + } + + #ifdef MICROPY_PY_WEBREPL_DELAY + // Some platforms may have broken drivers and easily gets + // overloaded with modest traffic WebREPL file transfers + // generate. The basic workaround is a crude rate control + // done in such way. + mp_hal_delay_ms(MICROPY_PY_WEBREPL_DELAY); + #endif + } + + return -2; +} + +STATIC mp_uint_t webrepl_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { + mp_obj_webrepl_t *self = self_in; + if (self->state == STATE_PASSWD) { + // Don't forward output until passwd is entered + return size; + } + const mp_stream_p_t *stream_p = mp_get_stream_raise(self->sock, MP_STREAM_OP_WRITE); + return stream_p->write(self->sock, buf, size, errcode); +} + +STATIC mp_obj_t webrepl_set_password(mp_obj_t passwd_in) { + mp_uint_t len; + const char *passwd = mp_obj_str_get_data(passwd_in, &len); + len = MIN(len, sizeof(webrepl_passwd) - 1); + memcpy(webrepl_passwd, passwd, len); + webrepl_passwd[len] = 0; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(webrepl_set_password_obj, webrepl_set_password); + +STATIC const mp_map_elem_t webrepl_locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_read), (mp_obj_t)&mp_stream_read_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&mp_stream_write_obj }, +}; +STATIC MP_DEFINE_CONST_DICT(webrepl_locals_dict, webrepl_locals_dict_table); + +STATIC const mp_stream_p_t webrepl_stream_p = { + .read = webrepl_read, + .write = webrepl_write, +}; + +STATIC const mp_obj_type_t webrepl_type = { + { &mp_type_type }, + .name = MP_QSTR__webrepl, + .make_new = webrepl_make_new, + .stream_p = &webrepl_stream_p, + .locals_dict = (mp_obj_t)&webrepl_locals_dict, +}; + +STATIC const mp_map_elem_t webrepl_module_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_websocket) }, + { MP_OBJ_NEW_QSTR(MP_QSTR__webrepl), (mp_obj_t)&webrepl_type }, + { MP_OBJ_NEW_QSTR(MP_QSTR_password), (mp_obj_t)&webrepl_set_password_obj }, +}; + +STATIC MP_DEFINE_CONST_DICT(webrepl_module_globals, webrepl_module_globals_table); + +const mp_obj_module_t mp_module_webrepl = { + .base = { &mp_type_module }, + .name = MP_QSTR__webrepl, + .globals = (mp_obj_dict_t*)&webrepl_module_globals, +}; + +#endif // MICROPY_PY_WEBREPL diff --git a/extmod/modwebsocket.c b/extmod/modwebsocket.c index 78bbe6d13e..344933ded3 100644 --- a/extmod/modwebsocket.c +++ b/extmod/modwebsocket.c @@ -37,7 +37,7 @@ #if MICROPY_PY_WEBSOCKET -enum { FRAME_HEADER, FRAME_OPT, PAYLOAD }; +enum { FRAME_HEADER, FRAME_OPT, PAYLOAD, CONTROL }; enum { BLOCKING_WRITE = 0x80 }; @@ -52,10 +52,14 @@ typedef struct _mp_obj_websocket_t { byte buf_pos; byte buf[6]; byte opts; - // Copy of current frame's flags + // Copy of last data frame flags byte ws_flags; + // Copy of current frame flags + byte last_flags; } mp_obj_websocket_t; +STATIC mp_uint_t websocket_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode); + STATIC mp_obj_t websocket_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 2, false); mp_obj_websocket_t *o = m_new_obj(mp_obj_websocket_t); @@ -97,10 +101,9 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int // "Control frames MAY be injected in the middle of a fragmented message." // So, they must be processed before data frames (and not alter // self->ws_flags) - if ((self->buf[0] & FRAME_OPCODE_MASK) >= FRAME_CLOSE) { - // TODO: implement - assert(0); - } + byte frame_type = self->buf[0]; + self->last_flags = frame_type; + frame_type &= FRAME_OPCODE_MASK; if ((self->buf[0] & FRAME_OPCODE_MASK) == FRAME_CONT) { // Preserve previous frame type @@ -119,7 +122,7 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int // Msg size is next 2 bytes to_recv += 2; } else if (sz == 127) { - // Msg size is next 2 bytes + // Msg size is next 8 bytes assert(0); } if (self->buf[1] & 0x80) { @@ -133,7 +136,11 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int if (to_recv != 0) { self->state = FRAME_OPT; } else { - self->state = PAYLOAD; + if (frame_type >= FRAME_CLOSE) { + self->state = CONTROL; + } else { + self->state = PAYLOAD; + } } continue; } @@ -148,14 +155,25 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int memcpy(self->mask, self->buf + self->buf_pos - 4, 4); } self->buf_pos = 0; - self->state = PAYLOAD; + if ((self->last_flags & FRAME_OPCODE_MASK) >= FRAME_CLOSE) { + self->state = CONTROL; + } else { + self->state = PAYLOAD; + } continue; } - case PAYLOAD: { + case PAYLOAD: + case CONTROL: { + mp_uint_t out_sz = 0; + if (self->msg_sz == 0) { + // In case message had zero payload + goto no_payload; + } + size_t sz = MIN(size, self->msg_sz); - mp_uint_t out_sz = stream_p->read(self->sock, buf, sz, errcode); - if (out_sz == MP_STREAM_ERROR) { + out_sz = stream_p->read(self->sock, buf, sz, errcode); + if (out_sz == 0 || out_sz == MP_STREAM_ERROR) { return out_sz; } @@ -166,12 +184,34 @@ STATIC mp_uint_t websocket_read(mp_obj_t self_in, void *buf, mp_uint_t size, int self->msg_sz -= out_sz; if (self->msg_sz == 0) { + byte last_state; +no_payload: + last_state = self->state; self->state = FRAME_HEADER; self->to_recv = 2; self->mask_pos = 0; self->buf_pos = 0; + + // Handle control frame + if (last_state == CONTROL) { + byte frame_type = self->last_flags & FRAME_OPCODE_MASK; + if (frame_type == FRAME_CLOSE) { + static char close_resp[2] = {0x88, 0}; + int err; + websocket_write(self_in, close_resp, sizeof(close_resp), &err); + return 0; + } + + //DEBUG_printf("Finished receiving ctrl message %x, ignoring\n", self->last_flags); + continue; + } } - return out_sz; + + if (out_sz != 0) { + return out_sz; + } + // Empty (data) frame received is not EOF + continue; } } |