micropython: add micropython component

This commit is contained in:
KY-zhang-X
2022-09-29 12:10:37 +08:00
parent 1514f1cb9b
commit dd76146324
2679 changed files with 354110 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
This directory contains libraries, utilities and helper code developed
specifically for this project. The code is intended to be portable and
usable by any port.

View File

@@ -0,0 +1,13 @@
// This file provides a version of __errno() for embedded systems that do not have one.
// This function is needed for expressions of the form: &errno
static int embed_errno;
#if defined(__linux__)
int *__errno_location(void)
#else
int *__errno(void)
#endif
{
return &embed_errno;
}

View File

@@ -0,0 +1,7 @@
#include <py/runtime.h>
NORETURN void abort_(void);
NORETURN void abort_(void) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("abort() called"));
}

View File

@@ -0,0 +1,133 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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 "py/mpconfig.h"
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include "py/obj.h"
#include "py/mphal.h"
#if MICROPY_PY_BUILTINS_FLOAT
#include "py/formatfloat.h"
#endif
#if MICROPY_DEBUG_PRINTERS
int DEBUG_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = mp_vprintf(MICROPY_DEBUG_PRINTER, fmt, ap);
va_end(ap);
return ret;
}
#endif
#if MICROPY_USE_INTERNAL_PRINTF
#undef putchar // Some stdlibs have a #define for putchar
int printf(const char *fmt, ...);
int vprintf(const char *fmt, va_list ap);
int putchar(int c);
int puts(const char *s);
int vsnprintf(char *str, size_t size, const char *fmt, va_list ap);
int snprintf(char *str, size_t size, const char *fmt, ...);
int printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = mp_vprintf(&mp_plat_print, fmt, ap);
va_end(ap);
return ret;
}
int vprintf(const char *fmt, va_list ap) {
return mp_vprintf(&mp_plat_print, fmt, ap);
}
// need this because gcc optimises printf("%c", c) -> putchar(c), and printf("a") -> putchar('a')
int putchar(int c) {
char chr = c;
mp_hal_stdout_tx_strn_cooked(&chr, 1);
return chr;
}
// need this because gcc optimises printf("string\n") -> puts("string")
int puts(const char *s) {
mp_hal_stdout_tx_strn_cooked(s, strlen(s));
char chr = '\n';
mp_hal_stdout_tx_strn_cooked(&chr, 1);
return 1;
}
typedef struct _strn_print_env_t {
char *cur;
size_t remain;
} strn_print_env_t;
STATIC void strn_print_strn(void *data, const char *str, size_t len) {
strn_print_env_t *strn_print_env = data;
if (len > strn_print_env->remain) {
len = strn_print_env->remain;
}
memcpy(strn_print_env->cur, str, len);
strn_print_env->cur += len;
strn_print_env->remain -= len;
}
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 9
// uClibc requires this alias to be defined, or there may be link errors
// when linkings against it statically.
// GCC 9 gives a warning about missing attributes so it's excluded until
// uClibc+GCC9 support is needed.
int __GI_vsnprintf(char *str, size_t size, const char *fmt, va_list ap) __attribute__((weak, alias("vsnprintf")));
#endif
int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) {
strn_print_env_t strn_print_env = {str, size};
mp_print_t print = {&strn_print_env, strn_print_strn};
int len = mp_vprintf(&print, fmt, ap);
// add terminating null byte
if (size > 0) {
if (strn_print_env.remain == 0) {
strn_print_env.cur[-1] = 0;
} else {
strn_print_env.cur[0] = 0;
}
}
return len;
}
int snprintf(char *str, size_t size, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(str, size, fmt, ap);
va_end(ap);
return ret;
}
#endif // MICROPY_USE_INTERNAL_PRINTF

View File

@@ -0,0 +1,261 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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 <stdint.h>
#include <stddef.h>
#define likely(x) __builtin_expect((x), 1)
void *memcpy(void *dst, const void *src, size_t n) {
if (likely(!(((uintptr_t)dst) & 3) && !(((uintptr_t)src) & 3))) {
// pointers aligned
uint32_t *d = dst;
const uint32_t *s = src;
// copy words first
for (size_t i = (n >> 2); i; i--) {
*d++ = *s++;
}
if (n & 2) {
// copy half-word
*(uint16_t*)d = *(const uint16_t*)s;
d = (uint32_t*)((uint16_t*)d + 1);
s = (const uint32_t*)((const uint16_t*)s + 1);
}
if (n & 1) {
// copy byte
*((uint8_t*)d) = *((const uint8_t*)s);
}
} else {
// unaligned access, copy bytes
uint8_t *d = dst;
const uint8_t *s = src;
for (; n; n--) {
*d++ = *s++;
}
}
return dst;
}
void *__memcpy_chk(void *dest, const void *src, size_t len, size_t slen) {
if (len > slen) {
return NULL;
}
return memcpy(dest, src, len);
}
void *memmove(void *dest, const void *src, size_t n) {
if (src < dest && (uint8_t*)dest < (const uint8_t*)src + n) {
// need to copy backwards
uint8_t *d = (uint8_t*)dest + n - 1;
const uint8_t *s = (const uint8_t*)src + n - 1;
for (; n > 0; n--) {
*d-- = *s--;
}
return dest;
} else {
// can use normal memcpy
return memcpy(dest, src, n);
}
}
void *memset(void *s, int c, size_t n) {
if (c == 0 && ((uintptr_t)s & 3) == 0) {
// aligned store of 0
uint32_t *s32 = s;
for (size_t i = n >> 2; i > 0; i--) {
*s32++ = 0;
}
if (n & 2) {
*((uint16_t*)s32) = 0;
s32 = (uint32_t*)((uint16_t*)s32 + 1);
}
if (n & 1) {
*((uint8_t*)s32) = 0;
}
} else {
uint8_t *s2 = s;
for (; n > 0; n--) {
*s2++ = c;
}
}
return s;
}
int memcmp(const void *s1, const void *s2, size_t n) {
const uint8_t *s1_8 = s1;
const uint8_t *s2_8 = s2;
while (n--) {
char c1 = *s1_8++;
char c2 = *s2_8++;
if (c1 < c2) return -1;
else if (c1 > c2) return 1;
}
return 0;
}
void *memchr(const void *s, int c, size_t n) {
if (n != 0) {
const unsigned char *p = s;
do {
if (*p++ == c)
return ((void *)(p - 1));
} while (--n != 0);
}
return 0;
}
size_t strlen(const char *str) {
int len = 0;
for (const char *s = str; *s; s++) {
len += 1;
}
return len;
}
int strcmp(const char *s1, const char *s2) {
while (*s1 && *s2) {
char c1 = *s1++; // XXX UTF8 get char, next char
char c2 = *s2++; // XXX UTF8 get char, next char
if (c1 < c2) return -1;
else if (c1 > c2) return 1;
}
if (*s2) return -1;
else if (*s1) return 1;
else return 0;
}
int strncmp(const char *s1, const char *s2, size_t n) {
while (*s1 && *s2 && n > 0) {
char c1 = *s1++; // XXX UTF8 get char, next char
char c2 = *s2++; // XXX UTF8 get char, next char
n--;
if (c1 < c2) return -1;
else if (c1 > c2) return 1;
}
if (n == 0) return 0;
else if (*s2) return -1;
else if (*s1) return 1;
else return 0;
}
char *strcpy(char *dest, const char *src) {
char *d = dest;
while (*src) {
*d++ = *src++;
}
*d = '\0';
return dest;
}
// Public Domain implementation of strncpy from:
// http://en.wikibooks.org/wiki/C_Programming/Strings#The_strncpy_function
char *strncpy(char *s1, const char *s2, size_t n) {
char *dst = s1;
const char *src = s2;
/* Copy bytes, one at a time. */
while (n > 0) {
n--;
if ((*dst++ = *src++) == '\0') {
/* If we get here, we found a null character at the end
of s2, so use memset to put null bytes at the end of
s1. */
memset(dst, '\0', n);
break;
}
}
return s1;
}
// needed because gcc optimises strcpy + strcat to this
char *stpcpy(char *dest, const char *src) {
while (*src) {
*dest++ = *src++;
}
*dest = '\0';
return dest;
}
char *strcat(char *dest, const char *src) {
char *d = dest;
while (*d) {
d++;
}
while (*src) {
*d++ = *src++;
}
*d = '\0';
return dest;
}
// Public Domain implementation of strchr from:
// http://en.wikibooks.org/wiki/C_Programming/Strings#The_strchr_function
char *strchr(const char *s, int c)
{
/* Scan s for the character. When this loop is finished,
s will either point to the end of the string or the
character we were looking for. */
while (*s != '\0' && *s != (char)c)
s++;
return ((*s == c) ? (char *) s : 0);
}
// Public Domain implementation of strstr from:
// http://en.wikibooks.org/wiki/C_Programming/Strings#The_strstr_function
char *strstr(const char *haystack, const char *needle)
{
size_t needlelen;
/* Check for the null needle case. */
if (*needle == '\0')
return (char *) haystack;
needlelen = strlen(needle);
for (; (haystack = strchr(haystack, *needle)) != 0; haystack++)
if (strncmp(haystack, needle, needlelen) == 0)
return (char *) haystack;
return 0;
}
size_t strspn(const char *s, const char *accept) {
const char *ss = s;
while (*s && strchr(accept, *s) != NULL) {
++s;
}
return s - ss;
}
size_t strcspn(const char *s, const char *reject) {
const char *ss = s;
while (*s && strchr(reject, *s) == NULL) {
++s;
}
return s - ss;
}

View File

@@ -0,0 +1,28 @@
MEMZIP - a simple readonly file system
memzip takes a zip file which is comprised of uncompressed files and
and presents it as a filesystem, allowing Python files to be imported.
The script make-memzip.py takes a directory name and will create a zip file
containing uncompressed files found in the directory. It will then generate
a C file which contains the data from the zip file.
A typical addition to a makefile would look like:
```
SRC_C += \
shared/memzip/import.c \
shared/memzip/lexermemzip.c \
shared/memzip/memzip.c \
OBJ += $(BUILD)/memzip-files.o
MAKE_MEMZIP = ../shared/memzip/make-memzip.py
$(BUILD)/memzip-files.o: $(BUILD)/memzip-files.c
$(call compile_c)
$(BUILD)/memzip-files.c: $(shell find ${MEMZIP_DIR} -type f)
@$(ECHO) "Creating $@"
$(Q)$(PYTHON) $(MAKE_MEMZIP) --zip-file $(BUILD)/memzip-files.zip --c-file $@ $(MEMZIP_DIR)
```

View File

@@ -0,0 +1,17 @@
#include <stdio.h>
#include "py/lexer.h"
#include "memzip.h"
mp_import_stat_t mp_import_stat(const char *path) {
MEMZIP_FILE_INFO info;
if (memzip_stat(path, &info) != MZ_OK) {
return MP_IMPORT_STAT_NO_EXIST;
}
if (info.is_dir) {
return MP_IMPORT_STAT_DIR;
}
return MP_IMPORT_STAT_FILE;
}

View File

@@ -0,0 +1,19 @@
#include <stdlib.h>
#include "py/lexer.h"
#include "py/runtime.h"
#include "py/mperrno.h"
#include "memzip.h"
mp_lexer_t *mp_lexer_new_from_file(const char *filename)
{
void *data;
size_t len;
if (memzip_locate(filename, &data, &len) != MZ_OK) {
mp_raise_OSError(MP_ENOENT);
}
return mp_lexer_new_from_str_len(qstr_from_str(filename), (const char *)data, (mp_uint_t)len, 0);
}

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python
#
# Takes a directory of files and zips them up (as uncompressed files).
# This then gets converted into a C data structure which can be read
# like a filesystem at runtime.
#
# This is somewhat like frozen modules in python, but allows arbitrary files
# to be used.
from __future__ import print_function
import argparse
import os
import subprocess
import sys
import types
def create_zip(zip_filename, zip_dir):
abs_zip_filename = os.path.abspath(zip_filename)
save_cwd = os.getcwd()
os.chdir(zip_dir)
if os.path.exists(abs_zip_filename):
os.remove(abs_zip_filename)
subprocess.check_call(['zip', '-0', '-r', '-D', abs_zip_filename, '.'])
os.chdir(save_cwd)
def create_c_from_file(c_filename, zip_filename):
with open(zip_filename, 'rb') as zip_file:
with open(c_filename, 'wb') as c_file:
print('#include <stdint.h>', file=c_file)
print('', file=c_file)
print('const uint8_t memzip_data[] = {', file=c_file)
while True:
buf = zip_file.read(16)
if not buf:
break
print(' ', end='', file=c_file)
for byte in buf:
if type(byte) is types.StringType:
print(' 0x{:02x},'.format(ord(byte)), end='', file=c_file)
else:
print(' 0x{:02x},'.format(byte), end='', file=c_file)
print('', file=c_file)
print('};', file=c_file)
def main():
parser = argparse.ArgumentParser(
prog='make-memzip.py',
usage='%(prog)s [options] [command]',
description='Generates a C source memzip file.'
)
parser.add_argument(
'-z', '--zip-file',
dest='zip_filename',
help='Specifies the name of the created zip file.',
default='memzip_files.zip'
)
parser.add_argument(
'-c', '--c-file',
dest='c_filename',
help='Specifies the name of the created C source file.',
default='memzip_files.c'
)
parser.add_argument(
dest='source_dir',
default='memzip_files'
)
args = parser.parse_args(sys.argv[1:])
print('args.zip_filename =', args.zip_filename)
print('args.c_filename =', args.c_filename)
print('args.source_dir =', args.source_dir)
create_zip(args.zip_filename, args.source_dir)
create_c_from_file(args.c_filename, args.zip_filename)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,106 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "py/mpconfig.h"
#include "py/misc.h"
#include "memzip.h"
extern uint8_t memzip_data[];
const MEMZIP_FILE_HDR *memzip_find_file_header(const char *filename) {
const MEMZIP_FILE_HDR *file_hdr = (const MEMZIP_FILE_HDR *)memzip_data;
uint8_t *mem_data;
/* Zip file filenames don't have a leading /, so we strip it off */
if (*filename == '/') {
filename++;
}
while (file_hdr->signature == MEMZIP_FILE_HEADER_SIGNATURE) {
const char *file_hdr_filename = (const char *)&file_hdr[1];
mem_data = (uint8_t *)file_hdr_filename;
mem_data += file_hdr->filename_len;
mem_data += file_hdr->extra_len;
if (!strncmp(file_hdr_filename, filename, file_hdr->filename_len)) {
/* We found a match */
return file_hdr;
}
mem_data += file_hdr->uncompressed_size;
file_hdr = (const MEMZIP_FILE_HDR *)mem_data;
}
return NULL;
}
bool memzip_is_dir(const char *filename) {
const MEMZIP_FILE_HDR *file_hdr = (const MEMZIP_FILE_HDR *)memzip_data;
uint8_t *mem_data;
if (strcmp(filename, "/") == 0) {
// The root directory is a directory.
return true;
}
// Zip filenames don't have a leading /, so we strip it off
if (*filename == '/') {
filename++;
}
size_t filename_len = strlen(filename);
while (file_hdr->signature == MEMZIP_FILE_HEADER_SIGNATURE) {
const char *file_hdr_filename = (const char *)&file_hdr[1];
if (filename_len < file_hdr->filename_len &&
strncmp(file_hdr_filename, filename, filename_len) == 0 &&
file_hdr_filename[filename_len] == '/') {
return true;
}
mem_data = (uint8_t *)file_hdr_filename;
mem_data += file_hdr->filename_len;
mem_data += file_hdr->extra_len;
mem_data += file_hdr->uncompressed_size;
file_hdr = (const MEMZIP_FILE_HDR *)mem_data;
}
return NULL;
}
MEMZIP_RESULT memzip_locate(const char *filename, void **data, size_t *len)
{
const MEMZIP_FILE_HDR *file_hdr = memzip_find_file_header(filename);
if (file_hdr == NULL) {
return MZ_NO_FILE;
}
if (file_hdr->compression_method != 0) {
return MZ_FILE_COMPRESSED;
}
uint8_t *mem_data;
mem_data = (uint8_t *)&file_hdr[1];
mem_data += file_hdr->filename_len;
mem_data += file_hdr->extra_len;
*data = mem_data;
*len = file_hdr->uncompressed_size;
return MZ_OK;
}
MEMZIP_RESULT memzip_stat(const char *path, MEMZIP_FILE_INFO *info) {
const MEMZIP_FILE_HDR *file_hdr = memzip_find_file_header(path);
if (file_hdr == NULL) {
if (memzip_is_dir(path)) {
info->file_size = 0;
info->last_mod_date = 0;
info->last_mod_time = 0;
info->is_dir = 1;
return MZ_OK;
}
return MZ_NO_FILE;
}
info->file_size = file_hdr->uncompressed_size;
info->last_mod_date = file_hdr->last_mod_date;
info->last_mod_time = file_hdr->last_mod_time;
info->is_dir = 0;
return MZ_OK;
}

View File

@@ -0,0 +1,83 @@
#pragma pack(push, 1)
#define MEMZIP_FILE_HEADER_SIGNATURE 0x04034b50
typedef struct
{
uint32_t signature;
uint16_t version;
uint16_t flags;
uint16_t compression_method;
uint16_t last_mod_time;
uint16_t last_mod_date;
uint32_t crc32;
uint32_t compressed_size;
uint32_t uncompressed_size;
uint16_t filename_len;
uint16_t extra_len;
/* char filename[filename_len] */
/* uint8_t extra[extra_len] */
} MEMZIP_FILE_HDR;
#define MEMZIP_CENTRAL_DIRECTORY_SIGNATURE 0x02014b50
typedef struct
{
uint32_t signature;
uint16_t version_made_by;
uint16_t version_read_with;
uint16_t flags;
uint16_t compression_method;
uint16_t last_mod_time;
uint16_t last_mod_date;
uint32_t crc32;
uint32_t compressed_size;
uint32_t uncompressed_size;
uint16_t filename_len;
uint16_t extra_len;
uint16_t disk_num;
uint16_t internal_file_attributes;
uint32_t external_file_attributes;
uint32_t file_header_offset;
/* char filename[filename_len] */
/* uint8_t extra[extra_len] */
} MEMZIP_CENTRAL_DIRECTORY_HDR;
#define MEMZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE 0x06054b50
typedef struct
{
uint32_t signature;
uint16_t disk_num;
uint16_t central_directory_disk;
uint16_t num_central_directories_this_disk;
uint16_t total_central_directories;
uint32_t central_directory_size;
uint32_t central_directory_offset;
uint16_t comment_len;
/* char comment[comment_len] */
} MEMZIP_END_OF_CENTRAL_DIRECTORY;
#pragma pack(pop)
typedef enum {
MZ_OK = 0, /* (0) Succeeded */
MZ_NO_FILE, /* (1) Could not find the file. */
MZ_FILE_COMPRESSED, /* (2) File is compressed (expecting uncompressed) */
} MEMZIP_RESULT;
typedef struct {
uint32_t file_size;
uint16_t last_mod_date;
uint16_t last_mod_time;
uint8_t is_dir;
} MEMZIP_FILE_INFO;
MEMZIP_RESULT memzip_locate(const char *filename, void **data, size_t *len);
MEMZIP_RESULT memzip_stat(const char *path, MEMZIP_FILE_INFO *info);

View File

@@ -0,0 +1,304 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018-2019 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.
*/
// For DHCP specs see:
// https://www.ietf.org/rfc/rfc2131.txt
// https://tools.ietf.org/html/rfc2132 -- DHCP Options and BOOTP Vendor Extensions
#include <stdio.h>
#include <string.h>
#include "py/mperrno.h"
#include "py/mphal.h"
#if MICROPY_PY_LWIP
#include "shared/netutils/dhcpserver.h"
#include "lwip/udp.h"
#define DHCPDISCOVER (1)
#define DHCPOFFER (2)
#define DHCPREQUEST (3)
#define DHCPDECLINE (4)
#define DHCPACK (5)
#define DHCPNACK (6)
#define DHCPRELEASE (7)
#define DHCPINFORM (8)
#define DHCP_OPT_PAD (0)
#define DHCP_OPT_SUBNET_MASK (1)
#define DHCP_OPT_ROUTER (3)
#define DHCP_OPT_DNS (6)
#define DHCP_OPT_HOST_NAME (12)
#define DHCP_OPT_REQUESTED_IP (50)
#define DHCP_OPT_IP_LEASE_TIME (51)
#define DHCP_OPT_MSG_TYPE (53)
#define DHCP_OPT_SERVER_ID (54)
#define DHCP_OPT_PARAM_REQUEST_LIST (55)
#define DHCP_OPT_MAX_MSG_SIZE (57)
#define DHCP_OPT_VENDOR_CLASS_ID (60)
#define DHCP_OPT_CLIENT_ID (61)
#define DHCP_OPT_END (255)
#define PORT_DHCP_SERVER (67)
#define PORT_DHCP_CLIENT (68)
#define DEFAULT_DNS MAKE_IP4(8, 8, 8, 8)
#define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds
#define MAC_LEN (6)
#define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
typedef struct {
uint8_t op; // message opcode
uint8_t htype; // hardware address type
uint8_t hlen; // hardware address length
uint8_t hops;
uint32_t xid; // transaction id, chosen by client
uint16_t secs; // client seconds elapsed
uint16_t flags;
uint8_t ciaddr[4]; // client IP address
uint8_t yiaddr[4]; // your IP address
uint8_t siaddr[4]; // next server IP address
uint8_t giaddr[4]; // relay agent IP address
uint8_t chaddr[16]; // client hardware address
uint8_t sname[64]; // server host name
uint8_t file[128]; // boot file name
uint8_t options[312]; // optional parameters, variable, starts with magic
} dhcp_msg_t;
static int dhcp_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) {
// family is AF_INET
// type is SOCK_DGRAM
*udp = udp_new();
if (*udp == NULL) {
return -MP_ENOMEM;
}
// Register callback
udp_recv(*udp, cb_udp_recv, (void *)cb_data);
return 0; // success
}
static void dhcp_socket_free(struct udp_pcb **udp) {
if (*udp != NULL) {
udp_remove(*udp);
*udp = NULL;
}
}
static int dhcp_socket_bind(struct udp_pcb **udp, uint32_t ip, uint16_t port) {
ip_addr_t addr;
IP4_ADDR(&addr, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff);
// TODO convert lwIP errors to errno
return udp_bind(*udp, &addr, port);
}
static int dhcp_socket_sendto(struct udp_pcb **udp, const void *buf, size_t len, uint32_t ip, uint16_t port) {
if (len > 0xffff) {
len = 0xffff;
}
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
if (p == NULL) {
return -MP_ENOMEM;
}
memcpy(p->payload, buf, len);
ip_addr_t dest;
IP4_ADDR(&dest, ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, ip & 0xff);
err_t err = udp_sendto(*udp, p, &dest, port);
pbuf_free(p);
if (err != ERR_OK) {
return err;
}
return len;
}
static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) {
for (int i = 0; i < 308 && opt[i] != DHCP_OPT_END;) {
if (opt[i] == cmd) {
return &opt[i];
}
i += 2 + opt[i + 1];
}
return NULL;
}
static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, void *data) {
uint8_t *o = *opt;
*o++ = cmd;
*o++ = n;
memcpy(o, data, n);
*opt = o + n;
}
static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) {
uint8_t *o = *opt;
*o++ = cmd;
*o++ = 1;
*o++ = val;
*opt = o;
}
static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) {
uint8_t *o = *opt;
*o++ = cmd;
*o++ = 4;
*o++ = val >> 24;
*o++ = val >> 16;
*o++ = val >> 8;
*o++ = val;
*opt = o;
}
static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) {
dhcp_server_t *d = arg;
(void)upcb;
(void)src_addr;
(void)src_port;
// This is around 548 bytes
dhcp_msg_t dhcp_msg;
#define DHCP_MIN_SIZE (240 + 3)
if (p->tot_len < DHCP_MIN_SIZE) {
goto ignore_request;
}
size_t len = pbuf_copy_partial(p, &dhcp_msg, sizeof(dhcp_msg), 0);
if (len < DHCP_MIN_SIZE) {
goto ignore_request;
}
dhcp_msg.op = DHCPOFFER;
memcpy(&dhcp_msg.yiaddr, &d->ip.addr, 4);
uint8_t *opt = (uint8_t *)&dhcp_msg.options;
opt += 4; // assume magic cookie: 99, 130, 83, 99
switch (opt[2]) {
case DHCPDISCOVER: {
int yi = DHCPS_MAX_IP;
for (int i = 0; i < DHCPS_MAX_IP; ++i) {
if (memcmp(d->lease[i].mac, dhcp_msg.chaddr, MAC_LEN) == 0) {
// MAC match, use this IP address
yi = i;
break;
}
if (yi == DHCPS_MAX_IP) {
// Look for a free IP address
if (memcmp(d->lease[i].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) {
// IP available
yi = i;
}
uint32_t expiry = d->lease[i].expiry << 16 | 0xffff;
if ((int32_t)(expiry - mp_hal_ticks_ms()) < 0) {
// IP expired, reuse it
memset(d->lease[i].mac, 0, MAC_LEN);
yi = i;
}
}
}
if (yi == DHCPS_MAX_IP) {
// No more IP addresses left
goto ignore_request;
}
dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi;
opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPOFFER);
break;
}
case DHCPREQUEST: {
uint8_t *o = opt_find(opt, DHCP_OPT_REQUESTED_IP);
if (o == NULL) {
// Should be NACK
goto ignore_request;
}
if (memcmp(o + 2, &d->ip.addr, 3) != 0) {
// Should be NACK
goto ignore_request;
}
uint8_t yi = o[5] - DHCPS_BASE_IP;
if (yi >= DHCPS_MAX_IP) {
// Should be NACK
goto ignore_request;
}
if (memcmp(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN) == 0) {
// MAC match, ok to use this IP address
} else if (memcmp(d->lease[yi].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) {
// IP unused, ok to use this IP address
memcpy(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN);
} else {
// IP already in use
// Should be NACK
goto ignore_request;
}
d->lease[yi].expiry = (mp_hal_ticks_ms() + DEFAULT_LEASE_TIME_S * 1000) >> 16;
dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi;
opt_write_u8(&opt, DHCP_OPT_MSG_TYPE, DHCPACK);
printf("DHCPS: client connected: MAC=%02x:%02x:%02x:%02x:%02x:%02x IP=%u.%u.%u.%u\n",
dhcp_msg.chaddr[0], dhcp_msg.chaddr[1], dhcp_msg.chaddr[2], dhcp_msg.chaddr[3], dhcp_msg.chaddr[4], dhcp_msg.chaddr[5],
dhcp_msg.yiaddr[0], dhcp_msg.yiaddr[1], dhcp_msg.yiaddr[2], dhcp_msg.yiaddr[3]);
break;
}
default:
goto ignore_request;
}
opt_write_n(&opt, DHCP_OPT_SERVER_ID, 4, &d->ip.addr);
opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, &d->nm.addr);
opt_write_n(&opt, DHCP_OPT_ROUTER, 4, &d->ip.addr); // aka gateway; can have mulitple addresses
opt_write_u32(&opt, DHCP_OPT_DNS, DEFAULT_DNS); // can have mulitple addresses
opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S);
*opt++ = DHCP_OPT_END;
dhcp_socket_sendto(&d->udp, &dhcp_msg, opt - (uint8_t *)&dhcp_msg, 0xffffffff, PORT_DHCP_CLIENT);
ignore_request:
pbuf_free(p);
}
void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) {
ip_addr_copy(d->ip, *ip);
ip_addr_copy(d->nm, *nm);
memset(d->lease, 0, sizeof(d->lease));
if (dhcp_socket_new_dgram(&d->udp, d, dhcp_server_process) != 0) {
return;
}
dhcp_socket_bind(&d->udp, 0, PORT_DHCP_SERVER);
}
void dhcp_server_deinit(dhcp_server_t *d) {
dhcp_socket_free(&d->udp);
}
#endif // MICROPY_PY_LWIP

View File

@@ -0,0 +1,49 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018-2019 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_LIB_NETUTILS_DHCPSERVER_H
#define MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H
#include "lwip/ip_addr.h"
#define DHCPS_BASE_IP (16)
#define DHCPS_MAX_IP (8)
typedef struct _dhcp_server_lease_t {
uint8_t mac[6];
uint16_t expiry;
} dhcp_server_lease_t;
typedef struct _dhcp_server_t {
ip_addr_t ip;
ip_addr_t nm;
dhcp_server_lease_t lease[DHCPS_MAX_IP];
struct udp_pcb *udp;
} dhcp_server_t;
void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm);
void dhcp_server_deinit(dhcp_server_t *d);
#endif // MICROPY_INCLUDED_LIB_NETUTILS_DHCPSERVER_H

View File

@@ -0,0 +1,94 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
* Copyright (c) 2015 Daniel Campora
*
* 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 <stdint.h>
#include <stdio.h>
#include <string.h>
#include "py/runtime.h"
#include "shared/netutils/netutils.h"
// Takes an array with a raw IPv4 address and returns something like '192.168.0.1'.
mp_obj_t netutils_format_ipv4_addr(uint8_t *ip, netutils_endian_t endian) {
char ip_str[16];
mp_uint_t ip_len;
if (endian == NETUTILS_LITTLE) {
ip_len = snprintf(ip_str, 16, "%u.%u.%u.%u", ip[3], ip[2], ip[1], ip[0]);
} else {
ip_len = snprintf(ip_str, 16, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
}
return mp_obj_new_str(ip_str, ip_len);
}
// Takes an array with a raw IP address, and a port, and returns a net-address
// tuple such as ('192.168.0.1', 8080).
mp_obj_t netutils_format_inet_addr(uint8_t *ip, mp_uint_t port, netutils_endian_t endian) {
mp_obj_t tuple[2] = {
tuple[0] = netutils_format_ipv4_addr(ip, endian),
tuple[1] = mp_obj_new_int(port),
};
return mp_obj_new_tuple(2, tuple);
}
void netutils_parse_ipv4_addr(mp_obj_t addr_in, uint8_t *out_ip, netutils_endian_t endian) {
size_t addr_len;
const char *addr_str = mp_obj_str_get_data(addr_in, &addr_len);
if (addr_len == 0) {
// special case of no address given
memset(out_ip, 0, NETUTILS_IPV4ADDR_BUFSIZE);
return;
}
const char *s = addr_str;
const char *s_top = addr_str + addr_len;
for (mp_uint_t i = 3; ; i--) {
mp_uint_t val = 0;
for (; s < s_top && *s != '.'; s++) {
val = val * 10 + *s - '0';
}
if (endian == NETUTILS_LITTLE) {
out_ip[i] = val;
} else {
out_ip[NETUTILS_IPV4ADDR_BUFSIZE - 1 - i] = val;
}
if (i == 0 && s == s_top) {
return;
} else if (i > 0 && s < s_top && *s == '.') {
s++;
} else {
mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments"));
}
}
}
// Takes an address of the form ('192.168.0.1', 8080), returns the port and
// puts IP in out_ip (which must take at least IPADDR_BUF_SIZE bytes).
mp_uint_t netutils_parse_inet_addr(mp_obj_t addr_in, uint8_t *out_ip, netutils_endian_t endian) {
mp_obj_t *addr_items;
mp_obj_get_array_fixed_n(addr_in, 2, &addr_items);
netutils_parse_ipv4_addr(addr_items[0], out_ip, endian);
return mp_obj_get_int(addr_items[1]);
}

View File

@@ -0,0 +1,56 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
* Copyright (c) 2015 Daniel Campora
*
* 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_LIB_NETUTILS_NETUTILS_H
#define MICROPY_INCLUDED_LIB_NETUTILS_NETUTILS_H
#define NETUTILS_IPV4ADDR_BUFSIZE 4
#define NETUTILS_TRACE_IS_TX (0x0001)
#define NETUTILS_TRACE_PAYLOAD (0x0002)
#define NETUTILS_TRACE_NEWLINE (0x0004)
typedef enum _netutils_endian_t {
NETUTILS_LITTLE,
NETUTILS_BIG,
} netutils_endian_t;
// Takes an array with a raw IPv4 address and returns something like '192.168.0.1'.
mp_obj_t netutils_format_ipv4_addr(uint8_t *ip, netutils_endian_t endian);
// Takes an array with a raw IP address, and a port, and returns a net-address
// tuple such as ('192.168.0.1', 8080).
mp_obj_t netutils_format_inet_addr(uint8_t *ip, mp_uint_t port, netutils_endian_t endian);
void netutils_parse_ipv4_addr(mp_obj_t addr_in, uint8_t *out_ip, netutils_endian_t endian);
// Takes an address of the form ('192.168.0.1', 8080), returns the port and
// puts IP in out_ip (which must take at least IPADDR_BUF_SIZE bytes).
mp_uint_t netutils_parse_inet_addr(mp_obj_t addr_in, uint8_t *out_ip, netutils_endian_t endian);
void netutils_ethernet_trace(const mp_print_t *print, size_t len, const uint8_t *buf, unsigned int flags);
#endif // MICROPY_INCLUDED_LIB_NETUTILS_NETUTILS_H

View File

@@ -0,0 +1,170 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 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 "py/mphal.h"
#include "shared/netutils/netutils.h"
static uint32_t get_be16(const uint8_t *buf) {
return buf[0] << 8 | buf[1];
}
static uint32_t get_be32(const uint8_t *buf) {
return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
}
static void dump_hex_bytes(const mp_print_t *print, size_t len, const uint8_t *buf) {
for (size_t i = 0; i < len; ++i) {
mp_printf(print, " %02x", buf[i]);
}
}
static const char *ethertype_str(uint16_t type) {
// A value between 0x0000 - 0x05dc (inclusive) indicates a length, not type
switch (type) {
case 0x0800:
return "IPv4";
case 0x0806:
return "ARP";
case 0x86dd:
return "IPv6";
default:
return NULL;
}
}
void netutils_ethernet_trace(const mp_print_t *print, size_t len, const uint8_t *buf, unsigned int flags) {
mp_printf(print, "[% 8d] ETH%cX len=%u", mp_hal_ticks_ms(), flags & NETUTILS_TRACE_IS_TX ? 'T' : 'R', len);
mp_printf(print, " dst=%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
mp_printf(print, " src=%02x:%02x:%02x:%02x:%02x:%02x", buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);
const char *ethertype = ethertype_str(buf[12] << 8 | buf[13]);
if (ethertype) {
mp_printf(print, " type=%s", ethertype);
} else {
mp_printf(print, " type=0x%04x", buf[12] << 8 | buf[13]);
}
if (len > 14) {
len -= 14;
buf += 14;
if (buf[-2] == 0x08 && buf[-1] == 0x00 && buf[0] == 0x45) {
// IPv4 packet
len = get_be16(buf + 2);
mp_printf(print, " srcip=%u.%u.%u.%u dstip=%u.%u.%u.%u",
buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19]);
uint8_t prot = buf[9];
buf += 20;
len -= 20;
if (prot == 6) {
// TCP packet
uint16_t srcport = get_be16(buf);
uint16_t dstport = get_be16(buf + 2);
uint32_t seqnum = get_be32(buf + 4);
uint32_t acknum = get_be32(buf + 8);
uint16_t dataoff_flags = get_be16(buf + 12);
uint16_t winsz = get_be16(buf + 14);
mp_printf(print, " TCP srcport=%u dstport=%u seqnum=%u acknum=%u dataoff=%u flags=%x winsz=%u",
srcport, dstport, (unsigned)seqnum, (unsigned)acknum, dataoff_flags >> 12, dataoff_flags & 0x1ff, winsz);
buf += 20;
len -= 20;
if (dataoff_flags >> 12 > 5) {
mp_printf(print, " opts=");
size_t opts_len = ((dataoff_flags >> 12) - 5) * 4;
dump_hex_bytes(print, opts_len, buf);
buf += opts_len;
len -= opts_len;
}
} else if (prot == 17) {
// UDP packet
uint16_t srcport = get_be16(buf);
uint16_t dstport = get_be16(buf + 2);
mp_printf(print, " UDP srcport=%u dstport=%u", srcport, dstport);
len = get_be16(buf + 4);
buf += 8;
if ((srcport == 67 && dstport == 68) || (srcport == 68 && dstport == 67)) {
// DHCP
if (srcport == 67) {
mp_printf(print, " DHCPS");
} else {
mp_printf(print, " DHCPC");
}
dump_hex_bytes(print, 12 + 16 + 16 + 64, buf);
size_t n = 12 + 16 + 16 + 64 + 128;
len -= n;
buf += n;
mp_printf(print, " opts:");
switch (buf[6]) {
case 1:
mp_printf(print, " DISCOVER");
break;
case 2:
mp_printf(print, " OFFER");
break;
case 3:
mp_printf(print, " REQUEST");
break;
case 4:
mp_printf(print, " DECLINE");
break;
case 5:
mp_printf(print, " ACK");
break;
case 6:
mp_printf(print, " NACK");
break;
case 7:
mp_printf(print, " RELEASE");
break;
case 8:
mp_printf(print, " INFORM");
break;
}
}
} else {
// Non-UDP packet
mp_printf(print, " prot=%u", prot);
}
} else if (buf[-2] == 0x86 && buf[-1] == 0xdd && (buf[0] >> 4) == 6) {
// IPv6 packet
uint32_t h = get_be32(buf);
uint16_t l = get_be16(buf + 4);
mp_printf(print, " tclass=%u flow=%u len=%u nexthdr=%u hoplimit=%u", (unsigned)((h >> 20) & 0xff), (unsigned)(h & 0xfffff), l, buf[6], buf[7]);
mp_printf(print, " srcip=");
dump_hex_bytes(print, 16, buf + 8);
mp_printf(print, " dstip=");
dump_hex_bytes(print, 16, buf + 24);
buf += 40;
len -= 40;
}
if (flags & NETUTILS_TRACE_PAYLOAD) {
mp_printf(print, " data=");
dump_hex_bytes(print, len, buf);
}
}
if (flags & NETUTILS_TRACE_NEWLINE) {
mp_printf(print, "\n");
}
}

View File

@@ -0,0 +1,579 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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/mpstate.h"
#include "py/repl.h"
#include "py/mphal.h"
#include "shared/readline/readline.h"
#if 0 // print debugging info
#define DEBUG_PRINT (1)
#define DEBUG_printf printf
#else // don't print debugging info
#define DEBUG_printf(...) (void)0
#endif
#define READLINE_HIST_SIZE (MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist)))
// flags for readline_t.auto_indent_state
#define AUTO_INDENT_ENABLED (0x01)
#define AUTO_INDENT_JUST_ADDED (0x02)
enum { ESEQ_NONE, ESEQ_ESC, ESEQ_ESC_BRACKET, ESEQ_ESC_BRACKET_DIGIT, ESEQ_ESC_O };
void readline_init0(void) {
memset(MP_STATE_PORT(readline_hist), 0, READLINE_HIST_SIZE * sizeof(const char*));
}
STATIC char *str_dup_maybe(const char *str) {
uint32_t len = strlen(str);
char *s2 = m_new_maybe(char, len + 1);
if (s2 == NULL) {
return NULL;
}
memcpy(s2, str, len + 1);
return s2;
}
// By default assume terminal which implements VT100 commands...
#ifndef MICROPY_HAL_HAS_VT100
#define MICROPY_HAL_HAS_VT100 (1)
#endif
// ...and provide the implementation using them
#if MICROPY_HAL_HAS_VT100
STATIC void mp_hal_move_cursor_back(uint pos) {
if (pos <= 4) {
// fast path for most common case of 1 step back
mp_hal_stdout_tx_strn("\b\b\b\b", pos);
} else {
char vt100_command[6];
// snprintf needs space for the terminating null character
int n = snprintf(&vt100_command[0], sizeof(vt100_command), "\x1b[%u", pos);
if (n > 0) {
assert((unsigned)n < sizeof(vt100_command));
vt100_command[n] = 'D'; // replace null char
mp_hal_stdout_tx_strn(vt100_command, n + 1);
}
}
}
STATIC void mp_hal_erase_line_from_cursor(uint n_chars_to_erase) {
(void)n_chars_to_erase;
mp_hal_stdout_tx_strn("\x1b[K", 3);
}
#endif
typedef struct _readline_t {
vstr_t *line;
size_t orig_line_len;
int escape_seq;
int hist_cur;
size_t cursor_pos;
char escape_seq_buf[1];
#if MICROPY_REPL_AUTO_INDENT
uint8_t auto_indent_state;
#endif
const char *prompt;
} readline_t;
STATIC readline_t rl;
#if MICROPY_REPL_EMACS_WORDS_MOVE
STATIC size_t cursor_count_word(int forward) {
const char *line_buf = vstr_str(rl.line);
size_t pos = rl.cursor_pos;
bool in_word = false;
for (;;) {
// if moving backwards and we've reached 0... break
if (!forward && pos == 0) {
break;
}
// or if moving forwards and we've reached to the end of line... break
else if (forward && pos == vstr_len(rl.line)) {
break;
}
if (unichar_isalnum(line_buf[pos + (forward - 1)])) {
in_word = true;
} else if (in_word) {
break;
}
pos += forward ? forward : -1;
}
return forward ? pos - rl.cursor_pos : rl.cursor_pos - pos;
}
#endif
int readline_process_char(int c) {
size_t last_line_len = rl.line->len;
int redraw_step_back = 0;
bool redraw_from_cursor = false;
int redraw_step_forward = 0;
if (rl.escape_seq == ESEQ_NONE) {
if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_E && vstr_len(rl.line) == rl.orig_line_len) {
// control character with empty line
return c;
} else if (c == CHAR_CTRL_A) {
// CTRL-A with non-empty line is go-to-start-of-line
goto home_key;
#if MICROPY_REPL_EMACS_KEYS
} else if (c == CHAR_CTRL_B) {
// CTRL-B with non-empty line is go-back-one-char
goto left_arrow_key;
#endif
} else if (c == CHAR_CTRL_C) {
// CTRL-C with non-empty line is cancel
return c;
#if MICROPY_REPL_EMACS_KEYS
} else if (c == CHAR_CTRL_D) {
// CTRL-D with non-empty line is delete-at-cursor
goto delete_key;
#endif
} else if (c == CHAR_CTRL_E) {
// CTRL-E is go-to-end-of-line
goto end_key;
#if MICROPY_REPL_EMACS_KEYS
} else if (c == CHAR_CTRL_F) {
// CTRL-F with non-empty line is go-forward-one-char
goto right_arrow_key;
} else if (c == CHAR_CTRL_K) {
// CTRL-K is kill from cursor to end-of-line, inclusive
vstr_cut_tail_bytes(rl.line, last_line_len - rl.cursor_pos);
// set redraw parameters
redraw_from_cursor = true;
} else if (c == CHAR_CTRL_N) {
// CTRL-N is go to next line in history
goto down_arrow_key;
} else if (c == CHAR_CTRL_P) {
// CTRL-P is go to previous line in history
goto up_arrow_key;
} else if (c == CHAR_CTRL_U) {
// CTRL-U is kill from beginning-of-line up to cursor
vstr_cut_out_bytes(rl.line, rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
// set redraw parameters
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
redraw_from_cursor = true;
#endif
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
} else if (c == CHAR_CTRL_W) {
goto backward_kill_word;
#endif
} else if (c == '\r') {
// newline
mp_hal_stdout_tx_str("\r\n");
readline_push_history(vstr_null_terminated_str(rl.line) + rl.orig_line_len);
return 0;
} else if (c == 27) {
// escape sequence
rl.escape_seq = ESEQ_ESC;
} else if (c == 8 || c == 127) {
// backspace/delete
if (rl.cursor_pos > rl.orig_line_len) {
// work out how many chars to backspace
#if MICROPY_REPL_AUTO_INDENT
int nspace = 0;
for (size_t i = rl.orig_line_len; i < rl.cursor_pos; i++) {
if (rl.line->buf[i] != ' ') {
nspace = 0;
break;
}
nspace += 1;
}
if (nspace < 4) {
nspace = 1;
} else {
nspace = 4;
}
#else
int nspace = 1;
#endif
// do the backspace
vstr_cut_out_bytes(rl.line, rl.cursor_pos - nspace, nspace);
// set redraw parameters
redraw_step_back = nspace;
redraw_from_cursor = true;
}
#if MICROPY_REPL_AUTO_INDENT
} else if ((rl.auto_indent_state & AUTO_INDENT_JUST_ADDED) && (c == 9 || c == ' ')) {
// tab/space after auto-indent: disable auto-indent
// - if it's a tab then leave existing indent
// - if it's a space then remove 3 spaces from existing indent
rl.auto_indent_state = 0;
if (c == ' ') {
redraw_step_back = 3;
vstr_cut_tail_bytes(rl.line, 3);
}
#endif
#if MICROPY_HELPER_REPL
} else if (c == 9) {
// tab magic
const char *compl_str;
size_t compl_len;
if (vstr_len(rl.line) != 0 && unichar_isspace(vstr_str(rl.line)[rl.cursor_pos - 1])) {
// expand tab to 4 spaces if it follows whitespace:
// - includes the case of additional indenting
// - includes the case of indenting the start of a line that's not the first line,
// because a newline will be the previous character
// - doesn't include the case when at the start of the first line, because we still
// want to use auto-complete there
compl_str = " ";
compl_len = 4;
} else {
// try to auto-complete a word
const char *cur_line_buf = vstr_str(rl.line) + rl.orig_line_len;
size_t cur_line_len = rl.cursor_pos - rl.orig_line_len;
compl_len = mp_repl_autocomplete(cur_line_buf, cur_line_len, &mp_plat_print, &compl_str);
}
if (compl_len == 0) {
// no match
} else if (compl_len == (size_t)(-1)) {
// many matches
mp_hal_stdout_tx_str(rl.prompt);
mp_hal_stdout_tx_strn(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
redraw_from_cursor = true;
} else {
// one match
for (size_t i = 0; i < compl_len; ++i) {
vstr_ins_byte(rl.line, rl.cursor_pos + i, *compl_str++);
}
// set redraw parameters
redraw_from_cursor = true;
redraw_step_forward = compl_len;
}
#endif
} else if (32 <= c && c <= 126) {
// printable character
vstr_ins_char(rl.line, rl.cursor_pos, c);
// set redraw parameters
redraw_from_cursor = true;
redraw_step_forward = 1;
}
} else if (rl.escape_seq == ESEQ_ESC) {
switch (c) {
case '[':
rl.escape_seq = ESEQ_ESC_BRACKET;
break;
case 'O':
rl.escape_seq = ESEQ_ESC_O;
break;
#if MICROPY_REPL_EMACS_WORDS_MOVE
case 'b':
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
backward_word:
#endif
redraw_step_back = cursor_count_word(0);
rl.escape_seq = ESEQ_NONE;
break;
case 'f':
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
forward_word:
#endif
redraw_step_forward = cursor_count_word(1);
rl.escape_seq = ESEQ_NONE;
break;
case 'd':
vstr_cut_out_bytes(rl.line, rl.cursor_pos, cursor_count_word(1));
redraw_from_cursor = true;
rl.escape_seq = ESEQ_NONE;
break;
case 127:
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
backward_kill_word:
#endif
redraw_step_back = cursor_count_word(0);
vstr_cut_out_bytes(rl.line, rl.cursor_pos - redraw_step_back, redraw_step_back);
redraw_from_cursor = true;
rl.escape_seq = ESEQ_NONE;
break;
#endif
default:
DEBUG_printf("(ESC %d)", c);
rl.escape_seq = ESEQ_NONE;
break;
}
} else if (rl.escape_seq == ESEQ_ESC_BRACKET) {
if ('0' <= c && c <= '9') {
rl.escape_seq = ESEQ_ESC_BRACKET_DIGIT;
rl.escape_seq_buf[0] = c;
} else {
rl.escape_seq = ESEQ_NONE;
if (c == 'A') {
#if MICROPY_REPL_EMACS_KEYS
up_arrow_key:
#endif
// up arrow
if (rl.hist_cur + 1 < (int)READLINE_HIST_SIZE && MP_STATE_PORT(readline_hist)[rl.hist_cur + 1] != NULL) {
// increase hist num
rl.hist_cur += 1;
// set line to history
rl.line->len = rl.orig_line_len;
vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
// set redraw parameters
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
redraw_from_cursor = true;
redraw_step_forward = rl.line->len - rl.orig_line_len;
}
} else if (c == 'B') {
#if MICROPY_REPL_EMACS_KEYS
down_arrow_key:
#endif
// down arrow
if (rl.hist_cur >= 0) {
// decrease hist num
rl.hist_cur -= 1;
// set line to history
vstr_cut_tail_bytes(rl.line, rl.line->len - rl.orig_line_len);
if (rl.hist_cur >= 0) {
vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
}
// set redraw parameters
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
redraw_from_cursor = true;
redraw_step_forward = rl.line->len - rl.orig_line_len;
}
} else if (c == 'C') {
#if MICROPY_REPL_EMACS_KEYS
right_arrow_key:
#endif
// right arrow
if (rl.cursor_pos < rl.line->len) {
redraw_step_forward = 1;
}
} else if (c == 'D') {
#if MICROPY_REPL_EMACS_KEYS
left_arrow_key:
#endif
// left arrow
if (rl.cursor_pos > rl.orig_line_len) {
redraw_step_back = 1;
}
} else if (c == 'H') {
// home
goto home_key;
} else if (c == 'F') {
// end
goto end_key;
} else {
DEBUG_printf("(ESC [ %d)", c);
}
}
} else if (rl.escape_seq == ESEQ_ESC_BRACKET_DIGIT) {
if (c == '~') {
if (rl.escape_seq_buf[0] == '1' || rl.escape_seq_buf[0] == '7') {
home_key:
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
} else if (rl.escape_seq_buf[0] == '4' || rl.escape_seq_buf[0] == '8') {
end_key:
redraw_step_forward = rl.line->len - rl.cursor_pos;
} else if (rl.escape_seq_buf[0] == '3') {
// delete
#if MICROPY_REPL_EMACS_KEYS
delete_key:
#endif
if (rl.cursor_pos < rl.line->len) {
vstr_cut_out_bytes(rl.line, rl.cursor_pos, 1);
redraw_from_cursor = true;
}
} else {
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
}
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
} else if (c == ';' && rl.escape_seq_buf[0] == '1') {
// ';' is used to separate parameters. so first parameter was '1',
// that's used for sequences like ctrl+left, which we will try to parse.
// escape_seq state is reset back to ESEQ_ESC_BRACKET, as if we've just received
// the opening bracket, because more parameters are to come.
// we don't track the parameters themselves to keep low on logic and code size. that
// might be required in the future if more complex sequences are added.
rl.escape_seq = ESEQ_ESC_BRACKET;
// goto away from the state-machine, as rl.escape_seq will be overridden.
goto redraw;
} else if (rl.escape_seq_buf[0] == '5' && c == 'C') {
// ctrl+right
goto forward_word;
} else if (rl.escape_seq_buf[0] == '5' && c == 'D') {
// ctrl+left
goto backward_word;
#endif
} else {
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
}
rl.escape_seq = ESEQ_NONE;
} else if (rl.escape_seq == ESEQ_ESC_O) {
switch (c) {
case 'H':
goto home_key;
case 'F':
goto end_key;
default:
DEBUG_printf("(ESC O %d)", c);
rl.escape_seq = ESEQ_NONE;
}
} else {
rl.escape_seq = ESEQ_NONE;
}
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
redraw:
#endif
// redraw command prompt, efficiently
if (redraw_step_back > 0) {
mp_hal_move_cursor_back(redraw_step_back);
rl.cursor_pos -= redraw_step_back;
}
if (redraw_from_cursor) {
if (rl.line->len < last_line_len) {
// erase old chars
mp_hal_erase_line_from_cursor(last_line_len - rl.cursor_pos);
}
// draw new chars
mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, rl.line->len - rl.cursor_pos);
// move cursor forward if needed (already moved forward by length of line, so move it back)
mp_hal_move_cursor_back(rl.line->len - (rl.cursor_pos + redraw_step_forward));
rl.cursor_pos += redraw_step_forward;
} else if (redraw_step_forward > 0) {
// draw over old chars to move cursor forwards
mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, redraw_step_forward);
rl.cursor_pos += redraw_step_forward;
}
#if MICROPY_REPL_AUTO_INDENT
rl.auto_indent_state &= ~AUTO_INDENT_JUST_ADDED;
#endif
return -1;
}
#if MICROPY_REPL_AUTO_INDENT
STATIC void readline_auto_indent(void) {
if (!(rl.auto_indent_state & AUTO_INDENT_ENABLED)) {
return;
}
vstr_t *line = rl.line;
if (line->len > 1 && line->buf[line->len - 1] == '\n') {
int i;
for (i = line->len - 1; i > 0; i--) {
if (line->buf[i - 1] == '\n') {
break;
}
}
size_t j;
for (j = i; j < line->len; j++) {
if (line->buf[j] != ' ') {
break;
}
}
// i=start of line; j=first non-space
if (i > 0 && j + 1 == line->len) {
// previous line is not first line and is all spaces
for (size_t k = i - 1; k > 0; --k) {
if (line->buf[k - 1] == '\n') {
// don't auto-indent if last 2 lines are all spaces
return;
} else if (line->buf[k - 1] != ' ') {
// 2nd previous line is not all spaces
break;
}
}
}
int n = (j - i) / 4;
if (line->buf[line->len - 2] == ':') {
n += 1;
}
while (n-- > 0) {
vstr_add_strn(line, " ", 4);
mp_hal_stdout_tx_strn(" ", 4);
rl.cursor_pos += 4;
rl.auto_indent_state |= AUTO_INDENT_JUST_ADDED;
}
}
}
#endif
void readline_note_newline(const char *prompt) {
rl.orig_line_len = rl.line->len;
rl.cursor_pos = rl.orig_line_len;
rl.prompt = prompt;
mp_hal_stdout_tx_str(prompt);
#if MICROPY_REPL_AUTO_INDENT
readline_auto_indent();
#endif
}
void readline_init(vstr_t *line, const char *prompt) {
rl.line = line;
rl.orig_line_len = line->len;
rl.escape_seq = ESEQ_NONE;
rl.escape_seq_buf[0] = 0;
rl.hist_cur = -1;
rl.cursor_pos = rl.orig_line_len;
rl.prompt = prompt;
mp_hal_stdout_tx_str(prompt);
#if MICROPY_REPL_AUTO_INDENT
if (vstr_len(line) == 0) {
// start with auto-indent enabled
rl.auto_indent_state = AUTO_INDENT_ENABLED;
}
readline_auto_indent();
#endif
}
int readline(vstr_t *line, const char *prompt) {
readline_init(line, prompt);
for (;;) {
int c = mp_hal_stdin_rx_chr();
int r = readline_process_char(c);
if (r >= 0) {
return r;
}
}
}
void readline_push_history(const char *line) {
if (line[0] != '\0'
&& (MP_STATE_PORT(readline_hist)[0] == NULL
|| strcmp(MP_STATE_PORT(readline_hist)[0], line) != 0)) {
// a line which is not empty and different from the last one
// so update the history
char *most_recent_hist = str_dup_maybe(line);
if (most_recent_hist != NULL) {
for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) {
MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1];
}
MP_STATE_PORT(readline_hist)[0] = most_recent_hist;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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_LIB_MP_READLINE_READLINE_H
#define MICROPY_INCLUDED_LIB_MP_READLINE_READLINE_H
#define CHAR_CTRL_A (1)
#define CHAR_CTRL_B (2)
#define CHAR_CTRL_C (3)
#define CHAR_CTRL_D (4)
#define CHAR_CTRL_E (5)
#define CHAR_CTRL_F (6)
#define CHAR_CTRL_K (11)
#define CHAR_CTRL_N (14)
#define CHAR_CTRL_P (16)
#define CHAR_CTRL_U (21)
#define CHAR_CTRL_W (23)
void readline_init0(void);
int readline(vstr_t *line, const char *prompt);
void readline_push_history(const char *line);
void readline_init(vstr_t *line, const char *prompt);
void readline_note_newline(const char *prompt);
int readline_process_char(int c);
#endif // MICROPY_INCLUDED_LIB_MP_READLINE_READLINE_H

View File

@@ -0,0 +1,50 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 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_LIB_UTILS_GCHELPER_H
#define MICROPY_INCLUDED_LIB_UTILS_GCHELPER_H
#include <stdint.h>
#if MICROPY_GCREGS_SETJMP
#include <setjmp.h>
typedef jmp_buf gc_helper_regs_t;
#else
#if defined(__x86_64__)
typedef uintptr_t gc_helper_regs_t[6];
#elif defined(__i386__)
typedef uintptr_t gc_helper_regs_t[4];
#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__)
typedef uintptr_t gc_helper_regs_t[10];
#elif defined(__aarch64__)
typedef uintptr_t gc_helper_regs_t[11]; // x19-x29
#endif
#endif
void gc_helper_collect_regs_and_stack(void);
#endif // MICROPY_INCLUDED_LIB_UTILS_GCHELPER_H

View File

@@ -0,0 +1,183 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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 "py/mpstate.h"
#include "py/gc.h"
#include "shared/runtime/gchelper.h"
#if MICROPY_ENABLE_GC
// Even if we have specific support for an architecture, it is
// possible to force use of setjmp-based implementation.
#if !MICROPY_GCREGS_SETJMP
// We capture here callee-save registers, i.e. ones which may contain
// interesting values held there by our callers. It doesn't make sense
// to capture caller-saved registers, because they, well, put on the
// stack already by the caller.
#if defined(__x86_64__)
STATIC void gc_helper_get_regs(gc_helper_regs_t arr) {
register long rbx asm ("rbx");
register long rbp asm ("rbp");
register long r12 asm ("r12");
register long r13 asm ("r13");
register long r14 asm ("r14");
register long r15 asm ("r15");
#ifdef __clang__
// TODO:
// This is dirty workaround for Clang. It tries to get around
// uncompliant (wrt to GCC) behavior of handling register variables.
// Application of this patch here is random, and done only to unbreak
// MacOS build. Better, cross-arch ways to deal with Clang issues should
// be found.
asm ("" : "=r" (rbx));
asm ("" : "=r" (rbp));
asm ("" : "=r" (r12));
asm ("" : "=r" (r13));
asm ("" : "=r" (r14));
asm ("" : "=r" (r15));
#endif
arr[0] = rbx;
arr[1] = rbp;
arr[2] = r12;
arr[3] = r13;
arr[4] = r14;
arr[5] = r15;
}
#elif defined(__i386__)
STATIC void gc_helper_get_regs(gc_helper_regs_t arr) {
register long ebx asm ("ebx");
register long esi asm ("esi");
register long edi asm ("edi");
register long ebp asm ("ebp");
#ifdef __clang__
// TODO:
// This is dirty workaround for Clang. It tries to get around
// uncompliant (wrt to GCC) behavior of handling register variables.
// Application of this patch here is random, and done only to unbreak
// MacOS build. Better, cross-arch ways to deal with Clang issues should
// be found.
asm ("" : "=r" (ebx));
asm ("" : "=r" (esi));
asm ("" : "=r" (edi));
asm ("" : "=r" (ebp));
#endif
arr[0] = ebx;
arr[1] = esi;
arr[2] = edi;
arr[3] = ebp;
}
#elif defined(__thumb2__) || defined(__thumb__) || defined(__arm__)
// Fallback implementation, prefer gchelper_m0.s or gchelper_m3.s
STATIC void gc_helper_get_regs(gc_helper_regs_t arr) {
register long r4 asm ("r4");
register long r5 asm ("r5");
register long r6 asm ("r6");
register long r7 asm ("r7");
register long r8 asm ("r8");
register long r9 asm ("r9");
register long r10 asm ("r10");
register long r11 asm ("r11");
register long r12 asm ("r12");
register long r13 asm ("r13");
arr[0] = r4;
arr[1] = r5;
arr[2] = r6;
arr[3] = r7;
arr[4] = r8;
arr[5] = r9;
arr[6] = r10;
arr[7] = r11;
arr[8] = r12;
arr[9] = r13;
}
#elif defined(__aarch64__)
STATIC void gc_helper_get_regs(gc_helper_regs_t arr) {
const register long x19 asm ("x19");
const register long x20 asm ("x20");
const register long x21 asm ("x21");
const register long x22 asm ("x22");
const register long x23 asm ("x23");
const register long x24 asm ("x24");
const register long x25 asm ("x25");
const register long x26 asm ("x26");
const register long x27 asm ("x27");
const register long x28 asm ("x28");
const register long x29 asm ("x29");
arr[0] = x19;
arr[1] = x20;
arr[2] = x21;
arr[3] = x22;
arr[4] = x23;
arr[5] = x24;
arr[6] = x25;
arr[7] = x26;
arr[8] = x27;
arr[9] = x28;
arr[10] = x29;
}
#else
#error "Architecture not supported for gc_helper_get_regs. Set MICROPY_GCREGS_SETJMP to use the fallback implementation."
#endif
#else // !MICROPY_GCREGS_SETJMP
// Even if we have specific support for an architecture, it is
// possible to force use of setjmp-based implementation.
STATIC void gc_helper_get_regs(gc_helper_regs_t arr) {
setjmp(arr);
}
#endif // MICROPY_GCREGS_SETJMP
// Explicitly mark this as noinline to make sure the regs variable
// is effectively at the top of the stack: otherwise, in builds where
// LTO is enabled and a lot of inlining takes place we risk a stack
// layout where regs is lower on the stack than pointers which have
// just been allocated but not yet marked, and get incorrectly sweeped.
MP_NOINLINE void gc_helper_collect_regs_and_stack(void) {
gc_helper_regs_t regs;
gc_helper_get_regs(regs);
// GC stack (and regs because we captured them)
void **regs_ptr = (void **)(void *)&regs;
gc_collect_root(regs_ptr, ((uintptr_t)MP_STATE_THREAD(stack_top) - (uintptr_t)&regs) / sizeof(uintptr_t));
}
#endif // MICROPY_ENABLE_GC

View File

@@ -0,0 +1,61 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018 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.
*/
.syntax unified
.cpu cortex-m0
.thumb
.section .text
.align 2
.global gc_helper_get_regs_and_sp
.type gc_helper_get_regs_and_sp, %function
@ uint gc_helper_get_regs_and_sp(r0=uint regs[10])
gc_helper_get_regs_and_sp:
@ store registers into given array
str r4, [r0, #0]
str r5, [r0, #4]
str r6, [r0, #8]
str r7, [r0, #12]
mov r1, r8
str r1, [r0, #16]
mov r1, r9
str r1, [r0, #20]
mov r1, r10
str r1, [r0, #24]
mov r1, r11
str r1, [r0, #28]
mov r1, r12
str r1, [r0, #32]
mov r1, r13
str r1, [r0, #36]
@ return the sp
mov r0, sp
bx lr
.size gc_helper_get_regs_and_sp, .-gc_helper_get_regs_and_sp

View File

@@ -0,0 +1,55 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2014 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.
*/
.syntax unified
.cpu cortex-m3
.thumb
.section .text
.align 2
.global gc_helper_get_regs_and_sp
.type gc_helper_get_regs_and_sp, %function
@ uint gc_helper_get_regs_and_sp(r0=uint regs[10])
gc_helper_get_regs_and_sp:
@ store registers into given array
str r4, [r0], #4
str r5, [r0], #4
str r6, [r0], #4
str r7, [r0], #4
str r8, [r0], #4
str r9, [r0], #4
str r10, [r0], #4
str r11, [r0], #4
str r12, [r0], #4
str r13, [r0], #4
@ return the sp
mov r0, sp
bx lr
.size gc_helper_get_regs_and_sp, .-gc_helper_get_regs_and_sp

View File

@@ -0,0 +1,47 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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 "py/mpstate.h"
#include "py/gc.h"
#include "shared/runtime/gchelper.h"
#if MICROPY_ENABLE_GC
// provided by gchelper_*.s
uintptr_t gc_helper_get_regs_and_sp(uintptr_t *regs);
MP_NOINLINE void gc_helper_collect_regs_and_stack(void) {
// get the registers and the sp
gc_helper_regs_t regs;
uintptr_t sp = gc_helper_get_regs_and_sp(regs);
// trace the stack, including the registers (since they live on the stack in this function)
gc_collect_root((void **)sp, ((uint32_t)MP_STATE_THREAD(stack_top) - sp) / sizeof(uint32_t));
}
#endif

View File

@@ -0,0 +1,38 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013-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 "py/obj.h"
#include "py/mpstate.h"
#if MICROPY_KBD_EXCEPTION
int mp_interrupt_char = -1;
void mp_hal_set_interrupt_char(int c) {
mp_interrupt_char = c;
}
#endif

View File

@@ -0,0 +1,32 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013-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_LIB_UTILS_INTERRUPT_CHAR_H
#define MICROPY_INCLUDED_LIB_UTILS_INTERRUPT_CHAR_H
extern int mp_interrupt_char;
void mp_hal_set_interrupt_char(int c);
#endif // MICROPY_INCLUDED_LIB_UTILS_INTERRUPT_CHAR_H

View File

@@ -0,0 +1,135 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2015 Daniel Campora
* 2018 Tobias Badertscher
*
* 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 "py/runtime.h"
#include "py/gc.h"
#include "shared/runtime/mpirq.h"
#if MICROPY_ENABLE_SCHEDULER
/******************************************************************************
DECLARE PUBLIC DATA
******************************************************************************/
const mp_arg_t mp_irq_init_args[] = {
{ MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
{ MP_QSTR_trigger, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} },
};
/******************************************************************************
DECLARE PRIVATE DATA
******************************************************************************/
/******************************************************************************
DEFINE PUBLIC FUNCTIONS
******************************************************************************/
mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent) {
mp_irq_obj_t *self = m_new0(mp_irq_obj_t, 1);
mp_irq_init(self, methods, parent);
return self;
}
void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent) {
self->base.type = &mp_irq_type;
self->methods = (mp_irq_methods_t *)methods;
self->parent = parent;
self->handler = mp_const_none;
self->ishard = false;
}
void mp_irq_handler(mp_irq_obj_t *self) {
if (self->handler != mp_const_none) {
if (self->ishard) {
// When executing code within a handler we must lock the scheduler to
// prevent any scheduled callbacks from running, and lock the GC to
// prevent any memory allocations.
mp_sched_lock();
gc_lock();
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
mp_call_function_1(self->handler, self->parent);
nlr_pop();
} else {
// Uncaught exception; disable the callback so that it doesn't run again
self->methods->trigger(self->parent, 0);
self->handler = mp_const_none;
printf("Uncaught exception in IRQ callback handler\n");
mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val));
}
gc_unlock();
mp_sched_unlock();
} else {
// Schedule call to user function
mp_sched_schedule(self->handler, self->parent);
}
}
}
/******************************************************************************/
// MicroPython bindings
STATIC mp_obj_t mp_irq_flags(mp_obj_t self_in) {
mp_irq_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_int(self->methods->info(self->parent, MP_IRQ_INFO_FLAGS));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_irq_flags_obj, mp_irq_flags);
STATIC mp_obj_t mp_irq_trigger(size_t n_args, const mp_obj_t *args) {
mp_irq_obj_t *self = MP_OBJ_TO_PTR(args[0]);
mp_obj_t ret_obj = mp_obj_new_int(self->methods->info(self->parent, MP_IRQ_INFO_TRIGGERS));
if (n_args == 2) {
// Set trigger
self->methods->trigger(self->parent, mp_obj_get_int(args[1]));
}
return ret_obj;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_irq_trigger_obj, 1, 2, mp_irq_trigger);
STATIC mp_obj_t mp_irq_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 0, 0, false);
mp_irq_handler(MP_OBJ_TO_PTR(self_in));
return mp_const_none;
}
STATIC const mp_rom_map_elem_t mp_irq_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_flags), MP_ROM_PTR(&mp_irq_flags_obj) },
{ MP_ROM_QSTR(MP_QSTR_trigger), MP_ROM_PTR(&mp_irq_trigger_obj) },
};
STATIC MP_DEFINE_CONST_DICT(mp_irq_locals_dict, mp_irq_locals_dict_table);
const mp_obj_type_t mp_irq_type = {
{ &mp_type_type },
.name = MP_QSTR_irq,
.call = mp_irq_call,
.locals_dict = (mp_obj_dict_t *)&mp_irq_locals_dict,
};
#endif // MICROPY_ENABLE_SCHEDULER

View File

@@ -0,0 +1,82 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2015 Daniel Campora
*
* 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_LIB_UTILS_MPIRQ_H
#define MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H
#include "py/runtime.h"
/******************************************************************************
DEFINE CONSTANTS
******************************************************************************/
enum {
MP_IRQ_ARG_INIT_handler = 0,
MP_IRQ_ARG_INIT_trigger,
MP_IRQ_ARG_INIT_hard,
MP_IRQ_ARG_INIT_NUM_ARGS,
};
/******************************************************************************
DEFINE TYPES
******************************************************************************/
typedef mp_uint_t (*mp_irq_trigger_fun_t)(mp_obj_t self, mp_uint_t trigger);
typedef mp_uint_t (*mp_irq_info_fun_t)(mp_obj_t self, mp_uint_t info_type);
enum {
MP_IRQ_INFO_FLAGS,
MP_IRQ_INFO_TRIGGERS,
};
typedef struct _mp_irq_methods_t {
mp_irq_trigger_fun_t trigger;
mp_irq_info_fun_t info;
} mp_irq_methods_t;
typedef struct _mp_irq_obj_t {
mp_obj_base_t base;
mp_irq_methods_t *methods;
mp_obj_t parent;
mp_obj_t handler;
bool ishard;
} mp_irq_obj_t;
/******************************************************************************
DECLARE EXPORTED DATA
******************************************************************************/
extern const mp_arg_t mp_irq_init_args[];
extern const mp_obj_type_t mp_irq_type;
/******************************************************************************
DECLARE PUBLIC FUNCTIONS
******************************************************************************/
mp_irq_obj_t *mp_irq_new(const mp_irq_methods_t *methods, mp_obj_t parent);
void mp_irq_init(mp_irq_obj_t *self, const mp_irq_methods_t *methods, mp_obj_t parent);
void mp_irq_handler(mp_irq_obj_t *self);
#endif // MICROPY_INCLUDED_LIB_UTILS_MPIRQ_H

View File

@@ -0,0 +1,726 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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 <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "py/compile.h"
#include "py/runtime.h"
#include "py/repl.h"
#include "py/gc.h"
#include "py/frozenmod.h"
#include "py/mphal.h"
#if MICROPY_HW_ENABLE_USB
#include "irq.h"
#include "usb.h"
#endif
#include "shared/readline/readline.h"
#include "shared/runtime/pyexec.h"
#include "genhdr/mpversion.h"
pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL;
int pyexec_system_exit = 0;
#if MICROPY_REPL_INFO
STATIC bool repl_display_debugging_info = 0;
#endif
#define EXEC_FLAG_PRINT_EOF (1 << 0)
#define EXEC_FLAG_ALLOW_DEBUGGING (1 << 1)
#define EXEC_FLAG_IS_REPL (1 << 2)
#define EXEC_FLAG_SOURCE_IS_RAW_CODE (1 << 3)
#define EXEC_FLAG_SOURCE_IS_VSTR (1 << 4)
#define EXEC_FLAG_SOURCE_IS_FILENAME (1 << 5)
#define EXEC_FLAG_SOURCE_IS_READER (1 << 6)
// parses, compiles and executes the code in the lexer
// frees the lexer before returning
// EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output
// EXEC_FLAG_ALLOW_DEBUGGING allows debugging info to be printed after executing the code
// EXEC_FLAG_IS_REPL is used for REPL inputs (flag passed on to mp_compile)
STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, mp_uint_t exec_flags) {
int ret = 0;
#if MICROPY_REPL_INFO
uint32_t start = 0;
#endif
#ifdef MICROPY_BOARD_BEFORE_PYTHON_EXEC
MICROPY_BOARD_BEFORE_PYTHON_EXEC(input_kind, exec_flags);
#endif
// by default a SystemExit exception returns 0
pyexec_system_exit = 0;
nlr_buf_t nlr;
nlr.ret_val = NULL;
if (nlr_push(&nlr) == 0) {
mp_obj_t module_fun;
#if MICROPY_MODULE_FROZEN_MPY
if (exec_flags & EXEC_FLAG_SOURCE_IS_RAW_CODE) {
// source is a raw_code object, create the function
const mp_frozen_module_t *frozen = source;
mp_module_context_t *ctx = m_new_obj(mp_module_context_t);
ctx->module.globals = mp_globals_get();
ctx->constants = frozen->constants;
module_fun = mp_make_function_from_raw_code(frozen->rc, ctx, NULL);
} else
#endif
{
#if MICROPY_ENABLE_COMPILER
mp_lexer_t *lex;
if (exec_flags & EXEC_FLAG_SOURCE_IS_VSTR) {
const vstr_t *vstr = source;
lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, 0);
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) {
lex = mp_lexer_new(MP_QSTR__lt_stdin_gt_, *(mp_reader_t *)source);
} else if (exec_flags & EXEC_FLAG_SOURCE_IS_FILENAME) {
lex = mp_lexer_new_from_file(source);
} else {
lex = (mp_lexer_t *)source;
}
// source is a lexer, parse and compile the script
qstr source_name = lex->source_name;
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
module_fun = mp_compile(&parse_tree, source_name, exec_flags & EXEC_FLAG_IS_REPL);
#else
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("script compilation not supported"));
#endif
}
// execute code
mp_hal_set_interrupt_char(CHAR_CTRL_C); // allow ctrl-C to interrupt us
#if MICROPY_REPL_INFO
start = mp_hal_ticks_ms();
#endif
mp_call_function_0(module_fun);
mp_hal_set_interrupt_char(-1); // disable interrupt
mp_handle_pending(true); // handle any pending exceptions (and any callbacks)
nlr_pop();
ret = 1;
if (exec_flags & EXEC_FLAG_PRINT_EOF) {
mp_hal_stdout_tx_strn("\x04", 1);
}
} else {
// uncaught exception
mp_hal_set_interrupt_char(-1); // disable interrupt
mp_handle_pending(false); // clear any pending exceptions (and run any callbacks)
if (exec_flags & EXEC_FLAG_SOURCE_IS_READER) {
const mp_reader_t *reader = source;
reader->close(reader->data);
}
// print EOF after normal output
if (exec_flags & EXEC_FLAG_PRINT_EOF) {
mp_hal_stdout_tx_strn("\x04", 1);
}
// check for SystemExit
if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
// at the moment, the value of SystemExit is unused
ret = pyexec_system_exit;
} else {
mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(nlr.ret_val));
ret = 0;
}
}
#if MICROPY_REPL_INFO
// display debugging info if wanted
if ((exec_flags & EXEC_FLAG_ALLOW_DEBUGGING) && repl_display_debugging_info) {
mp_uint_t ticks = mp_hal_ticks_ms() - start; // TODO implement a function that does this properly
printf("took " UINT_FMT " ms\n", ticks);
// qstr info
{
size_t n_pool, n_qstr, n_str_data_bytes, n_total_bytes;
qstr_pool_info(&n_pool, &n_qstr, &n_str_data_bytes, &n_total_bytes);
printf("qstr:\n n_pool=%u\n n_qstr=%u\n "
"n_str_data_bytes=%u\n n_total_bytes=%u\n",
(unsigned)n_pool, (unsigned)n_qstr, (unsigned)n_str_data_bytes, (unsigned)n_total_bytes);
}
#if MICROPY_ENABLE_GC
// run collection and print GC info
gc_collect();
gc_dump_info();
#endif
}
#endif
if (exec_flags & EXEC_FLAG_PRINT_EOF) {
mp_hal_stdout_tx_strn("\x04", 1);
}
#ifdef MICROPY_BOARD_AFTER_PYTHON_EXEC
MICROPY_BOARD_AFTER_PYTHON_EXEC(input_kind, exec_flags, nlr.ret_val, &ret);
#endif
return ret;
}
#if MICROPY_ENABLE_COMPILER
// This can be configured by a port (and even configured to a function to be
// computed dynamically) to indicate the maximum number of bytes that can be
// held in the stdin buffer.
#ifndef MICROPY_REPL_STDIN_BUFFER_MAX
#define MICROPY_REPL_STDIN_BUFFER_MAX (256)
#endif
typedef struct _mp_reader_stdin_t {
bool eof;
uint16_t window_max;
uint16_t window_remain;
} mp_reader_stdin_t;
STATIC mp_uint_t mp_reader_stdin_readbyte(void *data) {
mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data;
if (reader->eof) {
return MP_READER_EOF;
}
int c = mp_hal_stdin_rx_chr();
if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) {
reader->eof = true;
mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host
if (c == CHAR_CTRL_C) {
#if MICROPY_KBD_EXCEPTION
MP_STATE_VM(mp_kbd_exception).traceback_data = NULL;
nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception)));
#else
mp_raise_type(&mp_type_KeyboardInterrupt);
#endif
} else {
return MP_READER_EOF;
}
}
if (--reader->window_remain == 0) {
mp_hal_stdout_tx_strn("\x01", 1); // indicate window available to host
reader->window_remain = reader->window_max;
}
return c;
}
STATIC void mp_reader_stdin_close(void *data) {
mp_reader_stdin_t *reader = (mp_reader_stdin_t *)data;
if (!reader->eof) {
reader->eof = true;
mp_hal_stdout_tx_strn("\x04", 1); // indicate end to host
for (;;) {
int c = mp_hal_stdin_rx_chr();
if (c == CHAR_CTRL_C || c == CHAR_CTRL_D) {
break;
}
}
}
}
STATIC void mp_reader_new_stdin(mp_reader_t *reader, mp_reader_stdin_t *reader_stdin, uint16_t buf_max) {
// Make flow-control window half the buffer size, and indicate to the host that 2x windows are
// free (sending the window size implicitly indicates that a window is free, and then the 0x01
// indicates that another window is free).
size_t window = buf_max / 2;
char reply[3] = { window & 0xff, window >> 8, 0x01 };
mp_hal_stdout_tx_strn(reply, sizeof(reply));
reader_stdin->eof = false;
reader_stdin->window_max = window;
reader_stdin->window_remain = window;
reader->data = reader_stdin;
reader->readbyte = mp_reader_stdin_readbyte;
reader->close = mp_reader_stdin_close;
}
STATIC int do_reader_stdin(int c) {
if (c != 'A') {
// Unsupported command.
mp_hal_stdout_tx_strn("R\x00", 2);
return 0;
}
// Indicate reception of command.
mp_hal_stdout_tx_strn("R\x01", 2);
mp_reader_t reader;
mp_reader_stdin_t reader_stdin;
mp_reader_new_stdin(&reader, &reader_stdin, MICROPY_REPL_STDIN_BUFFER_MAX);
int exec_flags = EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_READER;
return parse_compile_execute(&reader, MP_PARSE_FILE_INPUT, exec_flags);
}
#if MICROPY_REPL_EVENT_DRIVEN
typedef struct _repl_t {
// This structure originally also held current REPL line,
// but it was moved to MP_STATE_VM(repl_line) as containing
// root pointer. Still keep structure in case more state
// will be added later.
// vstr_t line;
bool cont_line;
bool paste_mode;
} repl_t;
repl_t repl;
STATIC int pyexec_raw_repl_process_char(int c);
STATIC int pyexec_friendly_repl_process_char(int c);
void pyexec_event_repl_init(void) {
MP_STATE_VM(repl_line) = vstr_new(32);
repl.cont_line = false;
repl.paste_mode = false;
// no prompt before printing friendly REPL banner or entering raw REPL
readline_init(MP_STATE_VM(repl_line), "");
if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
pyexec_raw_repl_process_char(CHAR_CTRL_A);
} else {
pyexec_friendly_repl_process_char(CHAR_CTRL_B);
}
}
STATIC int pyexec_raw_repl_process_char(int c) {
if (c == CHAR_CTRL_A) {
// reset raw REPL
if (vstr_len(MP_STATE_VM(repl_line)) == 2 && vstr_str(MP_STATE_VM(repl_line))[0] == CHAR_CTRL_E) {
int ret = do_reader_stdin(vstr_str(MP_STATE_VM(repl_line))[1]);
if (ret & PYEXEC_FORCED_EXIT) {
return ret;
}
goto reset;
}
mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n");
goto reset;
} else if (c == CHAR_CTRL_B) {
// change to friendly REPL
pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL;
vstr_reset(MP_STATE_VM(repl_line));
repl.cont_line = false;
repl.paste_mode = false;
pyexec_friendly_repl_process_char(CHAR_CTRL_B);
return 0;
} else if (c == CHAR_CTRL_C) {
// clear line
vstr_reset(MP_STATE_VM(repl_line));
return 0;
} else if (c == CHAR_CTRL_D) {
// input finished
} else {
// let through any other raw 8-bit value
vstr_add_byte(MP_STATE_VM(repl_line), c);
return 0;
}
// indicate reception of command
mp_hal_stdout_tx_str("OK");
if (MP_STATE_VM(repl_line)->len == 0) {
// exit for a soft reset
mp_hal_stdout_tx_str("\r\n");
vstr_clear(MP_STATE_VM(repl_line));
return PYEXEC_FORCED_EXIT;
}
int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR);
if (ret & PYEXEC_FORCED_EXIT) {
return ret;
}
reset:
vstr_reset(MP_STATE_VM(repl_line));
mp_hal_stdout_tx_str(">");
return 0;
}
STATIC int pyexec_friendly_repl_process_char(int c) {
if (repl.paste_mode) {
if (c == CHAR_CTRL_C) {
// cancel everything
mp_hal_stdout_tx_str("\r\n");
goto input_restart;
} else if (c == CHAR_CTRL_D) {
// end of input
mp_hal_stdout_tx_str("\r\n");
int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_FILE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR);
if (ret & PYEXEC_FORCED_EXIT) {
return ret;
}
goto input_restart;
} else {
// add char to buffer and echo
vstr_add_byte(MP_STATE_VM(repl_line), c);
if (c == '\r') {
mp_hal_stdout_tx_str("\r\n=== ");
} else {
char buf[1] = {c};
mp_hal_stdout_tx_strn(buf, 1);
}
return 0;
}
}
int ret = readline_process_char(c);
if (!repl.cont_line) {
if (ret == CHAR_CTRL_A) {
// change to raw REPL
pyexec_mode_kind = PYEXEC_MODE_RAW_REPL;
mp_hal_stdout_tx_str("\r\n");
pyexec_raw_repl_process_char(CHAR_CTRL_A);
return 0;
} else if (ret == CHAR_CTRL_B) {
// reset friendly REPL
mp_hal_stdout_tx_str("\r\n");
mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION);
mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE);
mp_hal_stdout_tx_str("\r\n");
#if MICROPY_PY_BUILTINS_HELP
mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n");
#endif
goto input_restart;
} else if (ret == CHAR_CTRL_C) {
// break
mp_hal_stdout_tx_str("\r\n");
goto input_restart;
} else if (ret == CHAR_CTRL_D) {
// exit for a soft reset
mp_hal_stdout_tx_str("\r\n");
vstr_clear(MP_STATE_VM(repl_line));
return PYEXEC_FORCED_EXIT;
} else if (ret == CHAR_CTRL_E) {
// paste mode
mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== ");
vstr_reset(MP_STATE_VM(repl_line));
repl.paste_mode = true;
return 0;
}
if (ret < 0) {
return 0;
}
if (!mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) {
goto exec;
}
vstr_add_byte(MP_STATE_VM(repl_line), '\n');
repl.cont_line = true;
readline_note_newline(mp_repl_get_ps2());
return 0;
} else {
if (ret == CHAR_CTRL_C) {
// cancel everything
mp_hal_stdout_tx_str("\r\n");
repl.cont_line = false;
goto input_restart;
} else if (ret == CHAR_CTRL_D) {
// stop entering compound statement
goto exec;
}
if (ret < 0) {
return 0;
}
if (mp_repl_continue_with_input(vstr_null_terminated_str(MP_STATE_VM(repl_line)))) {
vstr_add_byte(MP_STATE_VM(repl_line), '\n');
readline_note_newline(mp_repl_get_ps2());
return 0;
}
exec:;
int ret = parse_compile_execute(MP_STATE_VM(repl_line), MP_PARSE_SINGLE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR);
if (ret & PYEXEC_FORCED_EXIT) {
return ret;
}
input_restart:
vstr_reset(MP_STATE_VM(repl_line));
repl.cont_line = false;
repl.paste_mode = false;
readline_init(MP_STATE_VM(repl_line), mp_repl_get_ps1());
return 0;
}
}
uint8_t pyexec_repl_active;
int pyexec_event_repl_process_char(int c) {
pyexec_repl_active = 1;
int res;
if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
res = pyexec_raw_repl_process_char(c);
} else {
res = pyexec_friendly_repl_process_char(c);
}
pyexec_repl_active = 0;
return res;
}
#else // MICROPY_REPL_EVENT_DRIVEN
int pyexec_raw_repl(void) {
vstr_t line;
vstr_init(&line, 32);
raw_repl_reset:
mp_hal_stdout_tx_str("raw REPL; CTRL-B to exit\r\n");
for (;;) {
vstr_reset(&line);
mp_hal_stdout_tx_str(">");
for (;;) {
int c = mp_hal_stdin_rx_chr();
if (c == CHAR_CTRL_A) {
// reset raw REPL
if (vstr_len(&line) == 2 && vstr_str(&line)[0] == CHAR_CTRL_E) {
int ret = do_reader_stdin(vstr_str(&line)[1]);
if (ret & PYEXEC_FORCED_EXIT) {
return ret;
}
vstr_reset(&line);
mp_hal_stdout_tx_str(">");
continue;
}
goto raw_repl_reset;
} else if (c == CHAR_CTRL_B) {
// change to friendly REPL
mp_hal_stdout_tx_str("\r\n");
vstr_clear(&line);
pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL;
return 0;
} else if (c == CHAR_CTRL_C) {
// clear line
vstr_reset(&line);
} else if (c == CHAR_CTRL_D) {
// input finished
break;
} else {
// let through any other raw 8-bit value
vstr_add_byte(&line, c);
}
}
// indicate reception of command
mp_hal_stdout_tx_str("OK");
if (line.len == 0) {
// exit for a soft reset
mp_hal_stdout_tx_str("\r\n");
vstr_clear(&line);
return PYEXEC_FORCED_EXIT;
}
int ret = parse_compile_execute(&line, MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF | EXEC_FLAG_SOURCE_IS_VSTR);
if (ret & PYEXEC_FORCED_EXIT) {
return ret;
}
}
}
int pyexec_friendly_repl(void) {
vstr_t line;
vstr_init(&line, 32);
friendly_repl_reset:
mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION);
mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE);
mp_hal_stdout_tx_str("\r\n");
#if MICROPY_PY_BUILTINS_HELP
mp_hal_stdout_tx_str("Type \"help()\" for more information.\r\n");
#endif
// to test ctrl-C
/*
{
uint32_t x[4] = {0x424242, 0xdeaddead, 0x242424, 0xdeadbeef};
for (;;) {
nlr_buf_t nlr;
printf("pyexec_repl: %p\n", x);
mp_hal_set_interrupt_char(CHAR_CTRL_C);
if (nlr_push(&nlr) == 0) {
for (;;) {
}
} else {
printf("break\n");
}
}
}
*/
for (;;) {
input_restart:
#if MICROPY_HW_ENABLE_USB
if (usb_vcp_is_enabled()) {
// If the user gets to here and interrupts are disabled then
// they'll never see the prompt, traceback etc. The USB REPL needs
// interrupts to be enabled or no transfers occur. So we try to
// do the user a favor and reenable interrupts.
if (query_irq() == IRQ_STATE_DISABLED) {
enable_irq(IRQ_STATE_ENABLED);
mp_hal_stdout_tx_str("MPY: enabling IRQs\r\n");
}
}
#endif
// If the GC is locked at this point there is no way out except a reset,
// so force the GC to be unlocked to help the user debug what went wrong.
if (MP_STATE_THREAD(gc_lock_depth) != 0) {
MP_STATE_THREAD(gc_lock_depth) = 0;
}
vstr_reset(&line);
int ret = readline(&line, mp_repl_get_ps1());
mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT;
if (ret == CHAR_CTRL_A) {
// change to raw REPL
mp_hal_stdout_tx_str("\r\n");
vstr_clear(&line);
pyexec_mode_kind = PYEXEC_MODE_RAW_REPL;
return 0;
} else if (ret == CHAR_CTRL_B) {
// reset friendly REPL
mp_hal_stdout_tx_str("\r\n");
goto friendly_repl_reset;
} else if (ret == CHAR_CTRL_C) {
// break
mp_hal_stdout_tx_str("\r\n");
continue;
} else if (ret == CHAR_CTRL_D) {
// exit for a soft reset
mp_hal_stdout_tx_str("\r\n");
vstr_clear(&line);
return PYEXEC_FORCED_EXIT;
} else if (ret == CHAR_CTRL_E) {
// paste mode
mp_hal_stdout_tx_str("\r\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== ");
vstr_reset(&line);
for (;;) {
char c = mp_hal_stdin_rx_chr();
if (c == CHAR_CTRL_C) {
// cancel everything
mp_hal_stdout_tx_str("\r\n");
goto input_restart;
} else if (c == CHAR_CTRL_D) {
// end of input
mp_hal_stdout_tx_str("\r\n");
break;
} else {
// add char to buffer and echo
vstr_add_byte(&line, c);
if (c == '\r') {
mp_hal_stdout_tx_str("\r\n=== ");
} else {
mp_hal_stdout_tx_strn(&c, 1);
}
}
}
parse_input_kind = MP_PARSE_FILE_INPUT;
} else if (vstr_len(&line) == 0) {
continue;
} else {
// got a line with non-zero length, see if it needs continuing
while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) {
vstr_add_byte(&line, '\n');
ret = readline(&line, mp_repl_get_ps2());
if (ret == CHAR_CTRL_C) {
// cancel everything
mp_hal_stdout_tx_str("\r\n");
goto input_restart;
} else if (ret == CHAR_CTRL_D) {
// stop entering compound statement
break;
}
}
}
ret = parse_compile_execute(&line, parse_input_kind, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL | EXEC_FLAG_SOURCE_IS_VSTR);
if (ret & PYEXEC_FORCED_EXIT) {
return ret;
}
}
}
#endif // MICROPY_REPL_EVENT_DRIVEN
#endif // MICROPY_ENABLE_COMPILER
int pyexec_file(const char *filename) {
return parse_compile_execute(filename, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_FILENAME);
}
int pyexec_file_if_exists(const char *filename) {
#if MICROPY_MODULE_FROZEN
if (mp_find_frozen_module(filename, NULL, NULL) == MP_IMPORT_STAT_FILE) {
return pyexec_frozen_module(filename);
}
#endif
if (mp_import_stat(filename) != MP_IMPORT_STAT_FILE) {
return 1; // success (no file is the same as an empty file executing without fail)
}
return pyexec_file(filename);
}
#if MICROPY_MODULE_FROZEN
int pyexec_frozen_module(const char *name) {
void *frozen_data;
int frozen_type;
mp_find_frozen_module(name, &frozen_type, &frozen_data);
switch (frozen_type) {
#if MICROPY_MODULE_FROZEN_STR
case MP_FROZEN_STR:
return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, 0);
#endif
#if MICROPY_MODULE_FROZEN_MPY
case MP_FROZEN_MPY:
return parse_compile_execute(frozen_data, MP_PARSE_FILE_INPUT, EXEC_FLAG_SOURCE_IS_RAW_CODE);
#endif
default:
printf("could not find module '%s'\n", name);
return false;
}
}
#endif
#if MICROPY_REPL_INFO
mp_obj_t pyb_set_repl_info(mp_obj_t o_value) {
repl_display_debugging_info = mp_obj_get_int(o_value);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj, pyb_set_repl_info);
#endif

View File

@@ -0,0 +1,59 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 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_LIB_UTILS_PYEXEC_H
#define MICROPY_INCLUDED_LIB_UTILS_PYEXEC_H
#include "py/obj.h"
typedef enum {
PYEXEC_MODE_FRIENDLY_REPL,
PYEXEC_MODE_RAW_REPL,
} pyexec_mode_kind_t;
extern pyexec_mode_kind_t pyexec_mode_kind;
// Set this to the value (eg PYEXEC_FORCED_EXIT) that will be propagated through
// the pyexec functions if a SystemExit exception is raised by the running code.
// It will reset to 0 at the start of each execution (eg each REPL entry).
extern int pyexec_system_exit;
#define PYEXEC_FORCED_EXIT (0x100)
int pyexec_raw_repl(void);
int pyexec_friendly_repl(void);
int pyexec_file(const char *filename);
int pyexec_file_if_exists(const char *filename);
int pyexec_frozen_module(const char *name);
void pyexec_event_repl_init(void);
int pyexec_event_repl_process_char(int c);
extern uint8_t pyexec_repl_active;
#if MICROPY_REPL_INFO
mp_obj_t pyb_set_repl_info(mp_obj_t o_value);
MP_DECLARE_CONST_FUN_OBJ_1(pyb_set_repl_info_obj);
#endif
#endif // MICROPY_INCLUDED_LIB_UTILS_PYEXEC_H

View File

@@ -0,0 +1,132 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Ayke van Laethem
*
* 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 "semihosting.h"
// Resources:
// http://embed.rs/articles/2016/semi-hosting-rust/
// https://wiki.dlang.org/Minimal_semihosted_ARM_Cortex-M_%22Hello_World%22
// https://github.com/arduino/OpenOCD/blob/master/src/target/arm_semihosting.c
#define SYS_OPEN 0x01
#define SYS_WRITEC 0x03
#define SYS_WRITE 0x05
#define SYS_READC 0x07
// Constants:
#define OPEN_MODE_READ (0) // mode "r"
#define OPEN_MODE_WRITE (4) // mode "w"
#ifndef __thumb__
#error Semihosting is only implemented for ARM microcontrollers.
#endif
static int mp_semihosting_stdout;
static uint32_t mp_semihosting_call(uint32_t num, const void *arg) {
// A semihosting call works as follows, similar to a SVCall:
// * the call is invoked by a special breakpoint: 0xAB
// * the command is placed in r0
// * a pointer to the arguments is placed in r1
// * the return value is placed in r0
// Note that because it uses the breakpoint instruction, applications
// will hang if they're not connected to a debugger. And they'll be
// stuck in a breakpoint if semihosting is not specifically enabled in
// the debugger.
// Also note that semihosting is extremely slow (sometimes >100ms per
// call).
register uint32_t num_reg __asm__ ("r0") = num;
register const void *args_reg __asm__ ("r1") = arg;
__asm__ __volatile__ (
"bkpt 0xAB\n" // invoke semihosting call
: "+r" (num_reg) // call number and result
: "r" (args_reg) // arguments
: "memory"); // make sure args aren't optimized away
return num_reg; // r0, which became the result
}
static int mp_semihosting_open_console(uint32_t mode) {
struct {
char *name;
uint32_t mode;
uint32_t name_len;
} args = {
.name = ":tt", // magic path to console
.mode = mode, // e.g. "r", "w" (see OPEN_MODE_* constants)
.name_len = 3, // strlen(":tt")
};
return mp_semihosting_call(SYS_OPEN, &args);
}
void mp_semihosting_init() {
mp_semihosting_stdout = mp_semihosting_open_console(OPEN_MODE_WRITE);
}
int mp_semihosting_rx_char() {
return mp_semihosting_call(SYS_READC, NULL);
}
static void mp_semihosting_tx_char(char c) {
mp_semihosting_call(SYS_WRITEC, &c);
}
uint32_t mp_semihosting_tx_strn(const char *str, size_t len) {
if (len == 0) {
return 0; // nothing to do
}
if (len == 1) {
mp_semihosting_tx_char(*str); // maybe faster?
return 0;
}
struct {
uint32_t fd;
const char *str;
uint32_t len;
} args = {
.fd = mp_semihosting_stdout,
.str = str,
.len = len,
};
return mp_semihosting_call(SYS_WRITE, &args);
}
uint32_t mp_semihosting_tx_strn_cooked(const char *str, size_t len) {
// Write chunks of data until (excluding) the first '\n' character,
// insert a '\r' character, and then continue with the next chunk
// (starting with '\n').
// Doing byte-by-byte writes would be easier to implement but is far
// too slow.
size_t start = 0;
for (size_t i = 0; i < len; i++) {
if (str[i] == '\n') {
mp_semihosting_tx_strn(str + start, i - start);
mp_semihosting_tx_char('\r');
start = i;
}
}
return mp_semihosting_tx_strn(str + start, len - start);
}

View File

@@ -0,0 +1,51 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Ayke van Laethem
*
* 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_LIB_UTILS_SEMIHOSTING_H
#define MICROPY_INCLUDED_LIB_UTILS_SEMIHOSTING_H
/*
To use semi-hosting for a replacement UART:
- Add lib/semihosting/semihosting.c to the Makefile sources.
- Call mp_semihosting_init() in main(), around the time UART is initialized.
- Replace mp_hal_stdin_rx_chr and similar in mphalport.c with the semihosting equivalent.
- Include lib/semihosting/semihosting.h in the relevant files.
Then make sure the debugger is attached and enables semihosting. In OpenOCD this is
done with ARM semihosting enable followed by reset. The terminal will need further
configuration to work with MicroPython (bash: stty raw -echo).
*/
#include <stddef.h>
#include <stdint.h>
void mp_semihosting_init();
int mp_semihosting_rx_char();
uint32_t mp_semihosting_tx_strn(const char *str, size_t len);
uint32_t mp_semihosting_tx_strn_cooked(const char *str, size_t len);
#endif // MICROPY_INCLUDED_LIB_UTILS_SEMIHOSTING_H

View File

@@ -0,0 +1,62 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 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 <string.h>
#include "py/mphal.h"
/*
* Extra stdout functions
* These can be either optimized for a particular port, or reference
* implementation below can be used.
*/
// Send "cooked" string of given length, where every occurrence of
// LF character is replaced with CR LF ("\n" is converted to "\r\n").
// This is an optimised version to reduce the number of calls made
// to mp_hal_stdout_tx_strn.
void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) {
const char *last = str;
while (len--) {
if (*str == '\n') {
if (str > last) {
mp_hal_stdout_tx_strn(last, str - last);
}
mp_hal_stdout_tx_strn("\r\n", 2);
++str;
last = str;
} else {
++str;
}
}
if (str > last) {
mp_hal_stdout_tx_strn(last, str - last);
}
}
// Send zero-terminated string
void mp_hal_stdout_tx_str(const char *str) {
mp_hal_stdout_tx_strn(str, strlen(str));
}

View File

@@ -0,0 +1,175 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2019 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/obj.h"
#include "py/stream.h"
#include "py/mperrno.h"
#include "py/mphal.h"
// TODO make stdin, stdout and stderr writable objects so they can
// be changed by Python code. This requires some changes, as these
// objects are in a read-only module (py/modsys.c).
/******************************************************************************/
// MicroPython bindings
#define STDIO_FD_IN (0)
#define STDIO_FD_OUT (1)
#define STDIO_FD_ERR (2)
typedef struct _sys_stdio_obj_t {
mp_obj_base_t base;
int fd;
} sys_stdio_obj_t;
#if MICROPY_PY_SYS_STDIO_BUFFER
STATIC const sys_stdio_obj_t stdio_buffer_obj;
#endif
void stdio_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
sys_stdio_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_printf(print, "<io.FileIO %d>", self->fd);
}
STATIC mp_uint_t stdio_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
sys_stdio_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (self->fd == STDIO_FD_IN) {
for (uint i = 0; i < size; i++) {
int c = mp_hal_stdin_rx_chr();
if (c == '\r') {
c = '\n';
}
((byte *)buf)[i] = c;
}
return size;
} else {
*errcode = MP_EPERM;
return MP_STREAM_ERROR;
}
}
STATIC mp_uint_t stdio_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) {
sys_stdio_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (self->fd == STDIO_FD_OUT || self->fd == STDIO_FD_ERR) {
mp_hal_stdout_tx_strn_cooked(buf, size);
return size;
} else {
*errcode = MP_EPERM;
return MP_STREAM_ERROR;
}
}
STATIC mp_uint_t stdio_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
(void)self_in;
if (request == MP_STREAM_POLL) {
return mp_hal_stdio_poll(arg);
} else {
*errcode = MP_EINVAL;
return MP_STREAM_ERROR;
}
}
STATIC mp_obj_t stdio_obj___exit__(size_t n_args, const mp_obj_t *args) {
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(stdio_obj___exit___obj, 4, 4, stdio_obj___exit__);
// TODO gc hook to close the file if not already closed
STATIC const mp_rom_map_elem_t stdio_locals_dict_table[] = {
#if MICROPY_PY_SYS_STDIO_BUFFER
{ MP_ROM_QSTR(MP_QSTR_buffer), MP_ROM_PTR(&stdio_buffer_obj) },
#endif
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)},
{ MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)},
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&stdio_obj___exit___obj) },
};
STATIC MP_DEFINE_CONST_DICT(stdio_locals_dict, stdio_locals_dict_table);
STATIC const mp_stream_p_t stdio_obj_stream_p = {
.read = stdio_read,
.write = stdio_write,
.ioctl = stdio_ioctl,
.is_text = true,
};
STATIC const mp_obj_type_t stdio_obj_type = {
{ &mp_type_type },
.name = MP_QSTR_FileIO,
// TODO .make_new?
.print = stdio_obj_print,
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &stdio_obj_stream_p,
.locals_dict = (mp_obj_dict_t *)&stdio_locals_dict,
};
const sys_stdio_obj_t mp_sys_stdin_obj = {{&stdio_obj_type}, .fd = STDIO_FD_IN};
const sys_stdio_obj_t mp_sys_stdout_obj = {{&stdio_obj_type}, .fd = STDIO_FD_OUT};
const sys_stdio_obj_t mp_sys_stderr_obj = {{&stdio_obj_type}, .fd = STDIO_FD_ERR};
#if MICROPY_PY_SYS_STDIO_BUFFER
STATIC mp_uint_t stdio_buffer_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
for (uint i = 0; i < size; i++) {
((byte *)buf)[i] = mp_hal_stdin_rx_chr();
}
return size;
}
STATIC mp_uint_t stdio_buffer_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) {
mp_hal_stdout_tx_strn(buf, size);
return size;
}
STATIC const mp_stream_p_t stdio_buffer_obj_stream_p = {
.read = stdio_buffer_read,
.write = stdio_buffer_write,
.ioctl = stdio_ioctl,
.is_text = false,
};
STATIC const mp_obj_type_t stdio_buffer_obj_type = {
{ &mp_type_type },
.name = MP_QSTR_FileIO,
.print = stdio_obj_print,
.getiter = mp_identity_getiter,
.iternext = mp_stream_unbuffered_iter,
.protocol = &stdio_buffer_obj_stream_p,
.locals_dict = (mp_obj_dict_t *)&stdio_locals_dict,
};
STATIC const sys_stdio_obj_t stdio_buffer_obj = {{&stdio_buffer_obj_type}, .fd = 0}; // fd unused
#endif

View File

@@ -0,0 +1,222 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
* Copyright (c) 2015 Daniel Campora
*
* 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 "py/obj.h"
#include "shared/timeutils/timeutils.h"
// LEAPOCH corresponds to 2000-03-01, which is a mod-400 year, immediately
// after Feb 29. We calculate seconds as a signed integer relative to that.
//
// Our timebase is relative to 2000-01-01.
#define LEAPOCH ((31 + 29) * 86400)
#define DAYS_PER_400Y (365 * 400 + 97)
#define DAYS_PER_100Y (365 * 100 + 24)
#define DAYS_PER_4Y (365 * 4 + 1)
STATIC const uint16_t days_since_jan1[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
bool timeutils_is_leap_year(mp_uint_t year) {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
// month is one based
mp_uint_t timeutils_days_in_month(mp_uint_t year, mp_uint_t month) {
mp_uint_t mdays = days_since_jan1[month] - days_since_jan1[month - 1];
if (month == 2 && timeutils_is_leap_year(year)) {
mdays++;
}
return mdays;
}
// compute the day of the year, between 1 and 366
// month should be between 1 and 12, date should start at 1
mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date) {
mp_uint_t yday = days_since_jan1[month - 1] + date;
if (month >= 3 && timeutils_is_leap_year(year)) {
yday += 1;
}
return yday;
}
void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t, timeutils_struct_time_t *tm) {
// The following algorithm was adapted from musl's __secs_to_tm and adapted
// for differences in MicroPython's timebase.
mp_int_t seconds = t - LEAPOCH;
mp_int_t days = seconds / 86400;
seconds %= 86400;
if (seconds < 0) {
seconds += 86400;
days -= 1;
}
tm->tm_hour = seconds / 3600;
tm->tm_min = seconds / 60 % 60;
tm->tm_sec = seconds % 60;
mp_int_t wday = (days + 2) % 7; // Mar 1, 2000 was a Wednesday (2)
if (wday < 0) {
wday += 7;
}
tm->tm_wday = wday;
mp_int_t qc_cycles = days / DAYS_PER_400Y;
days %= DAYS_PER_400Y;
if (days < 0) {
days += DAYS_PER_400Y;
qc_cycles--;
}
mp_int_t c_cycles = days / DAYS_PER_100Y;
if (c_cycles == 4) {
c_cycles--;
}
days -= (c_cycles * DAYS_PER_100Y);
mp_int_t q_cycles = days / DAYS_PER_4Y;
if (q_cycles == 25) {
q_cycles--;
}
days -= q_cycles * DAYS_PER_4Y;
mp_int_t years = days / 365;
if (years == 4) {
years--;
}
days -= (years * 365);
/* We will compute tm_yday at the very end
mp_int_t leap = !years && (q_cycles || !c_cycles);
tm->tm_yday = days + 31 + 28 + leap;
if (tm->tm_yday >= 365 + leap) {
tm->tm_yday -= 365 + leap;
}
tm->tm_yday++; // Make one based
*/
tm->tm_year = 2000 + years + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
// Note: days_in_month[0] corresponds to March
STATIC const int8_t days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29};
mp_int_t month;
for (month = 0; days_in_month[month] <= days; month++) {
days -= days_in_month[month];
}
tm->tm_mon = month + 2;
if (tm->tm_mon >= 12) {
tm->tm_mon -= 12;
tm->tm_year++;
}
tm->tm_mday = days + 1; // Make one based
tm->tm_mon++; // Make one based
tm->tm_yday = timeutils_year_day(tm->tm_year, tm->tm_mon, tm->tm_mday);
}
// returns the number of seconds, as an integer, since 2000-01-01
mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month,
mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
return
second
+ minute * 60
+ hour * 3600
+ (timeutils_year_day(year, month, date) - 1
+ ((year - 2000 + 3) / 4) // add a day each 4 years starting with 2001
- ((year - 2000 + 99) / 100) // subtract a day each 100 years starting with 2001
+ ((year - 2000 + 399) / 400) // add a day each 400 years starting with 2001
) * 86400
+ (year - 2000) * 31536000;
}
mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday,
mp_int_t hours, mp_int_t minutes, mp_int_t seconds) {
// Normalize the tuple. This allows things like:
//
// tm_tomorrow = list(time.localtime())
// tm_tomorrow[2] += 1 # Adds 1 to mday
// tomorrow = time.mktime(tm_tomorrow)
//
// And not have to worry about all the weird overflows.
//
// You can subtract dates/times this way as well.
minutes += seconds / 60;
if ((seconds = seconds % 60) < 0) {
seconds += 60;
minutes--;
}
hours += minutes / 60;
if ((minutes = minutes % 60) < 0) {
minutes += 60;
hours--;
}
mday += hours / 24;
if ((hours = hours % 24) < 0) {
hours += 24;
mday--;
}
month--; // make month zero based
year += month / 12;
if ((month = month % 12) < 0) {
month += 12;
year--;
}
month++; // back to one based
while (mday < 1) {
if (--month == 0) {
month = 12;
year--;
}
mday += timeutils_days_in_month(year, month);
}
while ((mp_uint_t)mday > timeutils_days_in_month(year, month)) {
mday -= timeutils_days_in_month(year, month);
if (++month == 13) {
month = 1;
year++;
}
}
return timeutils_seconds_since_2000(year, month, mday, hours, minutes, seconds);
}
// Calculate the weekday from the date.
// The result is zero based with 0 = Monday.
// by Michael Keith and Tom Craver, 1990.
int timeutils_calc_weekday(int y, int m, int d) {
return ((d += m < 3 ? y-- : y - 2, 23 * m / 9 + d + 4 + y / 4 - y / 100 + y / 400) + 6) % 7;
}

View File

@@ -0,0 +1,106 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
* Copyright (c) 2015 Daniel Campora
*
* 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_LIB_TIMEUTILS_TIMEUTILS_H
#define MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H
// The number of seconds between 1970/1/1 and 2000/1/1 is calculated using:
// time.mktime((2000,1,1,0,0,0,0,0,0)) - time.mktime((1970,1,1,0,0,0,0,0,0))
#define TIMEUTILS_SECONDS_1970_TO_2000 (946684800ULL)
typedef struct _timeutils_struct_time_t {
uint16_t tm_year; // i.e. 2014
uint8_t tm_mon; // 1..12
uint8_t tm_mday; // 1..31
uint8_t tm_hour; // 0..23
uint8_t tm_min; // 0..59
uint8_t tm_sec; // 0..59
uint8_t tm_wday; // 0..6 0 = Monday
uint16_t tm_yday; // 1..366
} timeutils_struct_time_t;
bool timeutils_is_leap_year(mp_uint_t year);
mp_uint_t timeutils_days_in_month(mp_uint_t year, mp_uint_t month);
mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date);
void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t,
timeutils_struct_time_t *tm);
mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month,
mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second);
mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday,
mp_int_t hours, mp_int_t minutes, mp_int_t seconds);
// Select the Epoch used by the port.
#if MICROPY_EPOCH_IS_1970
static inline void timeutils_seconds_since_epoch_to_struct_time(uint64_t t, timeutils_struct_time_t *tm) {
// TODO this will give incorrect results for dates before 2000/1/1
return timeutils_seconds_since_2000_to_struct_time(t - TIMEUTILS_SECONDS_1970_TO_2000, tm);
}
static inline uint64_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds) {
return timeutils_mktime_2000(year, month, mday, hours, minutes, seconds) + TIMEUTILS_SECONDS_1970_TO_2000;
}
static inline uint64_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month,
mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
// TODO this will give incorrect results for dates before 2000/1/1
return timeutils_seconds_since_2000(year, month, date, hour, minute, second) + TIMEUTILS_SECONDS_1970_TO_2000;
}
static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) {
return ns / 1000000000ULL;
}
static inline uint64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(uint64_t ns) {
return ns;
}
#else // Epoch is 2000
#define timeutils_seconds_since_epoch_to_struct_time timeutils_seconds_since_2000_to_struct_time
#define timeutils_seconds_since_epoch timeutils_seconds_since_2000
#define timeutils_mktime timeutils_mktime_2000
static inline uint64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_uint_t s) {
return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL;
}
static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) {
return ns / 1000000000ULL - TIMEUTILS_SECONDS_1970_TO_2000;
}
static inline int64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(int64_t ns) {
return ns + TIMEUTILS_SECONDS_1970_TO_2000 * 1000000000ULL;
}
#endif
int timeutils_calc_weekday(int y, int m, int d);
#endif // MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H

View File

@@ -0,0 +1,129 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2017 Linaro Limited
*
* 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 <string.h>
#include "py/mphal.h"
#include "py/gc.h"
#include "py/runtime.h"
#include "py/compile.h"
#include "upytesthelper.h"
static const char *test_exp_output;
static int test_exp_output_len, test_rem_output_len;
static int test_failed;
static void *heap_start, *heap_end;
void upytest_set_heap(void *start, void *end) {
heap_start = start;
heap_end = end;
}
void upytest_set_expected_output(const char *output, unsigned len) {
test_exp_output = output;
test_exp_output_len = test_rem_output_len = len;
test_failed = false;
}
bool upytest_is_failed(void) {
if (test_failed) {
return true;
}
#if 0
if (test_rem_output_len != 0) {
printf("remaining len: %d\n", test_rem_output_len);
}
#endif
return test_rem_output_len != 0;
}
// MP_PLAT_PRINT_STRN() should be redirected to this function.
// It will pass-thru any content to mp_hal_stdout_tx_strn_cooked()
// (the dfault value of MP_PLAT_PRINT_STRN), but will also match
// it to the expected output as set by upytest_set_expected_output().
// If mismatch happens, upytest_is_failed() returns true.
void upytest_output(const char *str, mp_uint_t len) {
if (!test_failed) {
if (len > test_rem_output_len) {
test_failed = true;
} else {
test_failed = memcmp(test_exp_output, str, len);
#if 0
if (test_failed) {
printf("failed after char %u, within %d chars, res: %d\n",
test_exp_output_len - test_rem_output_len, (int)len, test_failed);
for (int i = 0; i < len; i++) {
if (str[i] != test_exp_output[i]) {
printf("%d %02x %02x\n", i, str[i], test_exp_output[i]);
}
}
}
#endif
test_exp_output += len;
test_rem_output_len -= len;
}
}
mp_hal_stdout_tx_strn_cooked(str, len);
}
void upytest_execute_test(const char *src) {
// To provide clean room for each test, interpreter and heap are
// reinitialized before running each.
gc_init(heap_start, heap_end);
mp_init();
mp_obj_list_init(mp_sys_path, 0);
#if MICROPY_MODULE_FROZEN
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen));
#endif
mp_obj_list_init(mp_sys_argv, 0);
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
qstr source_name = lex->source_name;
mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT);
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false);
mp_call_function_0(module_fun);
nlr_pop();
} else {
mp_obj_t exc = (mp_obj_t)nlr.ret_val;
if (mp_obj_is_subclass_fast(mp_obj_get_type(exc), &mp_type_SystemExit)) {
// Assume that sys.exit() is called to skip the test.
// TODO: That can be always true, we should set up convention to
// use specific exit code as skip indicator.
tinytest_set_test_skipped_();
goto end;
}
mp_obj_print_exception(&mp_plat_print, exc);
tt_abort_msg("Uncaught exception\n");
}
if (upytest_is_failed()) {
tinytest_set_test_failed_();
}
end:
mp_deinit();
}

View File

@@ -0,0 +1,37 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2017 Linaro Limited
*
* 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 <stdbool.h>
#include <stdio.h>
#include "py/mpconfig.h"
#include "lib/tinytest/tinytest.h"
#include "lib/tinytest/tinytest_macros.h"
void upytest_set_heap(void *start, void *end);
void upytest_set_expected_output(const char *output, unsigned len);
void upytest_execute_test(const char *src);
void upytest_output(const char *str, mp_uint_t len);
bool upytest_is_failed(void);